diff --git a/README.md b/README.md index 0ddd3ca..8b89104 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,23 @@ 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. +The most popular browser-based EVM wallet has a cute mascot, but sucks now. It +has tracking, ads, preferred placement for swaps, tx broadcast fuckery, +intercepts tx status links to their own site instead of going to Etherscan, etc. +None of the common alternatives work on Firefox. + +Hence, a minimally viable ERC20 browser wallet/signer that works cross-platform. +Everything you need, nothing you don't. We import as few libraries as possible, +don't implement any crypto, and don't send user-specific data anywhere but a +(user-configurable) Ethereum RPC endpoint (which defaults to a public node). The +extension contacts precisely two external services: the configured RPC node for +blockchain interactions, and a public CoinDesk API (no API key) to get realtime +price information. + +In the extension is a hardcoded list of the top ERC20 contract addresses. You +can add any ERC20 contract by contract address if you wish, but the hardcoded +list exists to detect symbol spoofing attacks and improve UX. + ## Getting Started ```bash @@ -24,9 +41,9 @@ Load the extension: ## 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 +Common popular EVM wallets have become bloated with swap UIs, portfolio +dashboards, analytics, tracking, and advertisements. It is no longer a simple +wallet. Most alternatives only support Chromium browsers, leaving Firefox users without a usable option. AutistMask exists to provide the absolute minimum viable Ethereum wallet @@ -46,47 +63,70 @@ separate output directories. ``` 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) + background/ — service worker / background script + index.js — RPC routing, approval flows, message signing + content/ — content script injected into web pages + index.js — relay between inpage provider and background + inpage.js — the window.ethereum provider object (EIP-1193) + 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 + index.js — entry point, view routing, state restore + styles/main.css — Tailwind source + views/ — one JS module per screen (home, send, approval, etc.) + shared/ — modules used by both popup and background + balances.js — ETH + ERC-20 balance fetching via RPC + Blockscout + constants.js — chain IDs, default RPC endpoint, ERC-20 ABI + ens.js — ENS forward/reverse resolution + prices.js — ETH/USD and token/USD via CoinDesk API + scamlist.js — known fraud contract addresses + state.js — persisted state (extension storage) + tokenList.js — top ERC-20 tokens by market cap (hardcoded) + transactions.js — tx history fetching + anti-poisoning filters + uniswap.js — Uniswap Universal Router calldata decoder + vault.js — password-based encryption via libsodium + wallet.js — mnemonic generation, HD derivation, signing manifest/ - chrome.json — Manifest V3 for Chrome - firefox.json — Manifest V2/V3 for Firefox + chrome.json — Manifest V3 for Chrome + firefox.json — Manifest V2 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. +The UI is inspired by _Universal Paperclips_. It's deliberately minimal, +monochrome, fast, and includes once-popular usability affordances that seem to +have fallen out of fashion in modern UI design. Clickable things look clickable. +Things don't flash or spin or move around unnecessarily. This is a tool for +getting work done, not a toy. + +This is designed for a normal audience. Basic familiarity with cryptocurrency +terms is required, but you need not be a programmer or software engineer to use +this wallet. + +If you _are_ basically familiar with cryptocurrency terms, you should be able to +use all of the main features of this wallet without having to read the +documentation; i.e. we wish for the primary functionality to remain easily +discoverable. #### 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. +- **Monochrome**: Black text on white background. Color is only used when and + where it is semantically meaningful and explicitly useful, such as error + messages, critical warnings, or address disambiguation. (Notable exception: we + use color dots, and identicons, to help a user easily distinguish addresses.) - **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. + numbers. Addresses are hex strings. Flash messages are sentences. All fiddly + bits can be clicked to copy to the clipboard, and external links to Etherscan + are provided everywhere they might be useful. - **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.). + USDC, etc.). We display + [Blockie identicons](https://github.com/MyCryptoHQ/ethereum-blockies-base64) + on critical screens and when space is available to allow users to disambiguate + addresses visually, as a security feature. - **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. @@ -95,16 +135,19 @@ crypto knowledge. 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. + designed for this fixed viewport. #### 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. +messages, flash notifications, API results returning) must _never_ move around +the existing UI elements. All dynamic content areas must 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 +can cause dangerous mis-clicks. Anyone who has multi-tabled on ClubGG and +smashed the big red "all-in blind preflop" button when trying to simply "call" +on a different table knows exactly tf I am talking about. #### Clickable Affordance @@ -125,25 +168,33 @@ 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. -**Exception — Transaction Detail view:** The transaction detail screen is the -authoritative record of a specific transaction and shows the exact, untruncated -amount with all meaningful decimal places (e.g. "0.00498824598498216 ETH"). It -also shows the native quantity (e.g. "4988245984982160 wei") below it. Both are -click-copyable. Truncating to 4 decimals in summary views is acceptable for -scannability, but the detail view must never discard precision — it is the one -place the user goes to verify exact figures. +**Specific Exception — Truncation:** On some non-critical display locations, we +may truncate _a small number_ of characters from the middle of an address solely +due to display size constraints. Wherever possible, and, notably, **in all +critical contexts (transaction confirmation view before signing, transaction +history detail view) addresses will _NEVER_ be truncated**. Even in places we +truncate addresses, we truncate only a maximum of 10 characters, which means +that the portions still displayed will be more than adequate for the user to +verify addresses even in the case of address spoofing attacks. Clicking an +address will always copy the full, untruncated value. + +**Specific Exception — Transaction Detail view:** The transaction detail screen +is the authoritative record of a specific transaction and shows the exact, +untruncated amount with all meaningful decimal places (e.g. "0.00498824598498216 +ETH"). It also shows the native quantity (e.g. "4988245984982160 wei") below it. +Both are click-copyable. Truncating to 4 decimals in summary views is acceptable +for scannability, but the detail view must never discard precision — it is the +one place the user can always use to verify exact details. #### Language & Labeling -All user-facing text avoids crypto jargon wherever possible: +All user-facing text avoids unnecessary 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 @@ -152,14 +203,14 @@ All user-facing text avoids crypto jargon wherever possible: #### 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). +identifiers are displayed in full whenever possible. We truncate only in +specific, limited, non-critical places and even then only a small amount that +still prevents spoofing attacks. 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. #### Data Model @@ -483,20 +534,20 @@ transitions. ### External Services AutistMask is not a fully self-contained offline tool. It necessarily -communicates with external services to function as a wallet: +communicates with two 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. + any endpoint they prefer, including a local node). By default the extension + talks to `https://ethereum-rpc.publicnode.com`. - **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. + symbols. Note that CoinDesk will receive your client IP. What the extension does NOT do: @@ -512,8 +563,8 @@ services. Users who want maximum privacy can point the RPC at their own node ### Dependencies -AutistMask uses two runtime libraries. All cryptographic operations are -delegated to these libraries — see the Crypto Policy section below. +AutistMask uses four runtime libraries. All cryptographic operations are +delegated to ethers and libsodium — see the Crypto Policy section below. | Package | Version | License | Purpose | | -------------------------- | ------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -538,17 +589,11 @@ Dev dependencies (not shipped in extension): `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. +`ethers` or `libsodium-wrappers-sumo`. Both are widely audited and +battle-tested. Exceptions require explicit authorization in a code comment referencing this -policy. +policy, but as of now there are none. ### DEBUG Mode Policy @@ -580,12 +625,12 @@ project owner. - **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. + password. This matches standard EVM wallet 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. + passphrase is always empty (matching 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. @@ -730,104 +775,58 @@ Currently supported: "Swap USDT → ETH"), token addresses, amounts, execution steps, and deadline. Decodes Permit2, V2/V3/V4 swaps, wrap/unwrap, and balance checks. -### Non-Goals +### Non-Goals Forever -- Token swaps (use a DEX in the browser) -- NFT display or management -- Multi-chain support (Ethereum mainnet only, for now) +- Built in token swaps (use a DEX in the browser) - 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) +- Obscure token list auto-discovery (user adds tokens manually) + - We detect common/popular ERC20s in the basic case - Fiat on/off ramps -- Browser notifications -- Transaction history (use Etherscan) +- Extensive transaction decoding/parsing + - For common ones we will do best-effort, but you should just use a block + explorer. -## TODO — 0.1.0 MVP +### Non-Goals for 1.0 -Everything needed for a minimal working wallet that can send and receive ETH. +- Multi-chain support (Ethereum mainnet only) +- Hardware wallet support -### 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 +## TODO ### 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) +- [ ] Show wallet's recovery phrase (requires password) -### Sending +### Transactions -- [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) +- [ ] Research and document each address in scamlist.js +- [ ] Add more known fraud addresses from Etherscan labels -### Post-MVP +### Future -- [ ] 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 +## Policies + +- We don't mention "the other wallet" by name in code or documentation. We're + our own thing. +- The README is the complete authoritative technical documentation. It's ok if + it gets big. + ## License GPL-3.0. See [LICENSE](LICENSE). diff --git a/src/popup/index.html b/src/popup/index.html index 647da26..6cb7923 100644 --- a/src/popup/index.html +++ b/src/popup/index.html @@ -23,7 +23,7 @@