From 3bd2b585435533c97b5fe41a3dab525c555dfaa8 Mon Sep 17 00:00:00 2001 From: sneak Date: Thu, 26 Feb 2026 02:13:39 +0700 Subject: [PATCH] Token auto-discovery, tx history, balance polling, EIP-6963, UI overhaul Major changes: - Fetch token balances and tx history from Blockscout API (configurable) - Remove manual token discovery (discoverTokens) in favor of Blockscout - HD address gap scanning on mnemonic import - Duplicate mnemonic detection on wallet add - EIP-6963 multi-wallet discovery + selectedAddress updates in inpage - Two-tier balance refresh: 10s while popup open, 60s background - Fix $0.00 flash before prices load (return null when no prices) - No-layout-shift: min-height on total value element - Aligned balance columns (42ch address width, consistent USD column) - All errors use flash messages instead of off-screen error divs - Settings gear in global title bar, add-wallet moved to settings pane - Settings wells with light grey background, configurable Blockscout URL - Consistent "< Back" buttons top-left on all views - Address titles (Address 1.1, 1.2, etc.) on main and detail views - Send view shows current balance of selected asset - Clickable affordance policy added to README - Shortened mnemonic backup warning - Fix broken background script constant imports --- README.md | 25 +++ manifest/chrome.json | 7 + manifest/firefox.json | 2 +- src/background/index.js | 34 ++- src/content/index.js | 20 +- src/content/inpage.js | 65 +++++- src/popup/index.html | 341 ++++++++++++++++--------------- src/popup/index.js | 32 ++- src/popup/styles/main.css | 1 + src/popup/views/addToken.js | 41 ++-- src/popup/views/addWallet.js | 65 ++++-- src/popup/views/addressDetail.js | 104 +++++++--- src/popup/views/confirmTx.js | 24 ++- src/popup/views/helpers.js | 64 +++++- src/popup/views/home.js | 30 +-- src/popup/views/importKey.js | 19 +- src/popup/views/receive.js | 7 +- src/popup/views/send.js | 33 ++- src/popup/views/settings.js | 68 +++++- src/shared/balances.js | 196 +++++++++++++----- src/shared/constants.js | 11 +- src/shared/log.js | 30 +++ src/shared/prices.js | 8 +- src/shared/scamlist.js | 38 ++-- src/shared/state.js | 11 +- src/shared/transactions.js | 108 ++++++++++ src/shared/wallet.js | 14 +- 27 files changed, 978 insertions(+), 420 deletions(-) create mode 100644 src/shared/log.js create mode 100644 src/shared/transactions.js diff --git a/README.md b/README.md index 5b605d2..3a8fe8a 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,24 @@ crypto knowledge. - **360x600 popup**: Standard browser extension popup dimensions. The UI is designed for this fixed viewport — no responsive breakpoints needed. +#### No Layout Shift + +Asynchronous state changes (clipboard confirmation, transaction status, error +messages, flash notifications) must never move existing UI elements. All dynamic +content areas reserve their space up front using `min-height` or always-present +wrapper elements. `visibility: hidden` is preferred over `display: none` when +the element's space must be preserved. This prevents jarring content jumps that +disorient users and avoids mis-clicks caused by shifting buttons. + +#### 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. + #### Language & Labeling All user-facing text avoids crypto jargon wherever possible: @@ -509,6 +527,13 @@ Everything needed for a minimal working wallet that can send and receive ETH. - [ ] Test on Chrome (Manifest V3) - [ ] Test on Firefox (Manifest V2) +### Scam List + +- [ ] Research and document each address in scamlist.js (what it is, why it's on + the list, source) +- [ ] Add more known fraud addresses from Etherscan labels (drainers, phishing, + address poisoning deployers) + ### Post-MVP - [ ] EIP-1193 provider injection (window.ethereum) for web3 site connectivity diff --git a/manifest/chrome.json b/manifest/chrome.json index 9921717..c589225 100644 --- a/manifest/chrome.json +++ b/manifest/chrome.json @@ -4,6 +4,7 @@ "version": "0.1.0", "description": "Minimal Ethereum wallet for Chrome", "permissions": ["storage", "activeTab"], + "host_permissions": [""], "action": { "default_popup": "src/popup/index.html" }, @@ -11,6 +12,12 @@ "service_worker": "src/background/index.js" }, "content_scripts": [ + { + "matches": [""], + "js": ["src/content/inpage.js"], + "run_at": "document_start", + "world": "MAIN" + }, { "matches": [""], "js": ["src/content/index.js"], diff --git a/manifest/firefox.json b/manifest/firefox.json index 758ae5d..830f5c3 100644 --- a/manifest/firefox.json +++ b/manifest/firefox.json @@ -3,7 +3,7 @@ "name": "AutistMask", "version": "0.1.0", "description": "Minimal Ethereum wallet for Firefox", - "permissions": ["storage", "activeTab"], + "permissions": ["storage", "activeTab", ""], "browser_action": { "default_popup": "src/popup/index.html" }, diff --git a/src/background/index.js b/src/background/index.js index fbf9591..9d3b820 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -2,8 +2,12 @@ // Handles EIP-1193 RPC requests from content scripts and proxies // non-sensitive calls to the configured Ethereum JSON-RPC endpoint. -const CHAIN_ID = "0x1"; -const DEFAULT_RPC = "https://eth.llamarpc.com"; +const { + ETHEREUM_MAINNET_CHAIN_ID, + DEFAULT_RPC_URL, +} = require("../shared/constants"); +const { state, loadState, saveState } = require("../shared/state"); +const { refreshBalances } = require("../shared/balances"); const storageApi = typeof browser !== "undefined" @@ -17,7 +21,7 @@ const connectedSites = {}; async function getState() { const result = await storageApi.get("autistmask"); - return result.autistmask || { wallets: [], rpcUrl: DEFAULT_RPC }; + return result.autistmask || { wallets: [], rpcUrl: DEFAULT_RPC_URL }; } async function getAccounts() { @@ -33,7 +37,7 @@ async function getAccounts() { async function getRpcUrl() { const state = await getState(); - return state.rpcUrl || DEFAULT_RPC; + return state.rpcUrl || DEFAULT_RPC_URL; } // Proxy an RPC call to the Ethereum node @@ -94,7 +98,7 @@ async function handleRpc(method, params, origin) { } if (method === "eth_chainId") { - return { result: CHAIN_ID }; + return { result: ETHEREUM_MAINNET_CHAIN_ID }; } if (method === "net_version") { @@ -103,7 +107,7 @@ async function handleRpc(method, params, origin) { if (method === "wallet_switchEthereumChain") { const chainId = params?.[0]?.chainId; - if (chainId === CHAIN_ID) { + if (chainId === ETHEREUM_MAINNET_CHAIN_ID) { return { result: null }; } return { @@ -205,6 +209,24 @@ async function handleRpc(method, params, origin) { return { error: { message: "Unsupported method: " + method } }; } +// Background balance refresh: every 60 seconds when the popup isn't open. +// When the popup IS open, its 10-second interval keeps lastBalanceRefresh +// fresh, so this naturally skips. +const BACKGROUND_REFRESH_INTERVAL = 60000; + +async function backgroundRefresh() { + await loadState(); + const now = Date.now(); + if (now - (state.lastBalanceRefresh || 0) < BACKGROUND_REFRESH_INTERVAL) + return; + if (state.wallets.length === 0) return; + await refreshBalances(state.wallets, state.rpcUrl, state.blockscoutUrl); + state.lastBalanceRefresh = now; + await saveState(); +} + +setInterval(backgroundRefresh, BACKGROUND_REFRESH_INTERVAL); + // Listen for messages from content scripts runtime.onMessage.addListener((msg, sender, sendResponse) => { if (msg.type !== "AUTISTMASK_RPC") return; diff --git a/src/content/index.js b/src/content/index.js index cc708eb..b66b44c 100644 --- a/src/content/index.js +++ b/src/content/index.js @@ -1,15 +1,17 @@ // AutistMask content script — bridges between inpage (window.ethereum) // and the background service worker via extension messaging. -// Inject the inpage script into the page's JS context -const script = document.createElement("script"); -script.src = (typeof browser !== "undefined" ? browser : chrome).runtime.getURL( - "src/content/inpage.js", -); -script.onload = function () { - this.remove(); -}; -(document.head || document.documentElement).appendChild(script); +// In Chrome (MV3), inpage.js runs as a MAIN-world content script declared +// in the manifest, so no injection is needed here. In Firefox (MV2), the +// "world" key is not supported, so we inject via a