diff --git a/README.md b/README.md index f4e8ae7..b145b8b 100644 --- a/README.md +++ b/README.md @@ -12,16 +12,29 @@ 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 performs best-effort Etherscan address label lookups -during transaction confirmation. A community-maintained phishing domain -blocklist is built into the extension at build time and checked locally; nothing -is fetched for it at runtime. +Everything you need, nothing you don't. We import as few libraries as possible +and don't implement any crypto. By default the wallet sends user-specific data +only to the Ethereum RPC endpoint. The user can change that endpoint in +Settings. The endpoint defaults to a public node. + +Out of the box the extension contacts two services: + +- the configured RPC node, for blockchain interactions +- a public CoinDesk API (no API key), for realtime prices. The API receives + token symbols and nothing else. + +Two more services are sent none of the user's data until the user ticks a +checkbox for each. Welcome and Settings both offer the two checkboxes: + +- "Use Blockscout": a Blockscout block-explorer API, used for: + - transaction history + - the tokens the user has not tracked + - transaction details +- "Check recipients on Etherscan": a best-effort Etherscan label lookup of the + recipient during transaction confirmation + +A community-maintained phishing domain blocklist is built into the extension at +build time and checked locally. Nothing is fetched for it at runtime. 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 @@ -422,6 +435,35 @@ It also covers the red banner on the Settings screen: switching the network there to Sepolia must put up the `[TESTNET]` banner, and switching back to mainnet must take it down, both while Settings is still on screen. +It also covers what a new install sends to Blockscout and Etherscan +([#668](https://git.eeqj.de/sneak/AutistMask/issues/668)). The harness installs +a route handler before the first popup page opens. From then on, the handler +records every request the extension makes, from its pages and from its +background worker. The background worker starts earlier, but it makes no request +while it starts up. The tests take these steps in order: + +- Welcome must offer "Use Blockscout" and "Check recipients on Etherscan" + unticked. +- With neither ticked, Home, AddressDetail, AddressToken and ConfirmTx must each + show the line saying what is off. +- The popup is then closed until the background has run a balance refresh of its + own. No recorded request may have gone to Blockscout or Etherscan. +- "Use Blockscout" is ticked from AddressDetail's gear. On "Back", AddressDetail + must ask Blockscout for its history and no longer say the setting is off. + Blockscout must then be asked for the token balances. +- The stub Blockscout then reports a token the user has not tracked. Home lists + it. "Use Blockscout" is unticked from AddressDetail's gear. On "Back", the + token must be gone from AddressDetail, from Send's token dropdown and from + Home. Those three screens must each show the line saying the setting is off. + The setting is then ticked again. +- "Check recipients on Etherscan" is ticked from ConfirmTx's gear. On "Back", + ConfirmTx must ask Etherscan about the recipient and show the label the stub + page gives the recipient. + +Both settings stay on for the rest of the run. TransactionDetail with "Use +Blockscout" off is not driven here: while the setting is off, there is no +history row to open it from. The unit tests cover that case. + It also covers the confirmation screen, for both a native ETH send and an ERC-20 send: Send disabled while the fee estimate is in flight, enabled once it lands, the fee block quoting the expected cost and the reserve separately, the distinct @@ -724,13 +766,11 @@ Three limits are worth knowing, all real differences from the Chrome suite: in `tests/e2e/dapp.js`, which serves the dApp page and a JSON-RPC node and which the extension's `rpcUrl` is pointed at for the dApp steps; a JSON-RPC method that fixture does not model fails the run rather than answering `null`. - Everything else, Blockscout and the price feed among it, has no fixture and - simply fails, and the extension swallows its own fetch failures, so only the - _failure_ branches of that code are ever executed. A `ReferenceError` in the - success path of `renderTransactions`, or of price rendering, passes this suite - green. The offline run is also weaker than the Chrome suite's interception for - those calls: it proves nothing got out, but it cannot report which requests - were attempted. + The suite leaves "Use Blockscout" off, so Blockscout is not asked. A + `ReferenceError` in the success path of `renderTransactions`, or of price + rendering, passes this suite green. The offline run is also weaker than the + Chrome suite's interception for those calls: it proves nothing got out, but it + cannot report which requests were attempted. - **The site-connection prompt always opens in a window of its own.** The profile turns off `extensions.openPopupWithoutUserGesture.enabled`, so `src/background/index.js` falls back from the toolbar popup, which WebDriver @@ -845,13 +885,16 @@ src/ views/ — one JS module per screen (home, send, approval, etc.) shared/ — modules used by both popup and background alarms.js — recurring background jobs (extension alarms API) - balances.js — ETH and tracked token balances via RPC, others via Blockscout + balances.js — ETH and tracked token balances via RPC, others via + Blockscout while "Use Blockscout" is on constants.js — chain IDs, default RPC endpoint, ERC-20 ABI ens.js — ENS forward/reverse resolution (popup only) holders.js — holder-count parsing and the low-holder rule prices.js — ETH/USD and token/USD via CoinDesk API scamlist.js — known fraud contract addresses state.js — persisted state (extension storage) + storedSettings.js — "Use Blockscout" and "Check recipients on Etherscan", + read from storage for the requests those settings gate symbolSpoof.js — the known-symbol spoof rule, shared by all surfaces tokenList.js — top ERC-20 tokens by market cap (hardcoded) transactions.js — tx history fetching + anti-poisoning filters @@ -1122,25 +1165,39 @@ with the same floor (`.5` reads `.5000`). **Specific Exception — unknown token scale:** Calldata carries base units and no scale, so every amount on the dApp approval screen needs the token's `decimals`. -It is resolved from the bundled token list, then from the tokens the user tracks -on the network the transaction is for, then, when that is the active network, -from what the block explorer reported for the contract in the wallet's balances, -which are the active network's only (`resolveTokenDecimals()` in -`src/shared/approvalAmount.js`). Where none of them answers, the amount is not -formatted: the line reads `5000000000 base units (decimals unknown)`. A guessed -scale is not an approximation but a different number — 1,000 units of a -6-decimal token formatted at 18 decimals reads `0.000000001` — on the screen -whose only job is to state what is being authorized. Both amount paths of that -screen take this rule: the ERC-20 `transfer`/`approve` line -(`src/popup/views/approval.js`) and the swap's `Amount`, `Min. received`, -`Moved from the sender` and `Permit2 allowance` lines and the `Paid to` line of -a move into the router (`src/shared/uniswap.js`). The token permission warning -on the signature screen takes the same rule for its amounts. An unbounded -allowance or permit needs no scale to describe and is still shown as -`Unlimited`. A source's answer counts only if it is a whole number from 0 to 80: -`decimals()` returns a `uint8`, but `formatUnits()` cannot format more than 80 -decimal places, so a token that reports 81 to 255 is shown as one whose scale -nothing knows. +The wallet takes the `decimals` from these sources, in this order +(`resolveTokenDecimals()` in `src/shared/approvalAmount.js`): + +- the bundled token list +- the tokens the user tracks on the network the transaction is for +- what the block explorer reported for the contract in the wallet's balances. + Those balances cover the active network only. This source is used only while + "Use Blockscout" is on (`tokenSourcesByChainId()` in + `src/popup/views/helpers.js`). + +The token's name comes from the same sources. While "Use Blockscout" is off, a +token gets a line under it when the bundled list and the tracked tokens lack its +name or decimals. The line says "Blockscout's report of your balances is not +used to name this token, because "Use Blockscout" is off in Settings." These +screens show the line: + +- TxApproval and SignApproval +- WaitTx, SuccessTx and ErrorTx, which follow TxApproval + +Where none of the sources answers, the amount is not formatted: the line reads +`5000000000 base units (decimals unknown)`. A guessed scale is not an +approximation but a different number — 1,000 units of a 6-decimal token +formatted at 18 decimals reads `0.000000001` — on the screen whose only job is +to state what is being authorized. Both amount paths of that screen take this +rule: the ERC-20 `transfer`/`approve` line (`src/popup/views/approval.js`) and +the swap's `Amount`, `Min. received`, `Moved from the sender` and +`Permit2 allowance` lines and the `Paid to` line of a move into the router +(`src/shared/uniswap.js`). The token permission warning on the signature screen +takes the same rule for its amounts. An unbounded allowance or permit needs no +scale to describe and is still shown as `Unlimited`. A source's answer counts +only if it is a whole number from 0 to 80: `decimals()` returns a `uint8`, but +`formatUnits()` cannot format more than 80 decimal places, so a token that +reports 81 to 255 is shown as one whose scale nothing knows. The rule holds only if nothing invents a scale UPSTREAM of it. Those three sources are read as authoritative, so a value written into one of them cannot be @@ -1163,12 +1220,13 @@ and compare against. Reading the stored field directly instead answers `null` for a bundled or tracked token the explorer merely omitted, which is not a refusal the wallet has any reason to make. -The Send screen also consults every address's explorer reports, so a contract -two addresses report different `decimals` for has no scale there, and the stored -balance, formatted at one of those scales, is withdrawn with it. The Send -screen's `Current balance` and the confirmation screen's balance line then both -read `unknown (SYMBOL)`. The balance list formats each explorer row as it is -fetched, without that cross-address check, and shows the row's figure. +While "Use Blockscout" is on, the Send screen also consults every address's +explorer reports. The reports for two addresses may give different `decimals` +for one contract. That token then has no scale on Send. Its stored balance is +not shown either. The Send screen's `Current balance` and the confirmation +screen's balance line then both read `unknown (SYMBOL)`. The balance list +formats each explorer row as it is fetched, without that cross-address check, +and shows the row's figure. **Decoded amount lines on the transaction approval screen:** the `Amount` line of a decoded ERC-20 call, and the `Amount` and `Min. received` lines of a @@ -1388,23 +1446,39 @@ that token's previous entry, as a failed ETH balance keeps its figure, or takes Blockscout's entry from the same refresh when there is none. Every other token comes from the Blockscout `token-balances` response (`fetchTokenBalances()`), so tokens do appear without the user adding them, and they keep their previous -figures while Blockscout fails. A tracked token is shown when its balance is -nonzero, and any other ERC-20 when its balance is nonzero and it is in the -bundled known-token list or has 1,000 or more holders. A token claiming a symbol -from the bundled list from any other contract address is always dropped, tracked -or not, and so is any token claiming a symbol that belongs to the native asset -and therefore has no legitimate contract at all (`"ETH"`, and every network's -`nativeCurrency`, such as `"SepoliaETH"`, on every network). That filter is -unconditional — the "Hide tokens with fewer than 1,000 holders" setting governs -the transaction history and the send-screen token selector, not this list. A -token's holder count is unknown when the explorer reports none, or reports -anything other than a whole number written in digits alone, such as `1,000` or -`1e3` (`parseHoldersCount()` in `src/shared/holders.js`). This list does not -take an unknown count as 1,000 or more, so such a token is shown only when it is -on the bundled list or tracked. Every nonzero holding of a token shown is stored -exactly, however small, but a holding below 0.000001 is left out of the balance -lists, the send-screen token selector, the address total and the remove-address -warning (`isBelowOneMillionth()` in `src/shared/amountDisplay.js`). The Send and +figures while Blockscout fails. + +The refresh asks Blockscout only while the user has "Use Blockscout" on. The +setting is off until the user turns it on. While it is off: + +- Only tracked tokens are listed. Turning the setting off drops every other + token from every address at once. +- A tracked token's entry has no holder count and no `decimals` from Blockscout. +- A tracked token's balance uses the decimals from the bundled list, or else the + decimals saved when the token was tracked. + +Blockscout may answer just before the user turns the setting off. So the popup's +refresh and the background's both check the stored setting again when they save. +If the setting is off by then, the save drops every token the user has not +tracked. + +A tracked token is shown when its balance is nonzero, and any other ERC-20 when +its balance is nonzero and it is in the bundled known-token list or has 1,000 or +more holders. A token claiming a symbol from the bundled list from any other +contract address is always dropped, tracked or not, and so is any token claiming +a symbol that belongs to the native asset and therefore has no legitimate +contract at all (`"ETH"`, and every network's `nativeCurrency`, such as +`"SepoliaETH"`, on every network). That filter is unconditional — the "Hide +tokens with fewer than 1,000 holders" setting governs the transaction history +and the send-screen token selector, not this list. A token's holder count is +unknown when the explorer reports none, or reports anything other than a whole +number written in digits alone, such as `1,000` or `1e3` (`parseHoldersCount()` +in `src/shared/holders.js`). This list does not take an unknown count as 1,000 +or more, so such a token is shown only when it is on the bundled list or +tracked. Every nonzero holding of a token shown is stored exactly, however +small, but a holding below 0.000001 is left out of the balance lists, the +send-screen token selector, the address total and the remove-address warning +(`isBelowOneMillionth()` in `src/shared/amountDisplay.js`). The Send and confirmation screens show it when its token is the one being sent. Tracked tokens with a zero balance are listed as well while "Show tracked tokens with zero balance" is on. @@ -1578,6 +1652,18 @@ such as an edit typed into Settings and not yet saved. Home is the one screen "Back" always re-renders, so the wallet list reflects anything that changed while the user was away from it. +Changing "Use Blockscout" or "Check recipients on Etherscan" in Settings makes +every screen but Settings count as not yet rendered. "Back" then renders again +any screen that a reopened popup can render. A change to "Use Blockscout" also +cuts the stack, as a network switch does (below). The cut drops Send, whose +token dropdown was filled under the old setting. "Back" cannot render Send +again. A change to "Check recipients on Etherscan" leaves the stack as it is. + +Any other open popup takes either change from the stored record, as it takes a +network switch (below). That popup then redraws its top screen as a reopened +popup would. A screen that a reopened popup cannot render, such as Send, is +replaced by Home. + A network switch leaves every screen on the page showing the previous network. The user makes it in Settings, or approves a site's request and the background saves it. Any other popup open meanwhile takes the switch from the stored @@ -1619,8 +1705,17 @@ view would leave a wallet one click from deletion. screen in that case. - **Elements**: - "Welcome! To get started, add a wallet." text + - A well opening with "This wallet sends your data only to the Ethereum RPC + endpoint, unless you turn on one of these. You can change them later in + Settings." + - In the well, the "Use Blockscout" and "Check recipients on Etherscan" + checkboxes, the same two as in Settings. Under each, a sentence says what + the setting sends and to which service. Both are unticked until the user + ticks them - "Add wallet" button - **Transitions**: + - Ticking or unticking either checkbox → stores the setting, as Settings + does (no screen change) - "Add wallet" → **AddWallet** #### Home (`main`) @@ -1641,6 +1736,8 @@ view would leave a wallet one click from deletion. holding more than one address); the ENS name, if resolved, is below it; then the full address on a row of its own, followed by the address USD total and a balance line for ETH and for each token shown for that address + - While "Use Blockscout" is off, a line under the wallet list: "Only tracked + tokens are listed, because "Use Blockscout" is off in Settings." - "Recent Transactions": up to 25 transactions merged across every address of every wallet, deduplicated by hash and filtered. Each row is three lines: age and direction, then the counterparty's colour dot (with our own @@ -1648,7 +1745,11 @@ view would leave a wallet one click from deletion. counterparty's full address on a row of its own. A contract creation has no counterparty: its second line is the amount alone and its third line says "This transaction creates a new contract. It has no recipient." The - transaction lists on AddressDetail and AddressToken draw the same rows + transaction lists on AddressDetail and AddressToken draw the same rows. + The history comes from Blockscout. While "Use Blockscout" is off, + Blockscout is not asked. This list then shows only the line "No + transaction history is shown, because "Use Blockscout" is off in + Settings." - "Add additional wallet..." link at bottom - **Transitions**: - Tap address row → sets the active address and broadcasts @@ -1717,9 +1818,13 @@ view would leave a wallet one click from deletion. - USD total for address (see [Partial USD totals](#partial-usd-totals)) - Balance list: ETH + the ERC-20 tokens shown for this address (4 decimal places, USD inline). Each balance row is clickable → **AddressToken** + - While "Use Blockscout" is off, a line under the balance list: "Only + tracked tokens are listed, because "Use Blockscout" is off in Settings." - Send / Receive / + Token buttons and a "···" menu button - "···" dropdown containing a single "Export Private Key" entry - - Transaction list (with ENS resolution for counterparties) + - Transaction list (with ENS resolution for counterparties). While "Use + Blockscout" is off, it shows only the line "No transaction history is + shown, because "Use Blockscout" is off in Settings." - **Transitions**: - Tap balance row → **AddressToken** (for that token) - "Send" → **Send** (refuses with a flash message on a zero balance) @@ -1787,9 +1892,13 @@ view would leave a wallet one click from deletion. - Token contract well (ERC-20 only): full contract address (tap to copy, etherscan link) plus name, symbol, decimals, holder count and project website where known. The "Holders:" row is left out, not shown as 0, when - the token's balance-list entry has no holder count: the explorer did not - report a readable one, or the token is not in the balance list - - Token-filtered transaction list (only this token's transfers) + the token's balance-list entry has no holder count. That happens when: + - the explorer did not report a readable one + - "Use Blockscout" is off + - the token is not in the balance list + - Token-filtered transaction list (only this token's transfers). While "Use + Blockscout" is off, it shows only the line "No transaction history is + shown, because "Use Blockscout" is off in Settings." - **Transitions**: - "Send" → **Send** (token locked: the dropdown is replaced by a static symbol and contract address) @@ -1806,6 +1915,9 @@ view would leave a wallet one click from deletion. - From: address with color dot + etherscan link - What to send: token dropdown (or static display with contract address when locked from AddressToken) + - While "Use Blockscout" is off, a line under the token dropdown: "Only + tracked tokens are listed, because "Use Blockscout" is off in Settings." + The line is hidden with the dropdown when Send is opened from AddressToken - To: address or ENS name input, with an inline validation message - Amount input with current balance display, which reads `Current balance: unknown (SYMBOL)` for a token whose scale is unknown, as @@ -1825,13 +1937,14 @@ view would leave a wallet one click from deletion. - **Transitions**: - "Review" (valid inputs, ENS resolved) → **ConfirmTx** - "Review" with an unresolvable ENS name or an invalid amount → flash - message, no screen change. An amount is valid when it is a number above - zero, written as digits with at most one decimal point, with nothing but - zeros after the token's last decimal place. ETH has 18 places, and a token - with more, or with an unknown number, is held to 18. Zeros past those - places leave the amount unchanged, so `1.0000000` of a 6-place token - passes and is sent as exactly 1. ConfirmTx makes the same check before - anything can be sent + message, no screen change. A name with no address, or one resolved + off-chain, gets "That ENS name has no on-chain address." An amount is + valid when it is a number above zero, written as digits with at most one + decimal point, with nothing but zeros after the token's last decimal + place. ETH has 18 places, and a token with more, or with an unknown + number, is held to 18. Zeros past those places leave the amount unchanged, + so `1.0000000` of a 6-place token passes and is sent as exactly 1. + ConfirmTx makes the same check before anything can be sent - "Back" → previous screen (Home, AddressDetail, or AddressToken) #### ConfirmTx (`confirm-tx`) @@ -1875,7 +1988,12 @@ view would leave a wallet one click from deletion. recipient that has never sent a transaction and holds no ETH, recipient is a contract, burn address, and an Etherscan phishing/scam label. An address that has received ETH but never sent a transaction still holds that ETH, - so it gets no warning + so it gets no warning. The Etherscan label check is made only while "Check + recipients on Etherscan" is on. While the setting is off, the Etherscan + box is left out. A line in its place says "This address was not checked on + Etherscan, because "Check recipients on Etherscan" is off in Settings." + The box or the line is chosen when the screen is drawn, so neither moves + anything later - Errors (an invalid amount), plus five reserved error boxes, so the balance and the fee arriving later move nothing — the amount exceeds the balance (for a token whose scale is unknown it says the token's balance is @@ -1952,6 +2070,9 @@ view would leave a wallet one click from deletion. `Unknown (could not be decoded)` with no symbol, not the transaction's ETH value. When that swap's `Token In` is `ETH (native)`, the transaction's ETH value in the network's currency, as for any other transaction + - A line under the amount, worded as on TxApproval. While "Use Blockscout" + is off, the line shows when the bundled list and the tracked tokens lack + the token's name or decimals. - To: color dot + full address + etherscan link; for a contract creation, which has no recipient, "This transaction creates a new contract. It has no recipient." instead @@ -1984,7 +2105,9 @@ view would leave a wallet one click from deletion. - **Elements**: - "Transaction Confirmed" heading - Decoded action well (shown when the transaction carried recognized - calldata; the top-level Amount and To are hidden in that case) + calldata; the top-level Amount and To are hidden in that case). Each token + in the well gets TxApproval's line under it whenever TxApproval would show + that line. - Amount + symbol - To: color dot + full address + etherscan link, or for a contract creation the same sentence as on WaitTx @@ -2007,6 +2130,7 @@ view would leave a wallet one click from deletion. - Amount + symbol, or for a swap the approval screen showed no `Amount` for, the same as on WaitTx: `Unknown (could not be decoded)`, unless its `Token In` is `ETH (native)` + - While "Use Blockscout" is off, the same line under the amount as on WaitTx - To: color dot + full address + etherscan link, or for a contract creation the same sentence as on WaitTx - Error message (dashed border box) @@ -2041,7 +2165,11 @@ view would leave a wallet one click from deletion. AddressToken. The screen describes the transaction on the network it was sent on, whichever network is active: its block, network details, decoded details and raw data are read from that network's Blockscout, the one Settings has for - it, and its etherscan links open that network's explorer. + it, and its etherscan links open that network's explorer. While "Use + Blockscout" is off, Blockscout is not asked. One line then takes the place of + the block, network details, decoded details and raw data: "This screen shows + no block, decoded details, network details or raw data, because "Use + Blockscout" is off in Settings." - **Elements** (grouped into logical blocks using light well containers; field labels are self-explanatory so groups have no headings): - "Transaction" heading, "Back" button @@ -2124,8 +2252,21 @@ view would leave a wallet one click from deletion. already active changes nothing and sends nothing - Ethereum RPC: endpoint URL input + "Save" button (validated against `eth_chainId` before being saved) - - Blockscout API: endpoint URL input + "Save" button (validated against - `/stats` before being saved) + - Blockscout API: + - "Use Blockscout" checkbox, unticked until the user ticks it + - A sentence saying what the setting shows and what it sends. With the + setting on, the wallet sends the Blockscout API the user's addresses + and the hash of each transaction opened + - Endpoint URL input + "Save" button. "Save" asks the URL for `/stats` + and saves the URL only if that request succeeds. The request carries + no user data and is made whether or not "Use Blockscout" is ticked. + - Etherscan: + - "Check recipients on Etherscan" checkbox, unticked until the user + ticks it + - A sentence saying the setting sends each recipient's address to + Etherscan, to check the address for a phishing or scam label + - Each of the two settings applies to every network. Welcome offers the same + two checkboxes - Token Spam Protection: - "Hide fake tokens impersonating a known symbol" checkbox - "Hide tokens with fewer than 1,000 holders" checkbox @@ -2149,6 +2290,10 @@ view would leave a wallet one click from deletion. - Debug: hidden until revealed, then an "Enable debug mode" checkbox that turns on the red banner and verbose logging - **Transitions**: + - Ticking or unticking "Use Blockscout" or "Check recipients on Etherscan" → + stores the setting (no screen change). From then on, every screen drawn + and every balance refresh uses the new setting. Unticking "Use Blockscout" + also drops every token the user has not tracked from every address - "+ Add wallet" → **AddWallet** - "+ Add token" → **SettingsAddToken** - `[recovery phrase]` on an HD wallet → **ShowRecoveryPhrase** @@ -2167,7 +2312,8 @@ view would leave a wallet one click from deletion. nothing and tells the background nothing - Ten clicks on the version → reveals the Debug well (no screen change) - "Back" (or Settings gear again) → previous screen (Home); after a network - switch, the stack is cut first (see the top of Screen Map) + switch or a change to "Use Blockscout", the stack is cut first (see the + top of Screen Map) #### ShowRecoveryPhrase (`show-phrase`) @@ -2324,6 +2470,8 @@ view would leave a wallet one click from deletion. money. The predicate is `addressHoldsFunds()` in `src/popup/views/helpers.js`, unrounded and token-aware. A balance is a warning, never a refusal. + - While "Use Blockscout" is off, a line under the warning: "Only tracked + tokens are listed, because "Use Blockscout" is off in Settings." - The rule that a wallet always keeps at least one address, and that removing the last one means deleting the wallet from Settings - Error line @@ -2456,6 +2604,12 @@ view would leave a wallet one click from deletion. a swap pays only the From address, a `Paid to` line for each other address it pays, or saying that a token stays in the router or who it pays is unknown (see Transaction Decoding) + - A line under each token of the decoded action: "Blockscout's report of + your balances is not used to name this token, because "Use Blockscout" is + off in Settings." While "Use Blockscout" is off, the line shows when the + bundled list and the tracked tokens lack the token's name or decimals. + While the setting is on, Blockscout's report of the user's balances is a + third source of a token's name and decimals (see Unknown token scale). - From: color dot + full address + etherscan link - Contract: color dot + full address + etherscan link, token symbol label if known; for a contract creation, the same sentence as on WaitTx @@ -2535,7 +2689,10 @@ view would leave a wallet one click from deletion. the site puts in the message, except a `Permit`'s token, which is the domain's `verifyingContract`; any those fields do not give is shown as `Unknown`, and the domain, type and message lines still follow. Only typed - data that cannot be read at all is shown as raw text. + data that cannot be read at all is shown as raw text. A token gets the + same line under it as on TxApproval, whenever TxApproval would show that + line. As there, Blockscout's report of the user's balances is a source of + a token's name and decimals only while "Use Blockscout" is on. - Raw data (`personal_sign` and `eth_sign`): the message's hex exactly as the site sent it. The bytes it encodes are what is signed, as an EIP-191 personal message. @@ -2665,17 +2822,36 @@ view would leave a wallet one click from deletion. ### External Services -AutistMask is not a fully self-contained offline tool. It necessarily -communicates with three external services to function as a wallet: +AutistMask is not a fully self-contained offline tool. By default it sends the +user's data only to the Ethereum RPC endpoint. It also asks the CoinDesk price +API for prices, sending token symbols and nothing else. + +Two more services are sent none of the user's data until the user turns on a +setting for each: + +- Blockscout, with "Use Blockscout" +- Etherscan, with "Check recipients on Etherscan" + +Both settings are off in a new install. Welcome and Settings both offer them. +Each setting applies to every network. + +Before each request that sends the user's data to Blockscout or Etherscan, the +wallet reads that service's setting from storage. The wallet reads the setting +again when the answer lands. While the setting is off, no such request is made, +and an answer that lands after the setting went off is dropped. + +Contacted by default: - **Ethereum JSON-RPC endpoint**: The extension needs an Ethereum node to query balances (`eth_getBalance`), read ERC-20 token contracts (`eth_call`), among them the balance of every tracked token at every address (`balanceOf`), 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`. + receipts. Every ENS lookup is made through the RPC endpoint alone. So a name + does not resolve when its resolver says the name is resolved off-chain. 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. @@ -2687,13 +2863,44 @@ communicates with three external services to function as a wallet: - **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 +Sent the user's data only while its setting is on: + +- **Blockscout block-explorer API** ("Use Blockscout"): 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. + - **Used for**: + - transaction history (normal transactions and ERC-20 token transfers), + for Home, AddressDetail and AddressToken + - the balances of the ERC-20 tokens the user has not tracked, for Home, + AddressDetail, Send and DeleteAddress + - the names and decimals of those tokens, for TxApproval, SignApproval, + WaitTx, SuccessTx and ErrorTx + - token holder counts, for spam filtering + - a transaction's block, network details and calldata, for + TransactionDetail + - **Contacted**, while the setting is on: + - on every balance refresh, in the popup and in the background + - when Home, AddressDetail or AddressToken loads its history + - when TransactionDetail opens + - **While the setting is off**: Each screen that would show Blockscout's + data says instead that the setting is off. Only tracked tokens are listed. + - **Saving the URL**: pressing "Save" beside the URL in Settings asks that + URL for `/stats`, whether or not the setting is on. This request carries + no user data. + - **Data sent**: Ethereum addresses, and the hash of each transaction the + user opens. Blockscout receives the user's addresses to query their + transaction history and token balances. No private keys, passwords, or + signing operations are sent. + +- **Etherscan address labels** ("Check recipients on Etherscan"): While the + setting is on, ConfirmTx looks up the recipient's address on Etherscan each + time the screen is shown. The lookup checks for a phishing or scam label, + best-effort. This is a direct page fetch with no API key; the user's browser + makes the request. While the setting is off, ConfirmTx says that no Etherscan + check was made. + - **Data sent**: the recipient's address and nothing else. + +The etherscan links on the screens open a page in the browser when the user +clicks one. They are not requests the wallet makes. What the extension does NOT do: @@ -2702,16 +2909,9 @@ What the extension does NOT do: - 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: - -- **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). +Users who want maximum privacy can point the RPC URL at their own node. With +"Use Blockscout" on, they can point the Blockscout URL at their own instance +too. ### Dependencies @@ -2893,8 +3093,11 @@ logged. - Add multiple addresses within an HD wallet - Manage multiple wallets simultaneously - View ETH balance per address -- View ERC-20 token balances (tokens on the bundled known-token list, tokens - with 1,000 or more holders, and tokens the user adds by contract address) +- View ERC-20 token balances: + - tokens the user adds by contract address + - while "Use Blockscout" is on, tokens on the bundled known-token list and + tokens with 1,000 or more holders +- View transaction history and transaction details, while "Use Blockscout" is on - Send ETH to an address - Send ERC-20 tokens to an address - Receive ETH/tokens (display address, copy to clipboard, QR code) diff --git a/docs/README.md b/docs/README.md index 424d7a2..10c41d4 100644 --- a/docs/README.md +++ b/docs/README.md @@ -57,11 +57,12 @@ analytics, use a portfolio tracker. The wallet is not the place for any of that. - **No NFT galleries or portfolio views.** This is a wallet, not a dashboard. -- **No third-party token list APIs.** Token balances come from the same block - explorer you configure for transaction history, and the extension ships its - own hardcoded list of top ERC-20 contract addresses for symbol-spoofing - detection. Any token you want tracked across all your addresses, you add - yourself by contract address. +- **No third-party token list APIs.** The balances of the tokens you track are + read from your RPC endpoint. While "Use Blockscout" is on, the other tokens + come from the same block explorer you configure for transaction history. The + extension ships its own hardcoded list of top ERC-20 contract addresses for + symbol-spoofing detection. Any token you want tracked across all your + addresses, you add yourself by contract address. - **No backend servers operated by the developer.** Nothing is sent to any server run by AutistMask. Every network destination is listed below. @@ -69,8 +70,13 @@ analytics, use a portfolio tracker. The wallet is not the place for any of that. ## How It Works AutistMask is a browser extension that runs entirely in your browser. It does -not have a backend server. It communicates with five external destinations: -three you configure yourself, and two fixed ones used for scam detection. +not have a backend server. Out of the box it sends your data only to the +Ethereum RPC endpoint. It also asks the CoinDesk price API for prices, sending +token symbols and nothing else. + +Blockscout and Etherscan are sent none of your data until you turn on a setting +for each. The settings are "Use Blockscout" and "Check recipients on Etherscan". +Both are off in a new install. Welcome and Settings both offer them. ### External Services @@ -83,11 +89,14 @@ verify confirmations. The default is a free public RPC endpoint. You can change this in Settings to any Ethereum JSON-RPC endpoint, including your own local node. -When it is contacted: on every balance refresh (every 10 seconds while the popup -is open, every 60 seconds in the background), when you type an ENS name into the -Send screen, when a send is prepared and broadcast, while a pending transaction -is polled for its receipt, and for the reverse ENS lookups used to label -addresses (cached for 12 hours). +When it is contacted: + +- on every balance refresh (every 10 seconds while the popup is open, every 60 + seconds in the background) +- when you review a send to an ENS name +- when a send is prepared and broadcast +- while a pending transaction is polled for its receipt +- for the reverse ENS lookups used to label addresses (cached for 12 hours) What gets sent: standard Ethereum JSON-RPC requests (balance queries, transaction broadcasts, gas estimates, ENS lookups, contract-code checks). Your @@ -96,16 +105,31 @@ addresses are necessarily visible to the RPC provider when querying balances. **Blockscout API** (default: `eth.blockscout.com/api/v2`; `eth-sepolia.blockscout.com/api/v2` on Sepolia) -Used to fetch token balances and transaction history. Blockscout is an -open-source blockchain explorer. AutistMask queries it for your ERC-20 token -balances (including the holder counts used for spam filtering) and your recent -transactions and token transfers. You can change this in Settings to a -self-hosted Blockscout instance. +Blockscout is an open-source blockchain explorer. AutistMask sends it your data +only while "Use Blockscout" is on. AutistMask then asks Blockscout for: -When it is contacted: on every balance refresh, and whenever a screen showing -transaction history is opened. +- your recent transactions and token transfers +- the balances of the tokens you have not tracked, with the holder counts used + for spam filtering +- the block, fees and calldata of a transaction you open -What gets sent: your Ethereum addresses (to look up balances and transactions). +You can change the endpoint in Settings to a self-hosted Blockscout instance. +While the setting is off, each screen that would show Blockscout's data says +instead that the setting is off. Only the tokens you track are listed. When you +turn the setting off, every other token leaves the lists at once. + +When it is contacted, while "Use Blockscout" is on: + +- on every balance refresh +- whenever a screen showing transaction history is opened +- when you open a transaction + +Pressing "Save" beside the Blockscout URL in Settings checks the URL by asking +it for `/stats`. That happens whether or not the setting is on. The request +sends none of your data. + +What gets sent: your Ethereum addresses (to look up balances and transactions) +and the hash of each transaction you open. **CoinDesk CADLI price API** (`data-api.coindesk.com`) @@ -122,16 +146,21 @@ address. **Etherscan address labels** (`etherscan.io`; `sepolia.etherscan.io` on Sepolia) -When you review a send, AutistMask fetches the recipient's public Etherscan -address page and looks for a "Fake_Phishing"/"Phish/Hack" label or a scam -warning, and shows a red warning if it finds one. This is a plain page fetch -with no API key, made by your browser. It is best-effort: if it fails, it is -silently ignored. This endpoint is not user-configurable. +While "Check recipients on Etherscan" is on, AutistMask fetches the recipient's +public Etherscan address page when you review a send. AutistMask looks for a +"Fake_Phishing"/"Phish/Hack" label or a scam warning, and shows a red warning if +it finds one. This is a plain page fetch with no API key, made by your browser. +It is best-effort: if it fails, it is silently ignored. This endpoint is not +user-configurable. -When it is contacted: each time you reach the send confirmation screen. +The setting is off in a new install. While it is off, the confirmation screen +says that no Etherscan check was made. -What gets sent: the recipient address you are about to send to, and your IP -address. Your own addresses are not sent. +When it is contacted: only while "Check recipients on Etherscan" is on, each +time you reach the send confirmation screen. + +What gets sent: the recipient's address and nothing else. As with any request, +Etherscan sees your IP address. Etherscan links shown elsewhere in the UI (on addresses, transactions, and token contracts) are ordinary links. They contact nothing until you click them. @@ -185,6 +214,9 @@ password. 1. Click the AutistMask icon in your browser toolbar. 2. Click "Add wallet" (on first use), or open Settings and click "+ Add wallet". + On first use, the screen with "Add wallet" also offers "Use Blockscout" and + "Check recipients on Etherscan", both off. External Services above says what + each one does. You can change both later in Settings. 3. On the "From Phrase" tab, click the die button to generate a random 12-word recovery phrase. 4. **Write down the recovery phrase and store it safely.** Anyone with these @@ -220,10 +252,14 @@ always produce the same sequence of addresses. ### Adding ERC-20 Tokens -Tokens you hold show up automatically only if they are in the extension's -bundled list of well-known tokens or have at least 1,000 holders; everything -else is treated as spam and hidden. To track a token explicitly (which also -shows it at zero balance), add it by contract address: +The wallet looks for tokens you hold only while "Use Blockscout" is on. Even +then, a token shows up only if it is in the extension's bundled list of +well-known tokens or has at least 1,000 holders. Everything else is treated as +spam and hidden. + +The wallet shows every token you track, whether or not "Use Blockscout" is on. +By default a tracked token is shown at zero balance too (see Display in +Settings). To track a token, add it by contract address: 1. Go to an address detail view (click `[info]` on any address). 2. Click "+ Token". @@ -239,7 +275,9 @@ screen. Tokens can also be added from Settings, under "Tracked Tokens". 1. Click "Send" from the home screen or an address detail view. 2. Select what to send (ETH, or any ERC-20 token with a balance on this address that survives the spam filters). -3. Enter the recipient address or ENS name (e.g. `vitalik.eth`). +3. Enter the recipient address or ENS name (e.g. `vitalik.eth`). AutistMask does + not look up a name resolved off-chain. For such a name, enter the address + instead. 4. Enter the amount, or click "Max" to fill it in: a token's balance, cut to 18 decimal places, or your ETH balance minus the amount reserved for the network fee. @@ -389,8 +427,9 @@ Click the gear icon on the home screen to access settings: that network's defaults if you have not used it before. - **Ethereum RPC**: Change the Ethereum node endpoint. Default is a public RPC. You can use your own node for maximum privacy. -- **Blockscout API**: Change the Blockscout instance used for token balances and - transaction history. You can use a self-hosted instance. +- **Blockscout API**: the "Use Blockscout" checkbox, off until you tick it, and + the URL of the Blockscout instance. You can use a self-hosted instance. +- **Etherscan**: "Check recipients on Etherscan", off until you tick it. - **Token Spam Protection**: Toggle individual scam filters and set the dust transaction threshold. - **Allowed Sites / Denied Sites**: View and manage web3 site permissions. diff --git a/src/background/index.js b/src/background/index.js index f9d6b9d..17adbf6 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -17,7 +17,11 @@ const { applyChainSwitchFields } = require("../shared/chainSwitchFields"); // the same prohibition reported early, not the guarantee. const { getState, updateState } = require("./state"); const { StateUnusableError } = require("../shared/stateSchema"); -const { refreshBalances, getProvider } = require("../shared/balances"); +const { + refreshBalances, + dropUntrackedTokens, + getProvider, +} = require("../shared/balances"); const { debugFetch, log } = require("../shared/log"); const { verifySignedTx, @@ -1386,6 +1390,10 @@ const RECENT_BALANCE_REFRESH_MS = Math.floor(BALANCE_REFRESH_PERIOD_MS / 2); // back over them (https://git.eeqj.de/sneak/AutistMask/issues/577). // lastBalanceRefresh is left as the switch set it, so the next tick refreshes // the new network. +// +// Blockscout may have answered just before the user turned "Use Blockscout" +// off. So the save reads the stored setting again, and drops every token the +// user has not tracked if the setting is off. async function backgroundRefresh() { const s = await getState(); const now = Date.now(); @@ -1423,6 +1431,9 @@ async function backgroundRefresh() { } } } + if (!fresh.useBlockscout) { + dropUntrackedTokens(fresh.wallets, fresh.trackedTokens); + } fresh.lastBalanceRefresh = now; }); } diff --git a/src/popup/index.html b/src/popup/index.html index b41cf9e..9653ed3 100644 --- a/src/popup/index.html +++ b/src/popup/index.html @@ -43,6 +43,38 @@
+ Shows transaction history, transaction details and the + tokens you have not tracked. Sends your addresses, and + the hash of each transaction you open, to the Blockscout + API below. +
- Used to fetch token balances and transaction history. Change this if you run your own Blockscout instance.
+ Sends each recipient's address to Etherscan to check it + for a phishing or scam label. +
+@@ -1291,6 +1382,13 @@ >
A wallet always keeps at least one address. To remove the last one, delete the whole wallet from Settings instead. @@ -1504,6 +1602,16 @@
No wallets yet. Add one to get started.
'; @@ -447,7 +454,7 @@ function init(ctx) { } $("send-to").value = ""; $("send-amount").value = ""; - $("send-token").classList.remove("hidden"); + $("send-token-dropdown").classList.remove("hidden"); $("send-token-static").classList.add("hidden"); renderSendTokenSelect(addr); updateSendBalance(); diff --git a/src/popup/views/send.js b/src/popup/views/send.js index 6556c9f..a699562 100644 --- a/src/popup/views/send.js +++ b/src/popup/views/send.js @@ -10,6 +10,7 @@ const { renderAddressHtml, attachCopyHandlers, goBack, + tokenSourcesByChainId, } = require("./helpers"); const { state, currentAddress } = require("../../shared/state"); let ctx; @@ -141,6 +142,8 @@ function updateToValidation() { } function renderSendTokenSelect(addr) { + // Only Blockscout reports the tokens the user has not tracked. + $("send-tokens-note").classList.toggle("hidden", !!state.useBlockscout); const sel = $("send-token"); sel.innerHTML = ``; const fraudSet = new Set( @@ -185,7 +188,9 @@ function renderSendTokenSelect(addr) { // so the transfer is encoded with the number the user read rather than with // whatever the contract answers at signing time (see // src/shared/transferAmount.js). balances.js is formatting one explorer row at -// fetch time and cannot consult a state it is in the middle of replacing. +// fetch time and cannot consult a state it is in the middle of replacing. As on +// the approval screens, tokenSourcesByChainId() gives the wallets only while +// "Use Blockscout" is on. // // The two resolutions can therefore differ, and where they do, the stored // `balance` is a quantity computed at a scale this screen has just declined to @@ -198,10 +203,7 @@ function tokenBalanceAndDecimals(addr, token) { const tb = (addr.tokenBalances || []).find( (t) => t.address.toLowerCase() === token.toLowerCase(), ); - const tokenDecimals = resolveTokenDecimals(token, { - trackedTokens: state.trackedTokens, - wallets: state.wallets, - }); + const tokenDecimals = resolveTokenDecimals(token, tokenSourcesByChainId()); if (!tb) return { tokenBalance: "0", tokenDecimals }; if (tokenDecimals === null) return { tokenBalance: null, tokenDecimals }; return { tokenBalance: tb.balance ?? null, tokenDecimals }; @@ -401,8 +403,11 @@ function init(_ctx) { try { const provider = getProvider(state.rpcUrl, state.networkId); const resolved = await provider.resolveName(to); + // getProvider() does not look up a name resolved off-chain, + // so such a name comes back with no address too. The message + // covers both, in the one line the flash area shows. if (!resolved) { - showFlash("That ENS name has no address."); + showFlash("That ENS name has no on-chain address."); return; } resolvedTo = resolved; @@ -426,7 +431,7 @@ function init(_ctx) { }); $("btn-send-back").addEventListener("click", () => { - $("send-token").classList.remove("hidden"); + $("send-token-dropdown").classList.remove("hidden"); $("send-token-static").classList.add("hidden"); goBack(); }); diff --git a/src/popup/views/settings.js b/src/popup/views/settings.js index 4d0d53a..1162a64 100644 --- a/src/popup/views/settings.js +++ b/src/popup/views/settings.js @@ -17,6 +17,7 @@ const { const { state, saveState, currentNetwork } = require("../../shared/state"); const { onChainSwitch } = require("../../shared/chainSwitch"); const { restorableStack } = require("../../shared/persistedState"); +const { dropUntrackedTokens } = require("../../shared/balances"); const { resetRenderedViews, markViewRendered } = require("../viewRouter"); const { log, @@ -201,6 +202,10 @@ function show() { $("settings-rpc").value = state.rpcUrl; $("settings-blockscout").value = state.blockscoutUrl; $("settings-network").value = state.networkId; + // Set on every show, not only at init: Welcome can change these two. + $("settings-use-blockscout").checked = state.useBlockscout; + $("settings-check-recipients-on-etherscan").checked = + state.checkRecipientsOnEtherscan; renderTrackedTokens(); renderSiteLists(); renderWalletListSettings(); @@ -247,16 +252,20 @@ async function renderSiteLists() { // A network switch, made by the user here or saved by the background once the // user approved a site's request (src/popup/index.js), leaves every screen on -// the page showing the previous network. These two leave the page as README's -// Screen Map describes, the first before the switch is saved, so that save -// keeps the cut for a popup closed straight after, the second once `state` -// holds the new network. +// the page showing the previous network. Two functions leave the page as +// README's Screen Map describes: +// +// - cutViewStack(), before the switch is saved, so that save keeps the cut for +// a popup closed straight after +// - showNewSettings(), once `state` holds the new network // // The stack is cut at the first screen that cannot be rendered from saved // state, such as Send or AddToken, and at AddressToken unless it shows the // native token: any other token there was picked on the previous network, so // it is no longer selected either. Without a wallet the stack starts at // Welcome, which that cut would drop, and Welcome shows nothing from a network. +// A change to "Use Blockscout" cuts the stack the same way (setUseBlockscout() +// below). function cutViewStack() { if (!state.hasWallet) return; if (state.selectedToken !== "ETH") { @@ -269,18 +278,40 @@ function cutViewStack() { state.viewStack = restorableStack(state.viewStack, state.currentView); } -// Every screen counts as not yet rendered, so "Back" renders the one it lands -// on for the new network. Settings on top is brought up to date in place; -// any other screen is drawn again as a reopened popup draws it, which is Home -// for one that cannot be rendered from saved state. Without a wallet any other -// screen is Welcome or AddWallet, which show nothing from a network. -function showNewNetwork() { +// Sets "Use Blockscout" to `on` on this page. It is called inside a save, +// holding the lock, for the user's change here or another page's +// (followStoredSettings() in src/popup/index.js). +// +// Send's token dropdown was filled under the old setting, and Back cannot draw +// Send again. So the stack is cut, as for a network switch. Turning the setting +// off also drops every token the user has not tracked. +function setUseBlockscout(on) { + state.useBlockscout = on; + cutViewStack(); + if (!on) dropUntrackedTokens(state.wallets, state.trackedTokens); +} + +// Draws the page again after a network switch, or after another page changed +// "Use Blockscout" or "Check recipients on Etherscan". Every screen counts as +// not yet rendered, so "Back" renders again any screen that a reopened popup +// can render. Settings on top is brought up to date in place. Any other screen +// is drawn again as a reopened popup draws it, which is Home for one that +// cannot be rendered from saved state. Without a wallet any other screen is +// Welcome or AddWallet. Back only unhides Welcome, so its two checkboxes are +// set here. +function showNewSettings() { resetRenderedViews(); + $("welcome-use-blockscout").checked = state.useBlockscout; + $("welcome-check-recipients-on-etherscan").checked = + state.checkRecipientsOnEtherscan; if (state.currentView === "settings") { markViewRendered("settings"); $("settings-network").value = state.networkId; $("settings-rpc").value = state.rpcUrl; $("settings-blockscout").value = state.blockscoutUrl; + $("settings-use-blockscout").checked = state.useBlockscout; + $("settings-check-recipients-on-etherscan").checked = + state.checkRecipientsOnEtherscan; renderTrackedTokens(); } else if (state.hasWallet) { ctx.restoreView(); @@ -363,6 +394,35 @@ function init(_ctx) { showFlash("Saved."); }); + // Each change is made inside the save, holding the lock. So + // followStoredSettings() in src/popup/index.js never mistakes this page's + // unsaved change for one that another page saved. + // + // The screens under Settings were drawn under the old setting. So each + // change makes every screen but Settings count as not yet drawn, as a + // network switch does. Back then draws again any screen that a reopened + // popup can draw. Back only unhides Welcome, so its boxes are set here too. + $("settings-use-blockscout").addEventListener("change", async () => { + const on = $("settings-use-blockscout").checked; + await saveState(() => setUseBlockscout(on)); + $("welcome-use-blockscout").checked = on; + resetRenderedViews(); + markViewRendered("settings"); + }); + + $("settings-check-recipients-on-etherscan").addEventListener( + "change", + async () => { + const on = $("settings-check-recipients-on-etherscan").checked; + await saveState(() => { + state.checkRecipientsOnEtherscan = on; + }); + $("welcome-check-recipients-on-etherscan").checked = on; + resetRenderedViews(); + markViewRendered("settings"); + }, + ); + const networkSelect = $("settings-network"); networkSelect.addEventListener("change", async () => { // Whether `state` already has the network picked is asked only once @@ -376,7 +436,7 @@ function init(_ctx) { // Open pages are told by the background, as after a site's approved // switch request. notify({ type: "AUTISTMASK_NETWORK_CHANGED", chainId: net.chainId }); - showNewNetwork(); + showNewSettings(); showFlash("Switched to " + net.name + "."); }); @@ -516,5 +576,6 @@ module.exports = { show, renderSiteLists, cutViewStack, - showNewNetwork, + setUseBlockscout, + showNewSettings, }; diff --git a/src/popup/views/transactionDetail.js b/src/popup/views/transactionDetail.js index 832211f..84ca5fe 100644 --- a/src/popup/views/transactionDetail.js +++ b/src/popup/views/transactionDetail.js @@ -26,6 +26,7 @@ const { state } = require("../../shared/state"); const { nativeCurrencyByChainId } = require("../../shared/networks"); const { formatEther, formatUnits } = require("ethers"); const { log, debugFetch } = require("../../shared/log"); +const { storedSettingIsOn } = require("../../shared/storedSettings"); const { decodeCalldata } = require("./approval"); /** @@ -172,7 +173,8 @@ function render() { } } - // Hide calldata and raw data sections; always fetch full tx details + // Hide the calldata and raw data sections. loadFullTxDetails() shows them + // once it has their data. const calldataSection = $("tx-detail-calldata-section"); if (calldataSection) calldataSection.classList.add("hidden"); const rawDataSection = $("tx-detail-rawdata-section"); @@ -191,6 +193,12 @@ function render() { if (el) el.classList.add("hidden"); } + // The block, network details, decoded details and raw data come from + // Blockscout. While the setting is off, this line says why none is shown. + $("tx-detail-blockscout-note").classList.toggle( + "hidden", + !!state.useBlockscout, + ); loadFullTxDetails(tx.hash, tx.chainId); const isoStr = isoDate(tx.timestamp); @@ -303,7 +311,12 @@ function populateOnChainDetails(txData, chainId) { } } +// Reads the block, network details and calldata from Blockscout, and decodes +// the calldata. While the stored "Use Blockscout" is off, Blockscout is not +// asked. An answer that lands after the setting went off is dropped +// (src/shared/storedSettings.js). Either way none of them is shown. async function loadFullTxDetails(txHash, chainId) { + if (!(await storedSettingIsOn("useBlockscout"))) return; const section = $("tx-detail-calldata-section"); const actionEl = $("tx-detail-calldata-action"); const detailsEl = $("tx-detail-calldata-details"); @@ -319,6 +332,7 @@ async function loadFullTxDetails(txHash, chainId) { ); if (!resp.ok) return; const txData = await resp.json(); + if (!(await storedSettingIsOn("useBlockscout"))) return; // Populate on-chain detail fields (block, nonce, gas, fee) populateOnChainDetails(txData, chainId); diff --git a/src/popup/views/txStatus.js b/src/popup/views/txStatus.js index e55913f..3653b32 100644 --- a/src/popup/views/txStatus.js +++ b/src/popup/views/txStatus.js @@ -14,6 +14,7 @@ const { displaySymbol, clearViewStack, tokenLabel, + blockscoutOffTokenNoteHtml, reviewedNetworkProvider, } = require("./helpers"); const { state } = require("../../shared/state"); @@ -108,6 +109,10 @@ function startWait(txInfo, txHash, broadcastTime, pollNow) { } $("wait-tx-summary").textContent = txInfo.amount + (symbol ? " " + symbol : ""); + $("wait-tx-token-note").innerHTML = blockscoutOffTokenNoteHtml( + txInfo.token, + txInfo.chainId, + ); $("wait-tx-to").innerHTML = toAddressHtml(txInfo.to, txInfo.chainId); $("wait-tx-hash").innerHTML = txHashHtml(txHash, txInfo.chainId); attachCopyHandlers("view-wait-tx"); @@ -215,9 +220,10 @@ function showWait(txInfo, txHash) { // init and leaving wait-tx on screen with no back control. A non-numeric // broadcastTime leaves an unexitable wait counting "NaNs". txInfo.chainId picks // the node the receipt is asked of and the explorer the links open, so it has -// to name a network. txInfo.token and txInfo.tokenSymbol are deliberately -// unchecked: they are compared and coalesced rather than dereferenced, and -// tokenSymbol is null for ETH. +// to name a network. txInfo.token and txInfo.tokenSymbol are not checked here. +// blockscoutOffTokenNoteHtml() shows no line for a token that is not text. +// Everything else only compares and coalesces them, and tokenSymbol is null for +// ETH. function restoreWait() { const d = state.viewData; if (!d || !d.pendingWait) return false; @@ -282,6 +288,7 @@ function decodedDetailsHtml(decoded, chainId) { if (d.isToken) { const sym = tokenLabel(d.address, chainId) || "Unknown token"; html += `