Address rows on Home now carry an [x] control on wallets that derive their addresses from an extended key and hold more than one; it opens a confirmation screen before anything is removed. Removing an address destroys nothing: it stays derivable from key material the wallet still holds, and any funds at it stay where they are. That is also why the screen is not password-gated, unlike delete-wallet — a password gates the disclosure or destruction of a secret, and this does neither. Getting the address back into the list is another matter, and the copy states it exactly rather than promising a route the app refuses. "+" derives the next unused index, because the derivation counter is a high-water mark and is not rewound, and re-importing the wallet's key material is rejected as a duplicate for as long as the wallet is present — which it always is here, since a wallet never gives up its last address. What works is deleting the whole wallet in Settings, which asks for the password and destroys the stored secret, then importing again: the scan that follows rediscovers the address only if it has on-chain activity, and an address that was never used is not found by that scan. The text is built by recoveryPathText() rather than sitting in index.html so it can name the wallet's own kind of key material, an xprv wallet having no recovery phrase to re-import. A balance is surfaced as a warning, never a refusal, and holding something means any ERC-20 as well as ETH, at any size: an address with no ETH and a stablecoin position must not get a blank line on the screen whose job is to warn. The warning names no figure of its own, because the balance lines round to four decimals and a sentence built from a rounded number would report 0.0000 ETH for an address holding real money; the amounts come from the same balanceLinesForAddress() and getAddressValueUsd() every other screen uses. The state transition lives next to the wallet one in src/shared/walletDelete.js and shares its address comparison, site-permission cleanup and broadcast, so the rules match one level down: the last address of a wallet is never removable, the selection moves only when it was the address removed, an index after the splice is decremented, a selection in another wallet is untouched, and AUTISTMASK_ACTIVE_CHANGED is broadcast when the active address moves so a connected site stops being told about an address the user removed.
14 KiB
Workflow
git pullnextand cut a branch from it — one branch per issue, namedissue-<N>-<slug>. Never branch frommain.- Do the work as one commit whose title ends with
(closes #N), with theTODO.mdupdate in that same commit. - Move Next Step to the top of Completed Steps; move the top item of Future Steps into Next Step.
- Run
make fmt, thenmake check. A feature branch may be red;nextandmainmay not. - Rebase onto current
nextimmediately before pushing — other branches land onnextcontinuously — and re-runmake checkafter resolving, because a clean textual merge can still break the build. - Push the branch and open one PR per issue with base
next. Never basemain. - An independent reviewer who did not write the change gates the merge. On a
passed review the PR is squash-merged into
next. nextis the branch for the next milestone. It is kept green and mergeable tomainat any moment, without notice.mainreceives exactly one PR per milestone, fromnext. Releases are tagged frommain.
Status
pre-1.0, working towards the 1.0.0 milestone. Tagged v0.1.0 on 2026-02-27. The
milestone is in flight on next; its next -> main PR is
#190. make check verified
green on next at e9fa8be on 2026-08-10, and make build produces
dist/chrome/ and dist/firefox/ with every bundle verified to have DEBUG
compiled off.
The backlog lives on the
Gitea tracker, which is
authoritative; this file does not duplicate it. Full policy file set present. A
real-browser end-to-end suite (make test-e2e) now sits alongside make check,
which cannot see a runtime ReferenceError in a popup view.
Next Step
Land #152: add ESLint to
script/lint. make check is prettier --check only today and cannot catch
undefined identifiers, which is how
#150 and
#151 shipped.
Completed Steps
- 2026-08-12: An address can be removed from an HD or xprv wallet behind a confirmation screen that states nothing is destroyed, sharing the deletion state transitions with wallet deletion so the selection, site permissions and active-address broadcast follow the same rules (#162).
- 2026-08-12: The known-symbol spoof rule moved into
src/shared/symbolSpoof.jsand is now the only copy. The balance list had exempted symbols the token list maps tonull—"ETH"alone — so a fake ETH ERC-20 was hidden from the transaction history and the Send selector but listed as a holding named ETH. A symbol with no legitimate contract may now be borne by no contract on any of the three surfaces, and the native exemption is "has no contract address", so a second null-mapped symbol needs no call-site change. The user's real ETH balance is read over RPC and never passes through the rule (#235). - 2026-08-12:
script/verify-build's failure modes are now a committed target,script/test-verify-build, run bymake check. It asserts the exit status and the message of every case against a fixture tree in a temp dir, and drops privileges (proving the runner against a mode-000 file first) for the cases that only mean something when file permissions are in force (#227). - 2026-08-12: WaitTx lifecycle: a receipt and the 60-second timeout can no longer both render on one tick, no timer or in-flight lookup outlives its wait, a failed receipt lookup no longer counts as a timeout (but six in a row end the wait, reported as an unreachable network rather than as a timeout), and the wait now resumes after a popup close (#155).
- 2026-08-12: The private key export screen now wipes the key from the page
whenever it is left by any route, and a decrypt still in flight when the
screen is left is discarded instead of written; the same
onViewLeave()cleanup was extended to every other screen holding secret material in the DOM (AddWallet, ConfirmTx, DeleteWallet, ApproveTx, ApproveSign) (#221). - 2026-08-12: An xprv wallet already in storage that was imported from a
non-master key is detected from the depth of its stored
xpub, explained in the wallet list, and blocked from signing, sending and private-key export instead of throwing on the send screen (#234). - 2026-08-12: An unreported
holders_countis now parsed asnullrather than0, so the low-holder rule declines to judge an unknown count instead of hiding a legitimate token as spam, in both the transaction history and the Send token selector (#230). - 2026-08-12: Bundled token list documentation no longer states a count. The
four "top 250" claims in
README.mdand the "roughly 500" claim indocs/README.mdare replaced with a description of how the list is actually selected — a point-in-time CoinGecko snapshot of the highest-market-cap Ethereum mainnet ERC-20s — withTOKENSinsrc/shared/tokenList.jsnamed as the authoritative set (#239). - 2026-08-11: libsodium runs on WebAssembly in the shipped builds —
'wasm-unsafe-eval'added to both manifest CSPs after measuring the wasm2js fallback at 20x the Argon2id cost, pinned in both directions bytests/manifest.test.jsand observed in the real popup by the e2e suite (#182). - 2026-08-11: Known-symbol spoof verification became a Settings toggle
(
hideSpoofedSymbols), on by default, governing the transaction-history filter and the fraud-contract learning it feeds (#176). - 2026-08-11:
script/verify-buildnow walksdist/NUL-delimited and assertsdist/is a real directory, so a path with a trailing space or a newline can no longer carry a debug marker past the unlisted-bundle check (#223). - 2026-08-11: UTC Timestamps checkbox moved from the Token Spam Protection well into Display, next to the theme selector (#212).
- 2026-08-11: Network fee counted in the confirmation-screen balance check for
both ETH and ERC-20 sends, reserving what the node actually charges a type-2
transaction, with the arithmetic in a pure, unit-tested
src/shared/txValidation.js(#154). - 2026-08-11: A dust threshold of
0now means "hide nothing" instead of falling back to the 100,000 gwei default, and every address comparison insrc/shared/transactions.jsgoes through one case-normalising helper so a checksummed genuine contract is no longer read as a spoof (#179). - 2026-08-11: Password-gated recovery phrase display for HD wallets, reached from the wallet row in Settings, wiped on leaving the screen and excluded from the views the popup can reopen onto (#161).
- 2026-08-11: Extended-key import hardened — the base58 checksum is now enforced on every xprv and xpub, and a non-master key is refused with an explanation instead of being derived beneath (#210).
- 2026-08-11: the balance refresh and the 24-hour phishing list refresh moved
from
setIntervalto the extension alarms API, with the phishing delta and its fetch timestamps persisted to extension storage, so neither job dies with the MV3 service worker. Each job's freshness guard was decoupled from its alarm period at the same time — timed to the period, a guard vetoes its own scheduled tick and halves the real refresh rate (#158). - 2026-08-11: Policy compliance sweep — conditional verbose test rerun, local
Tailwind binary instead of
npx,--frozen-lockfileonmake install, and the Makefile-only targets documented in the README (#166). - 2026-08-11:
script/verify-builddiagnostics corrected: the both-markers message now states what is and is not proven, an unreadable bundle is diagnosed as an I/O fault rather than as changed output, the*.jsassumption lives only inbuild.js, and the unlisted-bundle scan hard-fails when it cannot enumeratedist/(#180). - 2026-08-11: Known-answer test coverage for the crypto core — BIP-39/BIP-32
derivation in
wallet.jsand the Argon2id vault invault.js(#159). - 2026-08-11: Three
README.mdclaims corrected against the code — blocklist attribution, token-display rule, navigation model (#213). - 2026-08-11: README Screen Map rebuilt from the code — every screen, element
and transition re-verified against
src/popup/(#164). - 2026-08-11:
docs/README.mdrewritten against the code: no competitor names, all five network destinations documented, password/Settings/Add Wallet sections corrected (#163). - 2026-08-11:
loadState()now deriveshasWalletfrom the wallet list instead of trusting the persisted flag, so a profile already saved inconsistent no longer stays broken on every load (#195). - 2026-08-11: Wallet deletion repairs its own state —
hasWalletfollows the remaining wallets, the selection only moves when it was deleted, and the active-address change is broadcast to connected sites (#156). - 2026-08-11: One row per on-chain value movement in transaction history: the
merge moved into the pure
mergeTransactionsand the zero-ETH native side of a plain ERC-20 transfer absorbed into its token row (#177). - 2026-08-11:
TODO.mdWorkflow rewritten to the branch-and-PR-per-issue model onnext, with Status and Next Step refreshed (#191). - 2026-08-09:
DEBUGbecame a build-time constant defaulting to off, injected as the__BUILD_DEBUG__esbuild define and turned on withAUTISTMASK_DEBUG=1, so a plainmake buildno longer hands every newly created wallet the publicly committed test recovery phrase (#149). - 2026-08-09: dApp approval signing moved into the popup — the password no longer crosses the extension messaging boundary; the background broadcasts and resolves approvals only, and verifies the signed artifact against the approval it holds (#157).
- 2026-08-09: Post-build assertion that every emitted bundle containing
constants.jshasDEBUGcompiled off, viascript/verify-buildon themake buildpath (#170). - 2026-08-09: Containerized Chrome end-to-end harness (
make test-e2e/script/test-e2e) driving the real popup with all network intercepted, plus the two used-but-not-imported crashes it caught: AddToken unreachable (#150) and TransactionDetail broken for every ERC-20 transfer (#151). Harness demonstrated failing before the fixes and passing after (#181). Interception covers the MV3 background service worker, not just the popup page, and a launch-time canary aborts the suite if worker traffic starts escaping. - 2026-08-09: Reviewed the repo end to end and filed the 1.0.0 backlog (#149-#168).
- 2026-08-09: Test coverage for the address-poisoning defense in
src/shared/transactions.js(#160) - 2026-07-26: About well in settings with build info, repo link and the version click easter egg (#145); proper view navigation stack (#146).
- 2026-07-07 Adopted scripts-to-rule-them-all:
script/entrypoints, Makefile shims, README Entrypoints section (#148) - 2026-03-01: USD display suppressed on testnets (#142); estimated USD for ETH in approve-tx view (#141).
- Sepolia testnet support (#137); etherscan links go to token-specific URLs (#136).
- Transaction detail improvements: Type field and on-chain details (#130), txid-first reordering (#133), swap display corrections (#128), expanded confirm-tx warnings (#118).
- Dark mode theme setting (Light/Dark/System) with contrast fixes (#126); timestamps include timezone offset (#120); layout shift audit, reserved space for error messages (#124).
- Copy-flash visual feedback with timing tune (#113, #121); cross-wallet-type duplicate detection (#115).
- 2026-02-27: v0.1.0 tagged.
- 2026-02-24: Initial scaffolding: popup UI, BIP-39 wallet creation via ethers.js, wallet persistence, real ETH balances over RPC, ENS forward and reverse resolution.
Future Steps
Only work that has no issue of its own belongs here; everything else is on the tracker.
- Pre-1.0 security review of the extension (key handling, DEBUG mode policy, RPC input validation) before any 1.0rc tag. Individual filed issues are parts of it, but the review is broader than any of them.
- Decide whether docker-in-docker makes
make test-e2erunnable in the Gitea workflow. Extending the suite itself is tracked as #183 and #184. - Cut 1.0.0 once the milestone is empty, then continue tagging as milestones land.