1 Commits
Author SHA1 Message Date
sneak 4910ad02fc fix: Blockscout and Etherscan lookups are off until the user turns them on (closes #668)
check / check (push) Canceled after 0s
e2e / e2e-chrome (push) Canceled after 0s
e2e / e2e-firefox (push) Canceled after 0s
Out of the box the wallet sent the user's addresses to Blockscout and each
recipient to Etherscan. Two settings, "Use Blockscout" and "Check recipients on
Etherscan", now gate those requests. Both are off in a new install and offered
unticked on Welcome and in Settings. While one is off, the screens that relied
on it say so in a plain line. Each gated request reads its setting from storage
before it asks and when the answer lands, and drops a late answer. An open popup
takes another page's change and draws its screens again. While "Use Blockscout"
is off, no address keeps an untracked token, and no screen names a token from
Blockscout's report. ENS lookups ask only the RPC endpoint.

Model: opus-5-5
2026-10-26 10:06:20 +00:00
8 changed files with 39 additions and 43 deletions

No files matched your search

+13 -12
View File
@@ -2599,12 +2599,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 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 it pays, or saying that a token stays in the router or who it pays is
unknown (see Transaction Decoding) unknown (see Transaction Decoding)
- A line under each token of the decoded action: "Blockscout's report of - While "Use Blockscout" is off, a line under each token in the decoded
your balances is not used to name this token, because "Use Blockscout" is action whose name or decimals are missing from the bundled list and the
off in Settings." It shows while "Use Blockscout" is off and the token's tracked tokens: "Blockscout's report of your balances is not used to name
name or decimals are missing from the bundled list and the tracked tokens. this token, because "Use Blockscout" is off in Settings." Blockscout's
Blockscout's report of the user's balances gives a token's name and report of the user's balances gives a token's name and decimals only while
decimals only while the setting is on (see Unknown token scale). the setting is on (see Unknown token scale).
- From: color dot + full address + etherscan link - From: color dot + full address + etherscan link
- Contract: color dot + full address + etherscan link, token symbol label if - Contract: color dot + full address + etherscan link, token symbol label if
known; for a contract creation, the same sentence as on WaitTx known; for a contract creation, the same sentence as on WaitTx
@@ -2684,10 +2684,11 @@ view would leave a wallet one click from deletion.
the site puts in the message, except a `Permit`'s token, which is the 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 domain's `verifyingContract`; any those fields do not give is shown as
`Unknown`, and the domain, type and message lines still follow. Only typed `Unknown`, and the domain, type and message lines still follow. Only typed
data that cannot be read at all is shown as raw text. A token gets the data that cannot be read at all is shown as raw text. While "Use
same line under it as on TxApproval, in the same case. As there, Blockscout" is off, a token whose name or decimals are missing from the
Blockscout's report of the user's balances gives a token's name and bundled list and the tracked tokens has the same line under it as on
decimals only while the setting is on. TxApproval. As there, Blockscout's report of the user's balances gives a
token's name and decimals only while the setting is on.
- Raw data (`personal_sign` and `eth_sign`): the message's hex exactly as - 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 the site sent it. The bytes it encodes are what is signed, as an EIP-191
personal message. personal message.
@@ -2830,8 +2831,8 @@ settings:
Both settings are on the Welcome screen and in Settings. Both are off in a new Both settings are on the Welcome screen and in Settings. Both are off in a new
install. Each setting applies to every network. install. Each setting applies to every network.
Before each of these requests, the wallet reads its setting from storage. It A request that either setting gates reads the setting from storage before it
reads it again when the answer lands. While the setting is off, nothing is asks, and again when the answer lands. While the setting is off, nothing is
asked, and an answer that lands is dropped. asked, and an answer that lands is dropped.
Contacted by default: Contacted by default:
+3 -3
View File
@@ -11,7 +11,7 @@ const {
onViewLeave, onViewLeave,
formatFee, formatFee,
tokenLabel, tokenLabel,
blockscoutOffTokenNoteHtml, tokenLookupOffHtml,
tokenSourcesByChainId, tokenSourcesByChainId,
} = require("./helpers"); } = require("./helpers");
const { state, saveState, currentNetwork } = require("../../shared/state"); const { state, saveState, currentNetwork } = require("../../shared/state");
@@ -402,7 +402,7 @@ function showTxApproval(details) {
if (d.address) { if (d.address) {
if (d.isToken) { if (d.isToken) {
detailsHtml += `<div class="font-bold">${escapeHtml(tokenLabel(d.address, approvedTx.chainId) || "Unknown token")}</div>`; detailsHtml += `<div class="font-bold">${escapeHtml(tokenLabel(d.address, approvedTx.chainId) || "Unknown token")}</div>`;
detailsHtml += blockscoutOffTokenNoteHtml( detailsHtml += tokenLookupOffHtml(
d.address, d.address,
approvedTx.chainId, approvedTx.chainId,
); );
@@ -657,7 +657,7 @@ function permitWarningHtml(types, primaryType, domain, message) {
html += `<div class="mb-2"><div>Token</div>`; html += `<div class="mb-2"><div>Token</div>`;
if (token) { if (token) {
html += `<div>${escapeHtml(tokenLabel(token) || "Unknown token")}</div>`; html += `<div>${escapeHtml(tokenLabel(token) || "Unknown token")}</div>`;
html += blockscoutOffTokenNoteHtml(token); html += tokenLookupOffHtml(token);
html += approvalAddressHtml(token); html += approvalAddressHtml(token);
} else { } else {
html += `<div>Unknown</div>`; html += `<div>Unknown</div>`;
+2 -2
View File
@@ -332,7 +332,7 @@ function tokenSourcesByChainId(chainId) {
// active network when none is given. "ETH" and null are no contract, and get // active network when none is given. "ETH" and null are no contract, and get
// "". A token that is not text also gets "": WaitTx and ErrorTx pass a token // "". A token that is not text also gets "": WaitTx and ErrorTx pass a token
// restored from storage unchecked. // restored from storage unchecked.
function blockscoutOffTokenNoteHtml(token, chainId) { function tokenLookupOffHtml(token, chainId) {
if (typeof token !== "string") return ""; if (typeof token !== "string") return "";
if (state.useBlockscout || !token || token === "ETH") return ""; if (state.useBlockscout || !token || token === "ETH") return "";
const sources = tokenSourcesByChainId(chainId); const sources = tokenSourcesByChainId(chainId);
@@ -829,7 +829,7 @@ module.exports = {
unknownableAmount, unknownableAmount,
nativeCurrency, nativeCurrency,
tokenSourcesByChainId, tokenSourcesByChainId,
blockscoutOffTokenNoteHtml, tokenLookupOffHtml,
tokenLabel, tokenLabel,
formatFee, formatFee,
addressDotHtml, addressDotHtml,
+6 -10
View File
@@ -14,7 +14,7 @@ const {
displaySymbol, displaySymbol,
clearViewStack, clearViewStack,
tokenLabel, tokenLabel,
blockscoutOffTokenNoteHtml, tokenLookupOffHtml,
reviewedNetworkProvider, reviewedNetworkProvider,
} = require("./helpers"); } = require("./helpers");
const { state } = require("../../shared/state"); const { state } = require("../../shared/state");
@@ -109,7 +109,7 @@ function startWait(txInfo, txHash, broadcastTime, pollNow) {
} }
$("wait-tx-summary").textContent = $("wait-tx-summary").textContent =
txInfo.amount + (symbol ? " " + symbol : ""); txInfo.amount + (symbol ? " " + symbol : "");
$("wait-tx-token-note").innerHTML = blockscoutOffTokenNoteHtml( $("wait-tx-token-note").innerHTML = tokenLookupOffHtml(
txInfo.token, txInfo.token,
txInfo.chainId, txInfo.chainId,
); );
@@ -221,9 +221,8 @@ function showWait(txInfo, txHash) {
// broadcastTime leaves an unexitable wait counting "NaNs". txInfo.chainId picks // 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 // 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 not checked here. // to name a network. txInfo.token and txInfo.tokenSymbol are not checked here.
// blockscoutOffTokenNoteHtml() shows no line for a token that is not text. // tokenLookupOffHtml() shows no line for a token that is not text. Everything
// Everything else only compares and coalesces them, and tokenSymbol is null for // else only compares and coalesces them, and tokenSymbol is null for ETH.
// ETH.
function restoreWait() { function restoreWait() {
const d = state.viewData; const d = state.viewData;
if (!d || !d.pendingWait) return false; if (!d || !d.pendingWait) return false;
@@ -288,7 +287,7 @@ function decodedDetailsHtml(decoded, chainId) {
if (d.isToken) { if (d.isToken) {
const sym = tokenLabel(d.address, chainId) || "Unknown token"; const sym = tokenLabel(d.address, chainId) || "Unknown token";
html += `<div class="font-bold">${escapeHtml(sym)}</div>`; html += `<div class="font-bold">${escapeHtml(sym)}</div>`;
html += blockscoutOffTokenNoteHtml(d.address, chainId); html += tokenLookupOffHtml(d.address, chainId);
html += toAddressHtml(d.address, chainId); html += toAddressHtml(d.address, chainId);
} else { } else {
html += toAddressHtml(d.address, chainId); html += toAddressHtml(d.address, chainId);
@@ -366,10 +365,7 @@ function renderError() {
if (!d || !d.message) return; if (!d || !d.message) return;
$("error-tx-summary").textContent = $("error-tx-summary").textContent =
d.amount + (d.symbol ? " " + d.symbol : ""); d.amount + (d.symbol ? " " + d.symbol : "");
$("error-tx-token-note").innerHTML = blockscoutOffTokenNoteHtml( $("error-tx-token-note").innerHTML = tokenLookupOffHtml(d.token, d.chainId);
d.token,
d.chainId,
);
$("error-tx-to").innerHTML = toAddressHtml(d.to, d.chainId); $("error-tx-to").innerHTML = toAddressHtml(d.to, d.chainId);
$("error-tx-message").textContent = d.message; $("error-tx-message").textContent = d.message;
+4 -4
View File
@@ -1,10 +1,10 @@
// Reads "Use Blockscout" or "Check recipients on Etherscan" from the stored // Reads "Use Blockscout" or "Check recipients on Etherscan" from the stored
// profile. // profile.
// //
// Before each request to Blockscout or Etherscan, the wallet reads its setting // A request that either setting gates reads it here twice: before it asks, and
// from storage. It reads it again when the answer lands. While the setting is // again when the answer lands. While the setting is off, nothing is asked, and
// off, nothing is asked, and an answer that lands is dropped. A page's own copy // an answer that lands is dropped. A page's own copy of the setting is not
// of the setting is not used, because another page may have just changed it. // used, because another page may have just changed it.
const { storageGet } = require("./browserApi"); const { storageGet } = require("./browserApi");
const { normalizePersisted } = require("./persistedState"); const { normalizePersisted } = require("./persistedState");
+2 -2
View File
@@ -2,8 +2,8 @@
// TxApproval name a token, and scale its amount, from the bundled list and the // TxApproval name a token, and scale its amount, from the bundled list and the
// tracked tokens alone (https://git.eeqj.de/sneak/AutistMask/issues/668). A // tracked tokens alone (https://git.eeqj.de/sneak/AutistMask/issues/668). A
// token they lack gets a line saying Blockscout's report of the user's balances // token they lack gets a line saying Blockscout's report of the user's balances
// is not used to name it. While the setting is on, Blockscout's report of the // is not used to name it. While the setting is on, what Blockscout reported for
// user's holding names and scales the token, as before. // a token the user holds names and scales it, as before.
// //
// Booted through the real popup entry point, as the approval window or as a // Booted through the real popup entry point, as the approval window or as a
// popup reopened onto SuccessTx, WaitTx or ErrorTx. The vault is mocked so that // popup reopened onto SuccessTx, WaitTx or ErrorTx. The vault is mocked so that
+6 -6
View File
@@ -7,10 +7,10 @@
// the Blockscout URL in Settings. It asks that URL only for `/stats` and sends // the Blockscout URL in Settings. It asks that URL only for `/stats` and sends
// none of the user's data. // none of the user's data.
// //
// Before any other request to Blockscout or Etherscan, the wallet reads its // A request that either setting gates reads the stored setting, not the page's
// setting from storage, not the page's copy. It reads it again when the answer // copy, before it asks and again when the answer lands. So a change another
// lands. So a change another page saved applies at once. An open page also // page saved applies to it at once. An open page also takes such a change and
// takes such a change and draws its screens again. // draws its screens again.
// //
// Only Blockscout reports the tokens the user has not tracked. While "Use // Only Blockscout reports the tokens the user has not tracked. While "Use
// Blockscout" is off, no address keeps them and no screen lists them. // Blockscout" is off, no address keeps them and no screen lists them.
@@ -583,8 +583,8 @@ describe("the popup", () => {
} }
// The token ids of the options created after the first `from` elements. // The token ids of the options created after the first `from` elements.
// The page stub keeps no children, so this reads the options for Send's // The page stub keeps no children, so this is where the options Send's
// dropdown from the elements the page created. // dropdown was filled with are read.
function optionsSince(page, from) { function optionsSince(page, from) {
return page.document.created return page.document.created
.slice(from) .slice(from)
+3 -4
View File
@@ -5,10 +5,9 @@
// (https://git.eeqj.de/sneak/AutistMask/issues/668). With the flag set, such a // (https://git.eeqj.de/sneak/AutistMask/issues/668). With the flag set, such a
// name does not resolve. // name does not resolve.
// //
// The test builds its provider with getProvider() and runs it against a fake // The provider getProvider() builds runs against a fake JSON-RPC node. Its
// JSON-RPC node. Its answers go through ethers' own request hook, which also // answers go through ethers' own request hook, which also records the address
// records the address of every request ethers makes, the off-chain fetch // of every request ethers makes, the off-chain fetch included.
// included.
const { FetchRequest, Interface } = require("ethers"); const { FetchRequest, Interface } = require("ethers");
const { getProvider } = require("../src/shared/balances"); const { getProvider } = require("../src/shared/balances");