Resolve all README FIXMEs and enforce truncation safety
All checks were successful
check / check (push) Successful in 18s
All checks were successful
check / check (push) Successful in 18s
- Update Architecture tree to match actual src/ structure - Fix settings button to have border and hover state (Clickable Affordance) - Cap truncateMiddle to remove at most 10 chars (anti-spoofing guard) - Raise caller floor from 10 to 32 chars for address display - Fill in default RPC URL (ethereum-rpc.publicnode.com) - Fix dependencies table intro (four runtime libs, not two) - Clean up TODO section: remove all completed items
This commit is contained in:
273
README.md
273
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
|
supports sending and receiving ETH and ERC-20 tokens, as well as web3 site
|
||||||
connection and authentication via the EIP-1193 provider API.
|
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
|
## Getting Started
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -24,9 +41,9 @@ Load the extension:
|
|||||||
|
|
||||||
## Rationale
|
## Rationale
|
||||||
|
|
||||||
MetaMask has become bloated with swap UIs, portfolio dashboards, analytics,
|
Common popular EVM wallets have become bloated with swap UIs, portfolio
|
||||||
tracking, and advertisements. It is no longer a simple wallet. Most alternatives
|
dashboards, analytics, tracking, and advertisements. It is no longer a simple
|
||||||
(Rabby, Rainbow, etc.) only support Chromium browsers, leaving Firefox users
|
wallet. Most alternatives only support Chromium browsers, leaving Firefox users
|
||||||
without a usable option.
|
without a usable option.
|
||||||
|
|
||||||
AutistMask exists to provide the absolute minimum viable Ethereum wallet
|
AutistMask exists to provide the absolute minimum viable Ethereum wallet
|
||||||
@@ -47,46 +64,69 @@ separate output directories.
|
|||||||
```
|
```
|
||||||
src/
|
src/
|
||||||
background/ — service worker / background script
|
background/ — service worker / background script
|
||||||
index.js — extension lifecycle, message routing
|
index.js — RPC routing, approval flows, message signing
|
||||||
wallet.js — wallet management (create, import, derive via ethers.js)
|
content/ — content script injected into web pages
|
||||||
provider.js — EIP-1193 JSON-RPC provider implementation
|
index.js — relay between inpage provider and background
|
||||||
|
inpage.js — the window.ethereum provider object (EIP-1193)
|
||||||
popup/ — popup UI (the main wallet interface)
|
popup/ — popup UI (the main wallet interface)
|
||||||
index.html
|
index.html
|
||||||
index.js
|
index.js — entry point, view routing, state restore
|
||||||
styles/ — CSS (Tailwind)
|
styles/main.css — Tailwind source
|
||||||
content/ — content script injected into web pages
|
views/ — one JS module per screen (home, send, approval, etc.)
|
||||||
index.js — injects the provider into page context
|
shared/ — modules used by both popup and background
|
||||||
inpage.js — the window.ethereum provider object
|
balances.js — ETH + ERC-20 balance fetching via RPC + Blockscout
|
||||||
shared/ — shared utilities
|
constants.js — chain IDs, default RPC endpoint, ERC-20 ABI
|
||||||
vault.js — encrypted storage via libsodium
|
ens.js — ENS forward/reverse resolution
|
||||||
constants.js — chain IDs, default RPC endpoints, ERC-20 ABI
|
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/
|
manifest/
|
||||||
chrome.json — Manifest V3 for Chrome
|
chrome.json — Manifest V3 for Chrome
|
||||||
firefox.json — Manifest V2/V3 for Firefox
|
firefox.json — Manifest V2 for Firefox
|
||||||
```
|
```
|
||||||
|
|
||||||
### UI Design Philosophy
|
### UI Design Philosophy
|
||||||
|
|
||||||
The UI follows a "Universal Paperclips" aesthetic — a deliberately spartan,
|
The UI is inspired by _Universal Paperclips_. It's deliberately minimal,
|
||||||
almost brutalist approach. The guiding principle is that an unskilled,
|
monochrome, fast, and includes once-popular usability affordances that seem to
|
||||||
non-technical person should be able to figure out how to use it without any
|
have fallen out of fashion in modern UI design. Clickable things look clickable.
|
||||||
crypto knowledge.
|
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
|
#### Visual Style
|
||||||
|
|
||||||
- **Monochrome**: Black text on white background. No brand colors, no gradients,
|
- **Monochrome**: Black text on white background. Color is only used when and
|
||||||
no color-coding. Color may be introduced later for specific semantic purposes
|
where it is semantically meaningful and explicitly useful, such as error
|
||||||
(e.g. error states) but the baseline is monochrome.
|
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
|
- **Text-first**: Every piece of information is presented as text. Balances are
|
||||||
numbers. Addresses are hex strings. Status is a sentence. No progress spinners
|
numbers. Addresses are hex strings. Flash messages are sentences. All fiddly
|
||||||
with animations — a text status line is sufficient.
|
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.
|
- **Monospace font**: All text is rendered in the system monospace font.
|
||||||
Ethereum addresses, transaction hashes, and balances are inherently
|
Ethereum addresses, transaction hashes, and balances are inherently
|
||||||
fixed-width data. Rather than mixing proportional and monospace fonts, we use
|
fixed-width data. Rather than mixing proportional and monospace fonts, we use
|
||||||
monospace everywhere for visual consistency and alignment.
|
monospace everywhere for visual consistency and alignment.
|
||||||
- **No images**: Zero image assets in the entire extension. No logos, no
|
- **No images**: Zero image assets in the entire extension. No logos, no
|
||||||
illustrations, no token icons. Token identity is conveyed by symbol text (ETH,
|
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
|
- **Tailwind CSS**: Utility-first CSS via Tailwind. No custom CSS classes for
|
||||||
styling. Tailwind is configured with a minimal monochrome palette. This keeps
|
styling. Tailwind is configured with a minimal monochrome palette. This keeps
|
||||||
the styling co-located with the markup and eliminates CSS file management.
|
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
|
would add bundle size, build complexity, and attack surface for no benefit at
|
||||||
this scale.
|
this scale.
|
||||||
- **360x600 popup**: Standard browser extension popup dimensions. The UI is
|
- **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
|
#### No Layout Shift
|
||||||
|
|
||||||
Asynchronous state changes (clipboard confirmation, transaction status, error
|
Asynchronous state changes (clipboard confirmation, transaction status, error
|
||||||
messages, flash notifications) must never move existing UI elements. All dynamic
|
messages, flash notifications, API results returning) must _never_ move around
|
||||||
content areas reserve their space up front using `min-height` or always-present
|
the existing UI elements. All dynamic content areas must reserve their space up
|
||||||
wrapper elements. `visibility: hidden` is preferred over `display: none` when
|
front using `min-height` or always-present wrapper elements.
|
||||||
the element's space must be preserved. This prevents jarring content jumps that
|
`visibility: hidden` is preferred over `display: none` when the element's space
|
||||||
disorient users and avoids mis-clicks caused by shifting buttons.
|
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
|
#### 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
|
place. Users should never see the same value rendered differently on two
|
||||||
screens.
|
screens.
|
||||||
|
|
||||||
**Exception — Transaction Detail view:** The transaction detail screen is the
|
**Specific Exception — Truncation:** On some non-critical display locations, we
|
||||||
authoritative record of a specific transaction and shows the exact, untruncated
|
may truncate _a small number_ of characters from the middle of an address solely
|
||||||
amount with all meaningful decimal places (e.g. "0.00498824598498216 ETH"). It
|
due to display size constraints. Wherever possible, and, notably, **in all
|
||||||
also shows the native quantity (e.g. "4988245984982160 wei") below it. Both are
|
critical contexts (transaction confirmation view before signing, transaction
|
||||||
click-copyable. Truncating to 4 decimals in summary views is acceptable for
|
history detail view) addresses will _NEVER_ be truncated**. Even in places we
|
||||||
scannability, but the detail view must never discard precision — it is the one
|
truncate addresses, we truncate only a maximum of 10 characters, which means
|
||||||
place the user goes to verify exact figures.
|
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
|
#### 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"
|
- "Recovery phrase" instead of "seed phrase", "mnemonic", or "BIP-39 mnemonic"
|
||||||
- "Address" instead of "account", "derived key", or "HD child"
|
- "Address" instead of "account", "derived key", or "HD child"
|
||||||
- "Password" instead of "encryption key" or "vault passphrase"
|
- "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",
|
- Buttons use plain verbs: "Send", "Receive", "Copy address", "Add", "Back",
|
||||||
"Cancel", "Lock", "Unlock", "Allow", "Deny"
|
"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
|
- Helpful inline descriptions where needed (e.g. "This password locks the wallet
|
||||||
on this device. It is not the same as your recovery phrase.")
|
on this device. It is not the same as your recovery phrase.")
|
||||||
- Error messages are full sentences ("Please enter your password." not "password
|
- 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
|
#### Full Identifiers Policy
|
||||||
|
|
||||||
Addresses, transaction hashes, contract addresses, and all other cryptographic
|
Addresses, transaction hashes, contract addresses, and all other cryptographic
|
||||||
identifiers are displayed in full whenever possible — never truncated. Address
|
identifiers are displayed in full whenever possible. We truncate only in
|
||||||
poisoning attacks exploit truncated displays by generating fraud addresses that
|
specific, limited, non-critical places and even then only a small amount that
|
||||||
share the same prefix and suffix as a legitimate address. If a user only sees
|
still prevents spoofing attacks. Address poisoning attacks exploit truncated
|
||||||
`0xAbCd...1234`, an attacker can create an address with the same visible
|
displays by generating fraud addresses that share the same prefix and suffix as
|
||||||
characters and trick the user into sending funds to it. Showing the complete
|
a legitimate address. If a user only sees `0xAbCd...1234`, an attacker can
|
||||||
identifier defeats this class of attack. Truncation is only acceptable in
|
create an address with the same visible characters and trick the user into
|
||||||
space-constrained contexts where the full identifier is accessible one tap away
|
sending funds to it. Showing the complete identifier defeats this class of
|
||||||
(e.g. a tooltip or copy action).
|
attack.
|
||||||
|
|
||||||
#### Data Model
|
#### Data Model
|
||||||
|
|
||||||
@@ -483,20 +534,20 @@ transitions.
|
|||||||
### External Services
|
### External Services
|
||||||
|
|
||||||
AutistMask is not a fully self-contained offline tool. It necessarily
|
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
|
- **Ethereum JSON-RPC endpoint**: The extension needs an Ethereum node to query
|
||||||
balances (`eth_getBalance`), read ERC-20 token contracts (`eth_call`),
|
balances (`eth_getBalance`), read ERC-20 token contracts (`eth_call`),
|
||||||
estimate gas (`eth_estimateGas`), fetch nonces (`eth_getTransactionCount`),
|
estimate gas (`eth_estimateGas`), fetch nonces (`eth_getTransactionCount`),
|
||||||
broadcast transactions (`eth_sendRawTransaction`), and check transaction
|
broadcast transactions (`eth_sendRawTransaction`), and check transaction
|
||||||
receipts. The default endpoint is a public RPC (configurable by the user to
|
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
|
any endpoint they prefer, including a local node). By default the extension
|
||||||
service the extension talks to.
|
talks to `https://ethereum-rpc.publicnode.com`.
|
||||||
|
|
||||||
- **CoinDesk CADLI price API**: Used to fetch ETH/USD and token/USD prices for
|
- **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
|
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
|
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:
|
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
|
### Dependencies
|
||||||
|
|
||||||
AutistMask uses two runtime libraries. All cryptographic operations are
|
AutistMask uses four runtime libraries. All cryptographic operations are
|
||||||
delegated to these libraries — see the Crypto Policy section below.
|
delegated to ethers and libsodium — see the Crypto Policy section below.
|
||||||
|
|
||||||
| Package | Version | License | Purpose |
|
| Package | Version | License | Purpose |
|
||||||
| -------------------------- | ------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| -------------------------- | ------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
@@ -538,17 +589,11 @@ Dev dependencies (not shipped in extension):
|
|||||||
`pbkdf`, `hmac`, `encrypt`, `decrypt`, `hash`, `cipher`, `digest`, `sign`
|
`pbkdf`, `hmac`, `encrypt`, `decrypt`, `hash`, `cipher`, `digest`, `sign`
|
||||||
(case-insensitive) appear in our own source code (outside of `node_modules/`),
|
(case-insensitive) appear in our own source code (outside of `node_modules/`),
|
||||||
it is almost certainly a bug. All cryptographic operations must go through
|
it is almost certainly a bug. All cryptographic operations must go through
|
||||||
`ethers` or `libsodium-wrappers-sumo`. This policy exists because:
|
`ethers` or `libsodium-wrappers-sumo`. Both are widely audited and
|
||||||
|
battle-tested.
|
||||||
- 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
|
Exceptions require explicit authorization in a code comment referencing this
|
||||||
policy.
|
policy, but as of now there are none.
|
||||||
|
|
||||||
### DEBUG Mode Policy
|
### DEBUG Mode Policy
|
||||||
|
|
||||||
@@ -580,12 +625,12 @@ project owner.
|
|||||||
- **The password is NOT used in address derivation.** It exists solely to
|
- **The password is NOT used in address derivation.** It exists solely to
|
||||||
protect the recovery phrase / private key on disk. Anyone with the
|
protect the recovery phrase / private key on disk. Anyone with the
|
||||||
recovery phrase can restore the wallet on any device without this
|
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
|
- **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
|
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
|
passphrase is always empty (matching most wallet software). The user's
|
||||||
user's password is completely separate and has no effect on which addresses
|
password is completely separate and has no effect on which addresses are
|
||||||
are generated.
|
generated.
|
||||||
- **ethers.js for everything Ethereum**: Transaction construction, signing, gas
|
- **ethers.js for everything Ethereum**: Transaction construction, signing, gas
|
||||||
estimation, RPC communication, ERC-20 contract calls, and address derivation
|
estimation, RPC communication, ERC-20 contract calls, and address derivation
|
||||||
are all handled by ethers.js. This means zero hand-rolled Ethereum logic.
|
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.
|
"Swap USDT → ETH"), token addresses, amounts, execution steps, and deadline.
|
||||||
Decodes Permit2, V2/V3/V4 swaps, wrap/unwrap, and balance checks.
|
Decodes Permit2, V2/V3/V4 swaps, wrap/unwrap, and balance checks.
|
||||||
|
|
||||||
### Non-Goals
|
### Non-Goals Forever
|
||||||
|
|
||||||
- Token swaps (use a DEX in the browser)
|
- Built in 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
|
- Analytics, telemetry, or tracking of any kind
|
||||||
- Advertisements or promotions
|
- Advertisements or promotions
|
||||||
- Phishing detection
|
- Obscure token list auto-discovery (user adds tokens manually)
|
||||||
- Hardware wallet support (maybe later)
|
- We detect common/popular ERC20s in the basic case
|
||||||
- Token list auto-discovery (user adds tokens manually)
|
|
||||||
- Fiat on/off ramps
|
- Fiat on/off ramps
|
||||||
- Browser notifications
|
- Extensive transaction decoding/parsing
|
||||||
- Transaction history (use Etherscan)
|
- 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
|
## TODO
|
||||||
|
|
||||||
- [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
|
### 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 wallet (with confirmation)
|
||||||
- [ ] Delete address from HD wallet (with confirmation)
|
- [ ] Delete address from HD wallet (with confirmation)
|
||||||
- [ ] Show wallet's recovery phrase (requires password, from Settings or wallet
|
- [ ] Show wallet's recovery phrase (requires password)
|
||||||
context menu)
|
|
||||||
|
|
||||||
### 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
|
- [ ] 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
|
### Testing
|
||||||
|
|
||||||
- [ ] Tests for mnemonic generation and address derivation
|
- [ ] Tests for mnemonic generation and address derivation
|
||||||
- [ ] Tests for xpub derivation and child address generation
|
- [ ] 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)
|
- [ ] Test on Firefox (Manifest V2)
|
||||||
|
|
||||||
### Scam List
|
### Scam List
|
||||||
|
|
||||||
- [ ] Research and document each address in scamlist.js (what it is, why it's on
|
- [ ] Research and document each address in scamlist.js
|
||||||
the list, source)
|
- [ ] Add more known fraud addresses from Etherscan labels
|
||||||
- [ ] Add more known fraud addresses from Etherscan labels (drainers, phishing,
|
|
||||||
address poisoning deployers)
|
|
||||||
|
|
||||||
### 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.)
|
- [ ] Multi-currency fiat display (EUR, GBP, etc.)
|
||||||
- [ ] Security audit of key management
|
- [ ] 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
|
## License
|
||||||
|
|
||||||
GPL-3.0. See [LICENSE](LICENSE).
|
GPL-3.0. See [LICENSE](LICENSE).
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
</h1>
|
</h1>
|
||||||
<button
|
<button
|
||||||
id="btn-settings"
|
id="btn-settings"
|
||||||
class="bg-transparent border-none text-fg cursor-pointer text-2xl p-0 leading-none"
|
class="border border-border text-fg cursor-pointer text-xl px-1 leading-none hover:bg-fg hover:text-bg"
|
||||||
title="Settings"
|
title="Settings"
|
||||||
>
|
>
|
||||||
⚙
|
⚙
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ function renderTransactions(txs) {
|
|||||||
const amountStr = tx.value
|
const amountStr = tx.value
|
||||||
? escapeHtml(tx.value + " " + tx.symbol)
|
? escapeHtml(tx.value + " " + tx.symbol)
|
||||||
: escapeHtml(tx.symbol);
|
: escapeHtml(tx.symbol);
|
||||||
const maxAddr = Math.max(10, 36 - Math.max(0, amountStr.length - 10));
|
const maxAddr = Math.max(32, 36 - Math.max(0, amountStr.length - 10));
|
||||||
const displayAddr = ensName || truncateMiddle(counterparty, maxAddr);
|
const displayAddr = ensName || truncateMiddle(counterparty, maxAddr);
|
||||||
const addrStr = escapeHtml(displayAddr);
|
const addrStr = escapeHtml(displayAddr);
|
||||||
const dot = addressDotHtml(counterparty);
|
const dot = addressDotHtml(counterparty);
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ function renderTransactions(txs) {
|
|||||||
const amountStr = tx.value
|
const amountStr = tx.value
|
||||||
? escapeHtml(tx.value + " " + tx.symbol)
|
? escapeHtml(tx.value + " " + tx.symbol)
|
||||||
: escapeHtml(tx.symbol);
|
: escapeHtml(tx.symbol);
|
||||||
const maxAddr = Math.max(10, 36 - Math.max(0, amountStr.length - 10));
|
const maxAddr = Math.max(32, 36 - Math.max(0, amountStr.length - 10));
|
||||||
const displayAddr = ensName || truncateMiddle(counterparty, maxAddr);
|
const displayAddr = ensName || truncateMiddle(counterparty, maxAddr);
|
||||||
const addrStr = escapeHtml(displayAddr);
|
const addrStr = escapeHtml(displayAddr);
|
||||||
const dot = addressDotHtml(counterparty);
|
const dot = addressDotHtml(counterparty);
|
||||||
|
|||||||
@@ -131,9 +131,18 @@ function balanceLinesForAddress(addr, trackedTokens, showZero) {
|
|||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Truncate the middle of a string, replacing removed characters with "…".
|
||||||
|
// Safety: refuses to truncate more than 10 characters, which is the maximum
|
||||||
|
// that still prevents address spoofing attacks (see Display Consistency in
|
||||||
|
// README). Callers that need to display less should use a different UI
|
||||||
|
// approach rather than silently making addresses insecure.
|
||||||
function truncateMiddle(str, maxLen) {
|
function truncateMiddle(str, maxLen) {
|
||||||
if (str.length <= maxLen) return str;
|
if (str.length <= maxLen) return str;
|
||||||
if (maxLen < 5) return str.slice(0, maxLen);
|
const removed = str.length - maxLen + 1; // +1 for the ellipsis char
|
||||||
|
if (removed > 10) {
|
||||||
|
maxLen = str.length - 10 + 1;
|
||||||
|
}
|
||||||
|
if (maxLen >= str.length) return str;
|
||||||
const half = Math.floor((maxLen - 1) / 2);
|
const half = Math.floor((maxLen - 1) / 2);
|
||||||
return str.slice(0, half) + "\u2026" + str.slice(-(maxLen - 1 - half));
|
return str.slice(0, half) + "\u2026" + str.slice(-(maxLen - 1 - half));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ function renderHomeTxList(ctx) {
|
|||||||
const amountStr = tx.value
|
const amountStr = tx.value
|
||||||
? escapeHtml(tx.value + " " + tx.symbol)
|
? escapeHtml(tx.value + " " + tx.symbol)
|
||||||
: escapeHtml(tx.symbol);
|
: escapeHtml(tx.symbol);
|
||||||
const maxAddr = Math.max(10, 36 - Math.max(0, amountStr.length - 10));
|
const maxAddr = Math.max(32, 36 - Math.max(0, amountStr.length - 10));
|
||||||
const displayAddr = truncateMiddle(counterparty, maxAddr);
|
const displayAddr = truncateMiddle(counterparty, maxAddr);
|
||||||
const addrStr = escapeHtml(displayAddr);
|
const addrStr = escapeHtml(displayAddr);
|
||||||
const dot = addressDotHtml(counterparty);
|
const dot = addressDotHtml(counterparty);
|
||||||
|
|||||||
Reference in New Issue
Block a user