# 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 setup make build ``` `make setup` is the entrypoint for a fresh clone: it installs dependencies from the lockfile and installs the git pre-commit hook. 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`. ### Debug Builds `make build` always produces a release build: the build-time `DEBUG` constant is `false`, so wallet creation uses real entropy and the red banner is off. To produce a debug build instead, set `AUTISTMASK_DEBUG=1` in the environment: ```bash make build-debug # or: AUTISTMASK_DEBUG=1 make build ``` Only the exact value `1` enables it; any other value (including unset, empty, or `true`) yields a release build, so a typo cannot accidentally ship the debug behavior. The build prints which mode it used. See the [DEBUG Mode Policy](#debug-mode-policy) for what the flag changes. **Never distribute a debug build** — every wallet it creates gets the same publicly known test recovery phrase. Both builds end by running `script/verify-build`, which reads the compiled `DEBUG` state back out of the emitted bundles and fails the build if it is not the one that was asked for. The test suite cannot check this: it loads `src/shared/constants.js` outside a bundle, so it only ever sees the fallback value. The assertion is on the artifacts because that is where the property lives. ## Entrypoints This repository adheres to the [Scripts to Rule Them All](https://github.com/github/scripts-to-rule-them-all) standard: normalized scripts in `script/` are the entrypoints for the development workflow, and the Makefile targets are thin shims that call them. We provide: - `script/bootstrap` — install all dependencies (pinned node via nvm if needed, yarn via corepack, `yarn install --frozen-lockfile`) - `script/setup` — make a fresh clone ready for development: bootstrap plus the git pre-commit hook - `script/projectname` — print the project name (used for the Docker image tag) - `script/test` — run the test suite (jest) - `script/test-e2e` — run the Chrome browser end-to-end suite (docker is the only prerequisite: it builds a pinned image that carries the repo and a fresh extension build, see [End-to-End Tests](#end-to-end-tests)) - `script/test-e2e-firefox` — run the Firefox browser end-to-end suite (same, against an image with a pinned Firefox and geckodriver, see [End-to-End Tests](#end-to-end-tests)) - `script/lint` — run ESLint (`eslint.config.js`) and then `prettier --check`, failing on either. It never writes: `--fix` is not in this path, so `make check` stays non-mutating. Linting runs in the container — the script builds the Dockerfile's `lint` stage — because an ESLint result that depends on whichever ESLint the host happens to have is not a result. Docker is therefore required to lint; inside that image `AUTISTMASK_LINT_NATIVE=1` makes the same script lint in place instead of recursing. - `script/fmt` — format all files (writes) - `script/fmt-check` — check formatting (read-only) - `script/check` — run test, test-verify-build, lint, and fmt-check - `script/verify-build` — assert the compiled `DEBUG` state of the bundles in `dist/`: every bundle containing `src/shared/constants.js` must have `DEBUG` off, or on when `AUTISTMASK_DEBUG=1`. Run automatically at the end of `make build` and `make build-debug`; fails loudly rather than passing if it cannot determine a bundle's state. Not part of `make check`, which does not depend on build artifacts existing. - `script/test-verify-build` — exercise every failure mode of `script/verify-build` against a fixture tree in a temp dir, asserting the exit status and the message of each. Part of `make check`; it reads no build artifacts and writes nothing under `dist/`. The cases that depend on file permissions cannot mean anything for a process that is not subject to them, so the harness proves its runner against a mode-000 file before counting them, dropping to an unprivileged user when run as root; if it cannot, it skips those cases and says so in a banner rather than passing them. - `script/docker` — build the Docker image tagged via `script/projectname` - `script/cibuild` — CI entrypoint: plain `docker build .` - `script/precommit` — run by the git pre-commit hook; runs `script/check` - `script/install-precommit` — install the git pre-commit hook The Makefile shims to those. It also carries a few targets that have no `script/` counterpart and are Makefile-only conveniences: - `make install` — `yarn install --frozen-lockfile` on its own, without the rest of `script/bootstrap`. Frozen so a stale `yarn.lock` fails instead of being silently rewritten. Use `make setup` for a fresh clone. - `make hooks` — shims to `script/install-precommit` - `make build` — build the extension into `dist/chrome/` and `dist/firefox/` - `make build-debug` — the same build with `AUTISTMASK_DEBUG=1` (see [Debug Builds](#debug-builds)) - `make clean` — remove `dist/` - `make dev` — build in watch mode ## End-to-End Tests There are two suites, one per browser, and they share no code. Chrome runs on Playwright; Firefox has its own WebDriver client, because Playwright cannot observe errors on a Firefox extension page at all — see [Firefox](#firefox-make-test-e2e-firefox) below. Both require docker, and both are outside `make check`. ### Chrome (`make test-e2e`) `make test-e2e` builds `dist/chrome/` and drives the **real popup in a real Chrome**, loaded as an unpacked MV3 extension inside a pinned `mcr.microsoft.com/playwright` container (pinned by digest in `tests/e2e/Dockerfile`, which is also where the extension is built; docker is required and the suite fails loudly rather than skipping if it is unavailable). The suite lives in `tests/e2e/` and is driven by `playwright-core`, whose version must stay matched to the container's Playwright version — the browsers ship inside the image. It covers popup load, WebAssembly compilation under the shipped CSP (see [Content Security Policy](#content-security-policy)), wallet creation through the UI, the Add Token screen, the transaction detail screen for an ERC-20 transfer, and the recovery phrase screen — which wallet types are offered it, that it holds nothing before the password is accepted, that a wrong password reveals nothing, that leaving it by either route wipes it — including a leave taken while the decrypt is still running — and that reopening the popup does not land on it. It also covers address removal: which wallets offer the control at all, that the confirmation states the route back rather than showing an empty paragraph, that leaving the confirmation removes nothing, and that confirming it does. All outbound network is intercepted at the browser level and served from fixtures in `tests/e2e/network.js`, so the run is deterministic and fully offline; unrecognised outbound requests are reported as failures rather than silently allowed. It also covers the **Settings screen**, which holds the densest run of element id lookups in the codebase and where one wrong id leaves the whole popup blank rather than only degrading Settings: that the screen renders populated — the About well and the wallet list are read back, so a `show()` that stopped early is caught rather than merely a view that failed to appear — that the four Token Spam Protection controls are real checkboxes defaulted on, and that the theme and network selectors offer exactly the choices `src/shared/networks.js` and `index.html` define. What the selectors persist is then driven through the UI to `dark` and `sepolia` and reasserted after a popup reopen before being restored the same way; neither value is its selector's first `