All checks were successful
check / check (push) Successful in 5s
Expands the confirm-tx warning system with three new warning types, all using the existing `visibility:hidden/visible` pattern from PR #98 (no animations, no layout shift). ## Changes 1. **Scam address list expanded** (7 → 652 addresses): Sourced from [MyEtherWallet/ethereum-lists](https://github.com/MyEtherWallet/ethereum-lists) darklist (MIT license). Checked synchronously before sending. 2. **Contract address warning**: When the recipient is a smart contract (detected via `getCode`), shows a warning that sending directly to a contract may result in permanent loss of funds. 3. **Null/burn address warning**: Detects known burn addresses (`0x0000...0000`, `0x...dead`, `0x...deadbeef`) and warns that funds are permanently destroyed. 4. **No-history warning** (existing from #98): Unchanged, still shows for EOAs with zero transaction history. All warnings use reserved-space `visibility:hidden/visible` elements — no layout shift, no animations. closes #114 Co-authored-by: clawbot <clawbot@noreply.git.eeqj.de> Co-authored-by: user <user@Mac.lan guest wan> Co-authored-by: clawbot <clawbot@eeqj.de> Reviewed-on: #118 Co-authored-by: clawbot <sneak+clawbot@sneak.cloud> Co-committed-by: clawbot <sneak+clawbot@sneak.cloud>
908 lines
44 KiB
Markdown
908 lines
44 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.
|
|
|
|
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 three user-configurable services: the configured RPC node for
|
|
blockchain interactions, a public CoinDesk API (no API key) for realtime price
|
|
information, and a Blockscout block-explorer API for transaction history and
|
|
token balances. It also fetches a community-maintained phishing domain blocklist
|
|
periodically and performs best-effort Etherscan address label lookups during
|
|
transaction confirmation.
|
|
|
|
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
|
|
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
|
|
|
|
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
|
|
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 — 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 — 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 for Firefox
|
|
```
|
|
|
|
### UI Design Philosophy
|
|
|
|
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. 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. 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.). 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.
|
|
- **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 Layout Shift
|
|
|
|
Asynchronous state changes (clipboard confirmation, transaction status, error
|
|
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
|
|
|
|
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 displayed with exactly 4 decimal places (e.g. "1.0500 ETH", "17.1900
|
|
USDT") in balance lists, transaction lists, send confirmations, and approval
|
|
screens. 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.
|
|
|
|
**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 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"
|
|
- Buttons use plain verbs: "Send", "Receive", "Copy address", "Add", "Back",
|
|
"Cancel", "Lock", "Unlock", "Allow", "Deny"
|
|
- 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. 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
|
|
|
|
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" → **AddWallet**
|
|
|
|
#### Home
|
|
|
|
- **When**: At least one wallet exists. This is the root screen.
|
|
- **Elements**:
|
|
- Header: "AutistMask", Settings gear button
|
|
- Active address ETH balance (large) + USD value (inline parentheses)
|
|
- Total USD value across all tokens (small text)
|
|
- Active address (color dot, full address, etherscan link, tap to copy)
|
|
- Send / Receive quick-action buttons
|
|
- ETH/USD price display
|
|
- Wallet list: each wallet shows name (tap to rename), "+" button (HD only),
|
|
and its addresses with color dots, balances, and `[info]` buttons
|
|
- Recent transactions across all addresses (merged, deduplicated, filtered)
|
|
- "Add additional wallet..." link at bottom
|
|
- **Transitions**:
|
|
- Tap address row → sets active address (no screen change)
|
|
- `[info]` on address → **AddressDetail**
|
|
- "Send" → **Send** (selects active address)
|
|
- "Receive" → **Receive** (shows active address QR)
|
|
- "+" on wallet → derives next address inline
|
|
- "Add additional wallet..." → **AddWallet**
|
|
- Settings gear → **Settings** (toggles; tap again to return)
|
|
- Tap home tx row → **AddressDetail** (for the address involved)
|
|
|
|
#### AddWallet
|
|
|
|
- **When**: User wants to add a new wallet (from Home, Welcome, or Settings).
|
|
- **Elements**:
|
|
- "Add Wallet" heading, "Back" button
|
|
- Instruction text
|
|
- Die button `[die]` (generates random recovery phrase)
|
|
- Recovery phrase textarea
|
|
- Backup warning box (shown after die is clicked)
|
|
- Password + confirm password inputs
|
|
- "Add" button
|
|
- "Have a private key instead?" link
|
|
- **Transitions**:
|
|
- "Add" (valid phrase + password) → **Home**
|
|
- "Back" → previous screen (Home or Welcome)
|
|
- "Have a private key instead?" → **ImportKey**
|
|
|
|
#### ImportKey
|
|
|
|
- **When**: User wants to import a single private key.
|
|
- **Elements**:
|
|
- "Import Private Key" heading, "Back" button
|
|
- Instruction text
|
|
- Private key input (password-masked)
|
|
- Password + confirm password inputs
|
|
- "Import" button
|
|
- **Transitions**:
|
|
- "Import" (valid key + password) → **Home**
|
|
- "Back" → **AddWallet**
|
|
|
|
#### AddressDetail
|
|
|
|
- **When**: User tapped `[info]` on an address from Home.
|
|
- **Elements**:
|
|
- "Back" button
|
|
- Blockie identicon (48px, centered)
|
|
- Title: "Wallet Name — Address N"
|
|
- ENS name (if resolved, bold with color dot)
|
|
- Full address (color dot, etherscan link, tap to copy)
|
|
- USD total for address
|
|
- Balance list: ETH + tracked ERC-20 tokens (4 decimal places, USD inline).
|
|
Each balance row is clickable → **AddressToken**
|
|
- Send / Receive / + Token buttons
|
|
- Transaction list (with ENS resolution for counterparties)
|
|
- **Transitions**:
|
|
- Tap balance row → **AddressToken** (for that token)
|
|
- "Send" → **Send**
|
|
- "Receive" → **Receive**
|
|
- "+ Token" → **AddToken**
|
|
- Tap transaction row → **TransactionDetail**
|
|
- "Back" → **Home**
|
|
|
|
#### AddressToken
|
|
|
|
- **When**: User clicked a specific token balance on AddressDetail.
|
|
- **Elements**:
|
|
- "Back" button
|
|
- Blockie identicon (48px, centered)
|
|
- Title: "Wallet Name — Address N — TOKEN"
|
|
- Full address (color dot, etherscan link, tap to copy)
|
|
- USD total for this token
|
|
- Single token balance line (4 decimal places)
|
|
- Send / Receive buttons
|
|
- Token-filtered transaction list (only this token's transfers)
|
|
- **Transitions**:
|
|
- "Send" → **Send** (token pre-selected and locked in dropdown)
|
|
- "Receive" → **Receive** (ERC-20 warning shown for non-ETH tokens)
|
|
- Tap transaction row → **TransactionDetail**
|
|
- "Back" → **AddressDetail**
|
|
|
|
#### Send
|
|
|
|
- **When**: User wants to send ETH or a token from this address.
|
|
- **Elements**:
|
|
- "Send" heading, "Back" button
|
|
- From: address with color dot + etherscan link
|
|
- What to send: token dropdown (or static display with contract address when
|
|
locked from AddressToken)
|
|
- To: address or ENS name input
|
|
- Amount input with current balance display
|
|
- "Review" button
|
|
- **Transitions**:
|
|
- "Review" (valid inputs, ENS resolved) → **ConfirmTx**
|
|
- "Back" → **AddressToken** (if came from token view) or **AddressDetail**
|
|
|
|
#### ConfirmTx
|
|
|
|
- **When**: User reviewed send details and is ready to authorize.
|
|
- **Elements**:
|
|
- "Confirm Transaction" heading, "Back" button
|
|
- Type: "Native ETH transfer" or "ERC-20 token transfer (SYMBOL)"
|
|
- Token contract: full address + etherscan link (ERC-20 only)
|
|
- From: blockie + color dot + full address + etherscan link + wallet title
|
|
- To: blockie + color dot + full address + etherscan link + ENS name
|
|
- Amount: value + symbol (USD in parentheses)
|
|
- Your balance: value + symbol (USD in parentheses)
|
|
- Estimated network fee: ETH amount (USD in parentheses), fetched async
|
|
- Warnings (scam address, self-send)
|
|
- Errors (insufficient balance)
|
|
- "Send" button (disabled if errors)
|
|
- **Transitions**:
|
|
- "Send" → password modal → broadcast tx → **WaitTx**
|
|
- "Send" → password modal → broadcast fails → **ErrorTx**
|
|
- "Back" → **Send**
|
|
|
|
#### WaitTx
|
|
|
|
- **When**: Transaction has been broadcast, waiting for on-chain confirmation.
|
|
- **Elements**:
|
|
- "Transaction Broadcast" heading (no back button — tx is irreversible)
|
|
- Amount + symbol
|
|
- To: color dot + full address + etherscan link
|
|
- Transaction hash: full hash (tap to copy) + etherscan link
|
|
- Count-up timer: "Waiting for confirmation... Ns"
|
|
- **Behavior**: Polls `getTransactionReceipt` every 10 seconds.
|
|
- **Transitions**:
|
|
- Receipt found → **SuccessTx**
|
|
- 60 seconds without confirmation → **ErrorTx** (timeout message)
|
|
|
|
#### SuccessTx
|
|
|
|
- **When**: Transaction confirmed on-chain.
|
|
- **Elements**:
|
|
- "Transaction Confirmed" heading
|
|
- Amount + symbol
|
|
- To: color dot + full address + etherscan link
|
|
- Block number
|
|
- Transaction hash: full hash (tap to copy) + etherscan link
|
|
- "Done" button
|
|
- **Transitions**:
|
|
- "Done" → **AddressToken** (if `selectedToken` set) or **AddressDetail**
|
|
|
|
#### ErrorTx
|
|
|
|
- **When**: Transaction broadcast failed, or timed out waiting for confirmation.
|
|
- **Elements**:
|
|
- "Transaction Failed" heading
|
|
- Amount + symbol
|
|
- To: color dot + full address + etherscan link
|
|
- Error message (dashed border box)
|
|
- Transaction hash section (hidden if broadcast failed before getting hash):
|
|
full hash (tap to copy) + etherscan link
|
|
- "Done" button
|
|
- **Transitions**:
|
|
- "Done" → **AddressToken** (if `selectedToken` set) or **AddressDetail**
|
|
|
|
#### Receive
|
|
|
|
- **When**: User wants to receive funds at this address.
|
|
- **Elements**:
|
|
- "Receive" heading, "Back" button
|
|
- Instruction text
|
|
- QR code encoding the address
|
|
- Full address (color dot, selectable, etherscan link)
|
|
- "Copy address" button
|
|
- ERC-20 warning (shown when navigating from AddressToken for non-ETH token)
|
|
- **Transitions**:
|
|
- "Back" → **AddressToken** (if `selectedToken` set) or **AddressDetail**
|
|
|
|
#### TransactionDetail
|
|
|
|
- **When**: User tapped a transaction row from AddressDetail or AddressToken.
|
|
- **Elements** (grouped into logical blocks using light well containers; field
|
|
labels are self-explanatory so groups have no headings):
|
|
- "Transaction" heading, "Back" button
|
|
- Transaction hash: full hash (tap to copy) + etherscan link
|
|
- Type: transaction classification — one of: Native ETH Transfer, ERC-20
|
|
Token Transfer, Swap, Token Approval, Contract Call, Contract Creation
|
|
- Status: "Success" or "Failed"
|
|
- From: blockie + color dot + full address (tap to copy) + etherscan link;
|
|
ENS name if available
|
|
- To: blockie + color dot + full address (tap to copy) + etherscan link; ENS
|
|
name if available
|
|
- Time: ISO datetime + relative age in parentheses
|
|
- Block: block number (tap to copy) + etherscan block link
|
|
- Amount: value + symbol (bold)
|
|
- Native quantity: raw integer + unit (shown when available)
|
|
- Token contract: shown for ERC-20 transfers — color dot + full contract
|
|
address (tap to copy) + etherscan token link
|
|
- Decoded details (shown for contract calls): action name, decoded
|
|
parameters, token details, swap steps
|
|
- Network details (shown when on-chain data is available): nonce, gas price,
|
|
gas used, transaction fee (all tap to copy)
|
|
- Raw data (shown when calldata is present): full calldata in monospace
|
|
dashed border
|
|
- **Transitions**:
|
|
- "Back" → **AddressToken** (if `selectedToken` set) or **AddressDetail**
|
|
|
|
#### AddToken
|
|
|
|
- **When**: User wants to track an ERC-20 token on this address.
|
|
- **Elements**:
|
|
- "Add Token" heading, "Back" button
|
|
- Instruction text (find contract address on Etherscan)
|
|
- Contract address input
|
|
- Token info preview (name, symbol — fetched from contract)
|
|
- Common token quick-pick buttons
|
|
- "Add" button
|
|
- **Transitions**:
|
|
- "Add" (valid contract) → **AddressDetail**
|
|
- "Back" → **AddressDetail**
|
|
|
|
#### Settings
|
|
|
|
- **When**: User tapped Settings gear from Home.
|
|
- **Elements**:
|
|
- "Settings" heading, "Back" button
|
|
- Wallets: "+ Add wallet" button
|
|
- Display: "Show tracked tokens with zero balance" checkbox
|
|
- Ethereum RPC: endpoint URL input + "Save" button
|
|
- Blockscout API: endpoint URL input + "Save" button
|
|
- Token Spam Protection:
|
|
- "Hide tokens with fewer than 1,000 holders" checkbox
|
|
- "Hide transactions from detected fraud contracts" checkbox
|
|
- "Hide dust transactions below N gwei" checkbox + threshold input
|
|
- Allowed Sites: list with remove buttons
|
|
- Denied Sites: list with remove buttons
|
|
- **Transitions**:
|
|
- "+ Add wallet" → **AddWallet**
|
|
- "Back" (or Settings gear again) → **Home**
|
|
|
|
#### SiteApproval
|
|
|
|
- **When**: A website requests wallet access via `eth_requestAccounts`. Opened
|
|
in a separate popup by the background script.
|
|
- **Elements**:
|
|
- "Connection Request" heading
|
|
- Site hostname (bold)
|
|
- Address that will be shared (color dot + full address + etherscan link)
|
|
- "Remember my choice for this site" checkbox
|
|
- "Allow" / "Deny" buttons
|
|
- **Transitions**:
|
|
- "Allow" / "Deny" → closes popup (returns result to background script)
|
|
|
|
#### TxApproval
|
|
|
|
- **When**: A connected website requests a transaction via
|
|
`eth_sendTransaction`. Opened via the toolbar popup by the background script.
|
|
- **Elements**:
|
|
- "Transaction Request" heading
|
|
- Site hostname (bold) + "wants to send a transaction"
|
|
- Decoded action (if calldata is recognized): action name, token details,
|
|
amounts, steps, deadline (see Transaction Decoding)
|
|
- From: color dot + full address + etherscan link
|
|
- To/Contract: color dot + full address + etherscan link (or "contract
|
|
creation"), token symbol label if known
|
|
- Value: amount in ETH (4 decimal places)
|
|
- Raw data: full calldata displayed inline (shown if present)
|
|
- Password input
|
|
- "Confirm" / "Reject" buttons
|
|
- **Transitions**:
|
|
- "Confirm" (with password) → closes popup (returns result to background)
|
|
- "Reject" → closes popup (returns rejection to background)
|
|
|
|
#### SignApproval
|
|
|
|
- **When**: A connected website requests a message signature via
|
|
`personal_sign`, `eth_sign`, or `eth_signTypedData_v4`. Opened via the toolbar
|
|
popup by the background script.
|
|
- **Elements**:
|
|
- "Signature Request" heading
|
|
- Site hostname (bold) + "wants you to sign a message"
|
|
- Type: "Personal message" or "Typed data (EIP-712)"
|
|
- From: color dot + full address + etherscan link
|
|
- Message: decoded UTF-8 text (personal_sign) or formatted domain/type/
|
|
message fields (EIP-712 typed data)
|
|
- Password input
|
|
- "Sign" / "Reject" buttons
|
|
- **Transitions**:
|
|
- "Sign" (with password) → signs locally → closes popup (returns signature)
|
|
- "Reject" → closes popup (returns rejection to background)
|
|
|
|
### External Services
|
|
|
|
AutistMask is not a fully self-contained offline tool. It necessarily
|
|
communicates with three 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). By default the extension
|
|
talks to `https://ethereum-rpc.publicnode.com`.
|
|
- **Data sent**: Ethereum addresses, transaction data, contract call
|
|
parameters. The RPC endpoint can see all on-chain queries and submitted
|
|
transactions.
|
|
|
|
- **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. Note that CoinDesk will receive your client IP.
|
|
- **Data sent**: Token symbol strings only (e.g. "ETH", "USDC"). No
|
|
addresses or user-specific data.
|
|
|
|
- **Blockscout block-explorer API**: Used to fetch transaction history (normal
|
|
transactions and ERC-20 token transfers), ERC-20 token balances, and token
|
|
holder counts (for spam filtering). The default endpoint is
|
|
`https://eth.blockscout.com/api/v2` (configurable by the user in Settings).
|
|
- **Data sent**: Ethereum addresses. Blockscout receives the user's
|
|
addresses to query their transaction history and token balances. No
|
|
private keys, passwords, or signing operations are sent.
|
|
|
|
What the extension does NOT do:
|
|
|
|
- No analytics or telemetry services
|
|
- No token list APIs (user adds tokens manually by contract address)
|
|
- No Infura/Alchemy dependency (any JSON-RPC endpoint works)
|
|
- No backend servers operated by the developer
|
|
|
|
In addition to the three user-configurable services above (RPC endpoint,
|
|
CoinDesk price API, and Blockscout API), AutistMask also contacts:
|
|
|
|
- **Phishing domain blocklist**: A community-maintained phishing domain
|
|
blocklist is vendored into the extension at build time. At runtime, the
|
|
extension fetches the live list once every 24 hours to detect newly added
|
|
domains. Only the delta (domains not already in the vendored list) is kept in
|
|
memory, keeping runtime memory usage small. The delta is persisted to
|
|
localStorage if it is under 256 KiB.
|
|
- **Etherscan address labels**: When confirming a transaction, the extension
|
|
performs a best-effort lookup of the recipient address on Etherscan to check
|
|
for phishing/scam labels. This is a direct page fetch with no API key; the
|
|
user's browser makes the request.
|
|
|
|
Users who want maximum privacy can point the RPC and Blockscout URLs at their
|
|
own self-hosted instances (price fetching can be disabled in a future version).
|
|
|
|
### Dependencies
|
|
|
|
AutistMask uses four runtime libraries. All cryptographic operations are
|
|
delegated to ethers and libsodium — 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). |
|
|
| `ethereum-blockies-base64` | 1.0.2 | ISC | Deterministic pixelated identicon generation from Ethereum addresses (same style used by Etherscan). |
|
|
|
|
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`. Both are widely audited and
|
|
battle-tested.
|
|
|
|
Exceptions require explicit authorization in a code comment referencing this
|
|
policy, but as of now there are none.
|
|
|
|
### 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 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 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, QR code)
|
|
- Connect to web3 sites (EIP-1193 `eth_requestAccounts`)
|
|
- Sign transactions requested by connected sites (`eth_sendTransaction`)
|
|
- Sign messages (`personal_sign`, `eth_sign`)
|
|
- Sign typed data (`eth_signTypedData_v4`, `eth_signTypedData`)
|
|
- Human-readable transaction decoding (ERC-20, Uniswap Universal Router)
|
|
- ETH/USD and token/USD price display
|
|
- Configurable RPC endpoint and Blockscout API
|
|
- Address poisoning protection (spam token filtering, dust filtering, fraud
|
|
contract blocklist)
|
|
|
|
### 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.
|
|
|
|
#### Phishing Domain Protection
|
|
|
|
AutistMask protects users from known phishing sites when they connect their
|
|
wallet or approve transactions/signatures. A community-maintained domain
|
|
blocklist is vendored into the extension at build time, providing immediate
|
|
protection without any network requests. At runtime, the extension fetches the
|
|
live list once every 24 hours and keeps only the delta (newly added domains not
|
|
in the vendored list) in memory. This architecture keeps runtime memory usage
|
|
small while ensuring fresh coverage of new phishing domains.
|
|
|
|
When a dApp on a blocklisted domain requests a wallet connection, transaction
|
|
approval, or signature, the approval popup displays a prominent red warning
|
|
banner alerting the user. The domain checker matches exact hostnames and all
|
|
parent domains (subdomain matching).
|
|
|
|
#### Transaction Decoding
|
|
|
|
When a dApp asks the user to approve a transaction, AutistMask attempts to
|
|
decode the calldata into a human-readable summary. This is purely a display
|
|
convenience to help the user understand what they are signing — it is not
|
|
endorsement, special treatment, or partnership with any protocol.
|
|
|
|
AutistMask is a generic web3 wallet. It treats all dApps, protocols, and
|
|
contracts equally. No contract gets special handling, priority, or integration
|
|
beyond what is needed to show the user a legible confirmation screen. Our
|
|
commitment is to the user, not to any service, site, or contract.
|
|
|
|
Decoded transaction summaries are best-effort. If decoding fails, the raw
|
|
calldata is displayed in full. The decoders live in self-contained modules under
|
|
`src/shared/` (e.g. `uniswap.js`) so they can be added for common contracts
|
|
without polluting wallet-specific code. Contributions of decoders for other
|
|
widely-used contracts are welcome.
|
|
|
|
Currently supported:
|
|
|
|
- **ERC-20**: `approve()` and `transfer()` calls — shows token symbol, spender
|
|
or recipient, and amount.
|
|
- **Uniswap Universal Router**: `execute()` calls — shows swap direction (e.g.
|
|
"Swap USDT → ETH"), token addresses, amounts, execution steps, and deadline.
|
|
Decodes Permit2, V2/V3/V4 swaps, wrap/unwrap, and balance checks.
|
|
|
|
### Non-Goals Forever
|
|
|
|
- Built in token swaps (use a DEX in the browser)
|
|
- Analytics, telemetry, or tracking of any kind
|
|
- Advertisements or promotions
|
|
- Obscure token list auto-discovery (user adds tokens manually)
|
|
- We detect common/popular ERC20s in the basic case
|
|
- Fiat on/off ramps
|
|
- Extensive transaction decoding/parsing
|
|
- For common ones we will do best-effort, but you should just use a block
|
|
explorer.
|
|
|
|
### Non-Goals for 1.0
|
|
|
|
- Multi-chain support (Ethereum mainnet only)
|
|
- Hardware wallet support
|
|
|
|
## TODO
|
|
|
|
### Wallet Management
|
|
|
|
- [ ] Delete wallet (with confirmation)
|
|
- [ ] Delete address from HD wallet (with confirmation)
|
|
- [ ] Show wallet's recovery phrase (requires password)
|
|
|
|
### Transactions
|
|
|
|
- [ ] Gas estimation and fee display before confirming
|
|
|
|
### Testing
|
|
|
|
- [ ] Tests for mnemonic generation and address derivation
|
|
- [ ] Tests for xpub derivation and child address generation
|
|
- [ ] Test on Firefox (Manifest V2)
|
|
|
|
### Scam List
|
|
|
|
- [ ] Research and document each address in scamlist.js
|
|
- [ ] Add more known fraud addresses from Etherscan labels
|
|
|
|
### Future
|
|
|
|
- [ ] 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).
|
|
|
|
### Third-Party Data Files
|
|
|
|
This repository includes data files from third-party projects that are not
|
|
covered by the GPL-3.0 license above. These files, their copyright holders, and
|
|
their licenses are:
|
|
|
|
| File | Source | Copyright | License |
|
|
| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | -------------------------------------------------------------- |
|
|
| `src/shared/phishingBlocklist.json` | [eth-phishing-detect](https://github.com/AugurProject/eth-phishing-detect) community-maintained phishing domain blocklist | Copyright (c) 2018 kumavis | [DBAD (Don't Be a Dick)](https://github.com/philsturgeon/dbad) |
|
|
| `src/shared/scamlist.js` (address data from MyEtherWallet) | [ethereum-lists](https://github.com/MyEtherWallet/ethereum-lists) `addresses-darklist.json` | Copyright (c) 2020 MyEtherWallet | MIT |
|
|
| `src/shared/scamlist.js` (address data from EtherScamDB) | [EtherScamDB](https://github.com/MrLuit/EtherScamDB) `scams.yaml` | Copyright (c) 2018 Luit Hollander | MIT |
|
|
|
|
The full license texts for these third-party files are included in the
|
|
[LICENSE](LICENSE) file.
|
|
|
|
## Author
|
|
|
|
[@sneak](https://sneak.berlin)
|