Files
AutistMask/README.md
sneak 9a6d1f6255
Some checks failed
check / check (push) Has been cancelled
Add dust transaction filter to catch native ETH poisoning
Address poisoning attacks also use real native ETH dust transfers
(e.g. 1 gwei) from look-alike addresses. Token-level filters cannot
catch these. Add a configurable dust threshold (default 100,000 gwei
/ 0.0001 ETH) that hides transactions below the threshold from
history. The threshold is editable in Settings and the filter can be
disabled entirely. Document the specific attack tx in the README.
2026-02-26 15:29:48 +07:00

649 lines
29 KiB
Markdown

# AutistMask
AutistMask is a GPL-licensed JavaScript browser extension by
[@sneak](https://sneak.berlin) that provides a minimal Ethereum wallet for
Chrome and Firefox. It manages HD wallets derived from BIP-39 seed phrases and
supports sending and receiving ETH and ERC-20 tokens, as well as web3 site
connection and authentication via the EIP-1193 provider API.
## Getting Started
```bash
git clone https://git.eeqj.de/sneak/autistmask.git
cd autistmask
make install
make build
```
Load the extension:
- **Chrome**: Navigate to `chrome://extensions/`, enable "Developer mode", click
"Load unpacked", and select the `dist/chrome/` directory.
- **Firefox**: Navigate to `about:debugging#/runtime/this-firefox`, click "Load
Temporary Add-on", and select `dist/firefox/manifest.json`.
## Rationale
MetaMask has become bloated with swap UIs, portfolio dashboards, analytics,
tracking, and advertisements. It is no longer a simple wallet. Most alternatives
(Rabby, Rainbow, etc.) only support Chromium browsers, leaving Firefox users
without a usable option.
AutistMask exists to provide the absolute minimum viable Ethereum wallet
experience: manage seed phrases, derive HD addresses, send and receive ETH and
ERC-20 tokens, and connect to web3 sites. Nothing else. No swaps (that's what
the web is for), no analytics, no tracking, no ads, no portfolio views, no NFT
galleries. Just a wallet.
## Design
AutistMask is a browser extension targeting both Chrome (Manifest V3) and
Firefox (Manifest V2/V3 as supported). The codebase is shared between both
targets with platform-specific manifest files and a build step that produces
separate output directories.
### Architecture
```
src/
background/ — service worker / background script
index.js — extension lifecycle, message routing
wallet.js — wallet management (create, import, derive via ethers.js)
provider.js — EIP-1193 JSON-RPC provider implementation
popup/ — popup UI (the main wallet interface)
index.html
index.js
styles/ — CSS (Tailwind)
content/ — content script injected into web pages
index.js — injects the provider into page context
inpage.js — the window.ethereum provider object
shared/ — shared utilities
vault.js — encrypted storage via libsodium
constants.js — chain IDs, default RPC endpoints, ERC-20 ABI
manifest/
chrome.json — Manifest V3 for Chrome
firefox.json — Manifest V2/V3 for Firefox
```
### UI Design Philosophy
The UI follows a "Universal Paperclips" aesthetic — a deliberately spartan,
almost brutalist approach. The guiding principle is that an unskilled,
non-technical person should be able to figure out how to use it without any
crypto knowledge.
#### Visual Style
- **Monochrome**: Black text on white background. No brand colors, no gradients,
no color-coding. Color may be introduced later for specific semantic purposes
(e.g. error states) but the baseline is monochrome.
- **Text-first**: Every piece of information is presented as text. Balances are
numbers. Addresses are hex strings. Status is a sentence. No progress spinners
with animations — a text status line is sufficient.
- **Monospace font**: All text is rendered in the system monospace font.
Ethereum addresses, transaction hashes, and balances are inherently
fixed-width data. Rather than mixing proportional and monospace fonts, we use
monospace everywhere for visual consistency and alignment.
- **No images**: Zero image assets in the entire extension. No logos, no
illustrations, no token icons. Token identity is conveyed by symbol text (ETH,
USDC, etc.).
- **Tailwind CSS**: Utility-first CSS via Tailwind. No custom CSS classes for
styling. Tailwind is configured with a minimal monochrome palette. This keeps
the styling co-located with the markup and eliminates CSS file management.
- **Vanilla JS**: No framework (React, Vue, Svelte, etc.). The popup UI is small
enough that vanilla JS with simple view switching is sufficient. A framework
would add bundle size, build complexity, and attack surface for no benefit at
this scale.
- **360x600 popup**: Standard browser extension popup dimensions. The UI is
designed for this fixed viewport — no responsive breakpoints needed.
#### No Layout Shift
Asynchronous state changes (clipboard confirmation, transaction status, error
messages, flash notifications) must never move existing UI elements. All dynamic
content areas reserve their space up front using `min-height` or always-present
wrapper elements. `visibility: hidden` is preferred over `display: none` when
the element's space must be preserved. This prevents jarring content jumps that
disorient users and avoids mis-clicks caused by shifting buttons.
#### Clickable Affordance
Every interactive element must visually indicate that it is clickable. Buttons
use a visible border, padding, and a hover state (invert to white-on-black).
Text that triggers an action (e.g. "Import private key") uses an underline. No
invisible hit targets, no bare text that happens to have a click handler. If it
does something when you click it, it must look like it does something when you
click it.
#### Display Consistency
The same data must be formatted identically everywhere it appears. Token and ETH
amounts are always displayed with exactly 4 decimal places (e.g. "1.0500 ETH",
"17.1900 USDT") — in balance lists, transaction lists, transaction details, send
confirmations, and any other context. Timestamps include both an ISO datetime
and a humanized relative age wherever shown. If a formatting rule applies in one
place, it applies in every place. Users should never see the same value rendered
differently on two screens.
#### Language & Labeling
All user-facing text avoids crypto jargon wherever possible:
- "Recovery phrase" instead of "seed phrase", "mnemonic", or "BIP-39 mnemonic"
- "Address" instead of "account", "derived key", or "HD child"
- "Password" instead of "encryption key" or "vault passphrase"
- "Private key" instead of "secret key" or "signing key"
- Buttons use plain verbs: "Send", "Receive", "Copy address", "Add", "Back",
"Cancel", "Lock", "Unlock", "Allow", "Deny"
- No bracket notation like `[locked]` or `[setup]` — just plain titles
- Helpful inline descriptions where needed (e.g. "This password locks the wallet
on this device. It is not the same as your recovery phrase.")
- Error messages are full sentences ("Please enter your password." not "password
required")
#### Full Identifiers Policy
Addresses, transaction hashes, contract addresses, and all other cryptographic
identifiers are displayed in full whenever possible — never truncated. Address
poisoning attacks exploit truncated displays by generating fraud addresses that
share the same prefix and suffix as a legitimate address. If a user only sees
`0xAbCd...1234`, an attacker can create an address with the same visible
characters and trick the user into sending funds to it. Showing the complete
identifier defeats this class of attack. Truncation is only acceptable in
space-constrained contexts where the full identifier is accessible one tap away
(e.g. a tooltip or copy action).
#### Data Model
The core hierarchy is **Wallets → Addresses**:
- A **wallet** is either:
- An **HD wallet** (recovery phrase): generates multiple addresses from a
single 12/24 word recovery phrase using BIP-39/BIP-44 derivation. The user
can add more addresses with a "+" button.
- A **key wallet** (private key): a single address imported directly from a
private key. No "+" button since there is only one address.
- An **address** holds ETH and any user-added ERC-20 tokens.
- The user can have multiple wallets, each with multiple addresses (HD) or a
single address (key).
#### Navigation
The main view shows all addresses grouped by wallet, with ETH balances inline.
The user taps an address to see its detail view (full address, balance, tokens,
send/receive). Navigation is flat — every view has a "Back" or "Cancel" button
that returns to the previous context. No deep nesting, no tabs, no hamburger
menus.
### Screen Map
Navigation uses a stack model (like iOS): each action pushes a screen onto the
stack, and "Back" pops it. The root screen is either Welcome (no wallets) or
Home (has wallets). Screens are listed below with their elements and
transitions.
#### Welcome
- **When**: No wallets exist yet.
- **Elements**: "AutistMask" heading, brief intro text, "Add wallet" button.
- **Transitions**:
- "Add wallet" → pushes **AddWallet**
#### Home
- **When**: At least one wallet exists. This is the root screen.
- **Elements**:
- Header: "AutistMask", Settings button
- Total ETH balance across all addresses (large text)
- Total USD value (small text below ETH total, cached 5 min)
- List of wallets, each showing:
- Wallet name (editable — tap to rename)
- "+" button (HD wallets only) to derive next address
- List of addresses under that wallet:
- Address name (editable — tap to rename, default "Address N")
- Full address (untruncated)
- ETH balance + USD value (small text)
- "+ Add wallet" button at the bottom
- **Transitions**:
- Tap address → pushes **AddressDetail**
- "+" on wallet → derives address inline (no screen change)
- "+ Add wallet" → pushes **AddWallet**
- Settings → pushes **Settings**
#### AddWallet
- **When**: User wants to add a new wallet (from Home or Welcome).
- **Elements**:
- "Add Wallet" heading
- Instruction text
- Die button `[die]` (generates random recovery phrase, can be clicked
repeatedly)
- Recovery phrase textarea (empty by default, or filled by die)
- Backup warning box (shown after die is clicked)
- "Add" button, "Back" button
- "Have a private key instead?" link → pushes **ImportKey**
- **Transitions**:
- "Add" (valid phrase) → pops to **Home**
- "Back" → pops to previous (Home or Welcome)
- "Have a private key instead?" → pushes **ImportKey**
#### ImportKey
- **When**: User wants to import a single private key.
- **Elements**:
- "Import Private Key" heading
- Instruction text
- Private key input (password-masked)
- "Import" button, "Back" button
- **Transitions**:
- "Import" (valid key) → pops to **Home**
- "Back" → pops to previous
#### AddressDetail
- **When**: User tapped an address from Home.
- **Elements**:
- Header: wallet name, "Back" button
- Address name (editable — tap to rename)
- ENS name (if resolved, shown above address)
- Full address (tap to copy, "Copied!" feedback)
- ETH balance (large) + USD value (small)
- "Send" button, "Receive" button
- Token list with balances
- "+ Add" token button
- **Transitions**:
- "Send" → pushes **Send**
- "Receive" → pushes **Receive**
- "+ Add" → pushes **AddToken**
- "Back" → pops to **Home**
#### Send
- **When**: User wants to send ETH or a token from this address.
- **Elements**:
- "Send" heading
- Token selector (ETH + any added tokens)
- "To" input (accepts address or ENS name, resolves before sending)
- Amount input
- Fee estimate (shown after entering amount)
- "Send" button, "Cancel" button
- Status area (resolving ENS, confirming, errors)
- **Transitions**:
- "Send" (valid) → prompts for password (to decrypt private key), submits
transaction, shows result, stays on screen
- "Cancel" → pops to **AddressDetail**
#### Receive
- **When**: User wants to receive funds at this address.
- **Elements**:
- "Receive" heading
- Instruction text
- QR code encoding the address
- Full address (displayed, selectable)
- "Copy address" button
- "Back" button
- **Transitions**:
- "Back" → pops to **AddressDetail**
#### AddToken
- **When**: User wants to track an ERC-20 token on this address.
- **Elements**:
- "Add Token" heading
- Instruction text (find contract address on Etherscan)
- Contract address input
- Token info preview (name, symbol — fetched from contract)
- "Add" button, "Cancel" button
- **Transitions**:
- "Add" (valid contract) → pops to **AddressDetail**
- "Cancel" → pops to **AddressDetail**
#### Settings
- **When**: User tapped Settings from Home.
- **Elements**:
- "Settings" heading
- Network section: RPC endpoint URL input, "Save" button, explanatory text
- "Back" button
- **Transitions**:
- "Back" → pops to **Home**
#### Approval (future)
- **When**: A connected website requests wallet access or a transaction
signature. Opened by the background script, not by user navigation.
- **Elements**:
- "A website is requesting access" heading
- Site origin (bold)
- Request type and details (preformatted)
- "Allow" button, "Deny" button
- **Transitions**:
- "Allow" / "Deny" → closes popup (returns result to background script)
### External Services
AutistMask is not a fully self-contained offline tool. It necessarily
communicates with external services to function as a wallet:
- **Ethereum JSON-RPC endpoint**: The extension needs an Ethereum node to query
balances (`eth_getBalance`), read ERC-20 token contracts (`eth_call`),
estimate gas (`eth_estimateGas`), fetch nonces (`eth_getTransactionCount`),
broadcast transactions (`eth_sendRawTransaction`), and check transaction
receipts. The default endpoint is a public RPC (configurable by the user to
any endpoint they prefer, including a local node). This is the only external
service the extension talks to.
- **CoinDesk CADLI price API**: Used to fetch ETH/USD and token/USD prices for
displaying fiat values. The price is cached for 5 minutes to avoid excessive
requests. No API key required. No user data is sent — only a list of token
symbols.
What the extension does NOT do:
- No analytics or telemetry services
- No token list APIs (user adds tokens manually by contract address)
- No phishing/blocklist APIs
- No Infura/Alchemy dependency (any JSON-RPC endpoint works)
- No backend servers operated by the developer
The user's RPC endpoint and the CoinDesk price API are the only external
services. Users who want maximum privacy can point the RPC at their own node
(price fetching can be disabled in a future version).
### Dependencies
AutistMask uses two runtime libraries. All cryptographic operations are
delegated to these libraries — see the Crypto Policy section below.
| Package | Version | License | Purpose |
| ------------------------- | ------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ethers` | 6.16.0 | MIT | All Ethereum operations: BIP-39 mnemonic generation/validation, BIP-32/BIP-44 HD key derivation (`m/44'/60'/0'/0/n`), secp256k1 signing, transaction construction, ERC-20 contract interaction, JSON-RPC communication, address derivation (keccak256). |
| `libsodium-wrappers-sumo` | 0.8.2 | ISC | Password-based encryption of secrets at rest: Argon2id key derivation (`crypto_pwhash`), authenticated encryption (`crypto_secretbox` / XSalsa20-Poly1305). |
| `qrcode` | 1.5.4 | MIT | QR code generation for the Receive screen (renders address as scannable QR on canvas). |
Dev dependencies (not shipped in extension):
| Package | Version | License | Purpose |
| ------------------ | ------- | ------- | ------------------------- |
| `esbuild` | 0.27.3 | MIT | JS bundler (inlines deps) |
| `tailwindcss` | 4.2.1 | MIT | CSS compilation |
| `@tailwindcss/cli` | 4.2.1 | MIT | Tailwind CLI |
| `jest` | 30.2.0 | MIT | Test runner |
| `prettier` | 3.8.1 | MIT | Code formatter |
### Crypto Policy
**No raw crypto primitives in application code.** If the strings `aes`, `sha`,
`pbkdf`, `hmac`, `encrypt`, `decrypt`, `hash`, `cipher`, `digest`, `sign`
(case-insensitive) appear in our own source code (outside of `node_modules/`),
it is almost certainly a bug. All cryptographic operations must go through
`ethers` or `libsodium-wrappers-sumo`. This policy exists because:
- Rolling your own crypto is the single most common source of security
vulnerabilities in wallet software.
- Both libraries are widely audited and battle-tested.
- Keeping crypto out of application code makes security review tractable:
reviewers only need to verify that we call the libraries correctly, not that
we implemented crypto correctly.
Exceptions require explicit authorization in a code comment referencing this
policy.
### DEBUG Mode Policy
The `DEBUG` constant in the popup JS enables a red "DEBUG / INSECURE" banner and
a hardcoded test mnemonic. **DEBUG mode must behave as close to normal mode as
possible.** No `if (DEBUG)` branches that skip functionality, bypass security
flows, or alter program behavior beyond the banner and the hardcoded mnemonic.
Adding new DEBUG-conditional branches requires explicit approval from the
project owner.
### Key Decisions
- **No framework**: The popup UI is vanilla JS and HTML. The extension is small
enough that a framework adds unnecessary complexity and attack surface.
- **Split storage model**: Public data (xpubs, derived addresses, token lists,
balances) is stored unencrypted in extension local storage so the user can
view their wallets and balances at any time without entering a password.
Private data (recovery phrases, private keys) will be encrypted at rest using
libsodium — a password is only required when the user needs to sign a
transaction or message. The encryption scheme for private data:
- The user's password is run through Argon2id (`crypto_pwhash`) to derive a
256-bit encryption key. Argon2id is memory-hard, making GPU/ASIC brute
force attacks expensive.
- The derived key encrypts the secret material using XSalsa20-Poly1305
(`crypto_secretbox`), which provides authenticated encryption (the
ciphertext cannot be tampered with without detection).
- Stored blob: `{ salt, nonce, ciphertext }` (the auth tag is part of the
`crypto_secretbox` output).
- **The password is NOT used in address derivation.** It exists solely to
protect the recovery phrase / private key on disk. Anyone with the
recovery phrase can restore the wallet on any device without this
password. This matches MetaMask's behavior.
- **BIP-39 / BIP-44 via ethers.js**: Mnemonic generation, validation, and HD key
derivation (`m/44'/60'/0'/0/n`) are handled entirely by ethers.js. The BIP-39
passphrase is always empty (matching MetaMask and most wallet software). The
user's password is completely separate and has no effect on which addresses
are generated.
- **ethers.js for everything Ethereum**: Transaction construction, signing, gas
estimation, RPC communication, ERC-20 contract calls, and address derivation
are all handled by ethers.js. This means zero hand-rolled Ethereum logic.
- **EIP-1193 provider**: The content script injects a `window.ethereum` object
that implements the EIP-1193 provider interface, enabling web3 site
connectivity.
- **Minimal RPC**: The extension communicates with Ethereum nodes via JSON-RPC
through ethers.js's `JsonRpcProvider`. The default endpoint is configurable.
No Infura dependency — users can point it at any Ethereum JSON-RPC endpoint.
### Supported Functionality
- Create new HD wallet (generates 12-word recovery phrase)
- Import HD wallet from existing 12 or 24 word recovery phrase
- Import single-address wallet from private key
- Add multiple addresses within an HD wallet
- Manage multiple wallets simultaneously
- View ETH balance per address
- View ERC-20 token balances (user adds token by contract address)
- Send ETH to an address
- Send ERC-20 tokens to an address
- Receive ETH/tokens (display address, copy to clipboard)
- Connect to web3 sites (EIP-1193 `eth_requestAccounts`)
- Sign transactions requested by connected sites
- Sign messages (`personal_sign`, `eth_sign`)
- Lock/unlock with password
- Configurable RPC endpoint
- Future: USD value display (and other fiat currencies)
### Address Poisoning and Fake Token Transfer Attacks
During development, one of our test addresses
(`0x66133E8ea0f5D1d612D2502a968757D1048c214a`) sent 0.005 ETH to
`0xC3c693Ae04BaD5f13C45885C1e85a9557798f37E`. Within seconds, a fraudulent
transaction appeared in the address's token transfer history
(`0x85215772ed26ea8b39c2b3b18779030487efbe0b5fd7e882592b2f62b837be84`) showing a
0.005 "ETH" transfer from our address to
`0xC3C0AEA127c575B9FFD03BF11C6a878e8979c37F` — a scam address whose first four
characters (`0xC3C0`) visually resemble the legitimate recipient (`0xC3c6`).
**How it works:** A scammer deploys a malicious ERC-20 contract (in this case,
`0xD05339f9Ea5ab9d9F03B9d57F671d2abD1F55c82`, a fake token calling itself
"Ethereum" with symbol "ETH" and zero holders). This contract has a function
that emits an ERC-20 `Transfer(from, to, amount)` event with arbitrary
parameters. The EVM does not enforce that the `from` address in a Transfer event
actually initiated or authorized the transfer — any contract can emit any event
with any parameters. The scammer calls their contract, which emits a Transfer
event claiming the victim sent tokens to the scam address. Every blockchain
indexer (Blockscout, Etherscan, etc.) sees a valid Transfer event log and
indexes it as a real token transfer.
**The attack has two goals:**
1. **Autocomplete poisoning**: Wallets that offer address autocomplete based on
transaction history will suggest the scam address (which looks similar to a
legitimate recent recipient) when the user starts typing. The user copies the
wrong address and sends real funds to the scammer.
2. **Transaction history confusion**: The fake transfer appears in the victim's
history as an outbound transaction, making it look like the user sent funds
to the scam address. Users who copy-paste addresses from their own
transaction history may grab the wrong one.
**What AutistMask does about it:**
- **Minimal, careful truncation**: Where space constraints require truncation
(e.g. the transaction history list), AutistMask truncates conservatively —
displaying enough characters that generating a vanity address matching the
visible portion is computationally infeasible. All confirmation screens
(transaction signing, send confirmation) display the complete untruncated
address. Users should always verify the full address on the confirmation
screen before signing or sending.
- **Known token symbol verification**: AutistMask ships a hardcoded list of the
top 250 ERC-20 tokens with their legitimate contract addresses and symbols.
Any token transfer claiming a symbol from this list (e.g. "ETH", "USDT",
"USDC") but originating from an unrecognized contract address is identified as
a spoof and filtered from display. The fake "Ethereum" token in the attack
above used symbol "ETH" from contract
`0xD05339f9Ea5ab9d9F03B9d57F671d2abD1F55c82`, which does not match the known
WETH contract — so it would be caught by this check.
- **Low-holder token filtering**: Token transfers from ERC-20 contracts with
fewer than 1,000 holders are hidden from transaction history by default.
Legitimate tokens have substantial holder counts; poisoning tokens typically
have zero. This catches new poisoning contracts that use novel symbols not in
the known token list.
- **Fraud contract blocklist**: AutistMask maintains a local list of known fraud
contract addresses. Token transfers involving these contracts are filtered
from the transaction history display. The list is populated when a fraudulent
transfer is detected and persists across sessions.
- **Send-side token filtering**: Tokens with fewer than 1,000 holders are
excluded from the token selector on the send screen. This prevents users from
accidentally interacting with a spoofed token that appeared in their balance
via a fake Transfer event.
- **Dust transaction filtering**: A second wave of the same attack used real
native ETH transfers instead of fake tokens. Transaction
`0x2708ebddfb9b5fa3f7a89d3ea398ef9fd8771b83ed861ecb7c21cd55d18edc74` sent 1
gwei (0.000000001 ETH) from `0xC3c6B3b4402bD78A9582aB6b00E747769344F37E`
another look-alike of the legitimate recipient `0xC3c693...`. Because this is
a real ETH transfer (not a fake token), none of the token-level filters catch
it. AutistMask hides transactions below a configurable dust threshold
(default: 100,000 gwei / 0.0001 ETH). This is high enough to filter poisoning
dust while low enough to preserve any transfer a user would plausibly care
about. The threshold is user-configurable in Settings.
- **User-configurable**: All of the above filters (known symbol verification,
low-holder threshold, fraud contract blocklist, dust threshold) are settings
that default to on but can be individually disabled by the user. AutistMask is
designed as a sharp tool — users who understand the risks can configure the
wallet to show everything unfiltered, unix-style.
### Non-Goals
- Token swaps (use a DEX in the browser)
- NFT display or management
- Multi-chain support (Ethereum mainnet only, for now)
- Analytics, telemetry, or tracking of any kind
- Advertisements or promotions
- Phishing detection
- Hardware wallet support (maybe later)
- Token list auto-discovery (user adds tokens manually)
- Fiat on/off ramps
- Browser notifications
- Transaction history (use Etherscan)
## TODO — 0.1.0 MVP
Everything needed for a minimal working wallet that can send and receive ETH.
### Done
- [x] Project scaffolding (Makefile, Dockerfile, CI, manifests, esbuild)
- [x] Tailwind CSS build pipeline
- [x] Popup UI shell with screen stacking (Welcome, AddWallet, Home,
AddressDetail, Send, Receive, Settings)
- [x] BIP-39 mnemonic generation via ethers.js (die button)
- [x] BIP-39 mnemonic validation on import
- [x] BIP-32/BIP-44 HD key derivation (real addresses from xpub)
- [x] Private key import (real address via ethers.Wallet)
- [x] Xpub stored unencrypted for read-only address derivation
- [x] State persistence to extension storage (survives popup close)
- [x] Live ETH balance fetching via JSON-RPC (`eth_getBalance`)
- [x] ENS reverse lookup (address → name) and forward resolution (name → address
in Send field)
- [x] ETH/USD price fetching via CoinDesk API
- [x] USD value display next to ETH balances
- [x] Full address display everywhere (no truncation)
- [x] Token list module with ~150 ERC-20 tokens ordered by market cap
### Wallet Management
- [ ] Rename wallets (tap wallet name on Home to edit)
- [ ] Rename addresses (tap address name on AddressDetail to edit)
- [ ] Delete wallet (with confirmation)
- [ ] Delete address from HD wallet (with confirmation)
- [ ] Show wallet's recovery phrase (requires password, from Settings or wallet
context menu)
### Sending
- [x] Encrypt recovery phrase / private key with password via libsodium
(Argon2id + XSalsa20-Poly1305)
- [x] Password prompt on Send (decrypt private key to construct transaction)
- [x] Transaction construction via ethers.js (to, value, gasLimit, gasPrice)
- [ ] Gas estimation and fee display before confirming
- [x] Broadcast transaction via `eth_sendRawTransaction`
- [x] Transaction status feedback (pending → confirmed / failed)
### Receiving
- [x] QR code generation for address (qrcode library, renders to canvas)
### Display
- [ ] Home screen: total ETH balance summed across all addresses
- [ ] Home screen: total USD value (small text under total ETH)
- [ ] Cache ETH/USD price for 5 minutes (don't re-fetch on every popup open)
- [ ] Per-address USD value in small text under ETH balance everywhere
### Tokens (ERC-20)
- [ ] Add token by contract address (fetch name/symbol/decimals from contract)
- [ ] Display ERC-20 token balances per address
- [ ] Send ERC-20 tokens
### Testing
- [ ] Tests for mnemonic generation and address derivation
- [ ] Tests for xpub derivation and child address generation
- [ ] Tests for token list module (getTopTokenPrices, getTopTokenSymbols)
- [ ] Test on Chrome (Manifest V3)
- [ ] Test on Firefox (Manifest V2)
### Scam List
- [ ] Research and document each address in scamlist.js (what it is, why it's on
the list, source)
- [ ] Add more known fraud addresses from Etherscan labels (drainers, phishing,
address poisoning deployers)
### Post-MVP
- [ ] EIP-1193 provider injection (window.ethereum) for web3 site connectivity
- [ ] Site connection approval flow
- [ ] Transaction signing approval flow (requests from connected sites)
- [ ] Message signing (`personal_sign`, `eth_sign`)
- [ ] Multi-currency fiat display (EUR, GBP, etc.)
- [ ] Security audit of key management
## License
GPL-3.0. See [LICENSE](LICENSE).
## Author
[@sneak](https://sneak.berlin)