1 Commits
Author SHA1 Message Date
sneak ceb6a3195b 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 for history
and token lookups. It sent each recipient to Etherscan for its label. Two
settings, "Use Blockscout" and "Check recipients on Etherscan", now gate those
requests where they are made. 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 naming the setting. Unticking "Use Blockscout"
drops every untracked token from every address. After either changes in
Settings, Back draws again any screen a reopened popup can draw. ENS lookups
ask only the RPC endpoint.

Model: opus-5-5
2026-10-26 01:39:30 +00:00
24 changed files with 177 additions and 375 deletions

No files matched your search

+6 -11
View File
@@ -15,7 +15,7 @@ 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 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 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 only to the Ethereum RPC endpoint. The user can change that endpoint in
Settings. It defaults to a public node. Settings; it defaults to a public node.
Out of the box the extension contacts two services: Out of the box the extension contacts two services:
@@ -1434,15 +1434,13 @@ figures while Blockscout fails.
The refresh asks Blockscout only while the user has "Use Blockscout" on. The 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: 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 - Only tracked tokens are listed. Turning the setting off in Settings drops
token from every address at once. 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 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 - A tracked token's balance uses the decimals from the bundled list, or else the
decimals saved when the token was tracked. decimals saved when the token was tracked.
Each refresh, in the popup or the background, reads the stored setting when it A refresh that started before the setting changed saves nothing when it returns.
starts. If the setting is off when the refresh saves, 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 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 its balance is nonzero and it is in the bundled known-token list or has 1,000 or
@@ -1641,10 +1639,6 @@ 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 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. change to "Check recipients on Etherscan" leaves the stack as it is.
Any other popup open meanwhile takes either change from the stored record, as it
takes a network switch (below). It then draws its screen on top again under the
new setting.
A network switch leaves every screen on the page showing the previous network. 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 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 saves it. Any other popup open meanwhile takes the switch from the stored
@@ -2852,7 +2846,8 @@ Sent the user's data only while the user has turned the setting on:
- **Data sent**: the recipient's address and nothing else. - **Data sent**: the recipient's address and nothing else.
The etherscan links on the screens open a page in the browser when the user The etherscan links on the screens open a page in the browser when the user
clicks one. They are not requests the wallet makes. clicks one. They are not requests the wallet makes. Nothing is fetched from them
until the user clicks.
What the extension does NOT do: What the extension does NOT do:
+9 -10
View File
@@ -75,8 +75,8 @@ Ethereum RPC endpoint. It also asks the CoinDesk price API for prices, sending
token symbols and nothing else. token symbols and nothing else.
Blockscout and Etherscan are sent none of your data until you turn them on. Blockscout and Etherscan are sent none of your data until you turn them on.
Their settings are "Use Blockscout" and "Check recipients on Etherscan". Both Their settings are "Use Blockscout" and "Check recipients on Etherscan", on the
are on the Welcome screen and in Settings. Both are off in a new install. Welcome screen and in Settings. Both are off in a new install.
### External Services ### External Services
@@ -143,14 +143,13 @@ address.
**Etherscan address labels** (`etherscan.io`; `sepolia.etherscan.io` on Sepolia) **Etherscan address labels** (`etherscan.io`; `sepolia.etherscan.io` on Sepolia)
When you review a send, AutistMask fetches the recipient's public Etherscan Off until you turn on "Check recipients on Etherscan". When you review a send,
address page. It looks for a "Fake_Phishing"/"Phish/Hack" label or a scam AutistMask fetches the recipient's public Etherscan address page. It looks for a
warning, and shows a red warning if it finds one. This is a plain page fetch "Fake_Phishing"/"Phish/Hack" label or a scam warning, and shows a red warning if
with no API key, made by your browser. It is best-effort: if it fails, it is it finds one. This is a plain page fetch with no API key, made by your browser.
silently ignored. This endpoint is not user-configurable. It is best-effort: if it fails, it is silently ignored. This endpoint is not
user-configurable. While it is off, the confirmation screen says that no
The check is off until you turn on "Check recipients on Etherscan". While the Etherscan check was made.
setting is off, the confirmation screen says that no Etherscan check was made.
When it is contacted: only while "Check recipients on Etherscan" is on, each When it is contacted: only while "Check recipients on Etherscan" is on, each
time you reach the send confirmation screen. time you reach the send confirmation screen.
+5 -11
View File
@@ -17,11 +17,7 @@ const { applyChainSwitchFields } = require("../shared/chainSwitchFields");
// the same prohibition reported early, not the guarantee. // the same prohibition reported early, not the guarantee.
const { getState, updateState } = require("./state"); const { getState, updateState } = require("./state");
const { StateUnusableError } = require("../shared/stateSchema"); const { StateUnusableError } = require("../shared/stateSchema");
const { const { refreshBalances, getProvider } = require("../shared/balances");
refreshBalances,
dropUntrackedTokens,
getProvider,
} = require("../shared/balances");
const { debugFetch, log } = require("../shared/log"); const { debugFetch, log } = require("../shared/log");
const { const {
verifySignedTx, verifySignedTx,
@@ -1391,9 +1387,9 @@ const RECENT_BALANCE_REFRESH_MS = Math.floor(BALANCE_REFRESH_PERIOD_MS / 2);
// lastBalanceRefresh is left as the switch set it, so the next tick refreshes // lastBalanceRefresh is left as the switch set it, so the next tick refreshes
// the new network. // the new network.
// //
// The refresh asks Blockscout only if "Use Blockscout" is on when it starts. // Nothing is applied either when "Use Blockscout" stored NOW is not the one the
// The user may turn it off while the refresh runs. So when "Use Blockscout" // refresh ran with. Turning it off dropped the tokens the user has not tracked
// stored NOW is off, the save drops every token the user has not tracked. // (src/popup/views/settings.js), and this refresh must not bring them back.
async function backgroundRefresh() { async function backgroundRefresh() {
const s = await getState(); const s = await getState();
const now = Date.now(); const now = Date.now();
@@ -1419,6 +1415,7 @@ async function backgroundRefresh() {
await updateState((fresh) => { await updateState((fresh) => {
if (fresh.networkId !== s.networkId) return; if (fresh.networkId !== s.networkId) return;
if (fresh.useBlockscout !== s.useBlockscout) return;
for (const wallet of fresh.wallets) { for (const wallet of fresh.wallets) {
for (const addr of wallet.addresses || []) { for (const addr of wallet.addresses || []) {
const got = refreshed.get(String(addr.address).toLowerCase()); const got = refreshed.get(String(addr.address).toLowerCase());
@@ -1432,9 +1429,6 @@ async function backgroundRefresh() {
} }
} }
} }
if (!fresh.useBlockscout) {
dropUntrackedTokens(fresh.wallets, fresh.trackedTokens);
}
fresh.lastBalanceRefresh = now; fresh.lastBalanceRefresh = now;
}); });
} }
+25 -56
View File
@@ -11,7 +11,7 @@ const {
const { StateUnusableError } = require("../shared/stateSchema"); const { StateUnusableError } = require("../shared/stateSchema");
const { log, setRuntimeDebug } = require("../shared/log"); const { log, setRuntimeDebug } = require("../shared/log");
const { refreshPrices } = require("../shared/prices"); const { refreshPrices } = require("../shared/prices");
const { refreshBalances, dropUntrackedTokens } = require("../shared/balances"); const { refreshBalances } = require("../shared/balances");
const { clearPopupCaches } = require("../shared/chainSwitch"); const { clearPopupCaches } = require("../shared/chainSwitch");
const { const {
$, $,
@@ -71,28 +71,25 @@ let refreshInFlight = false;
// the recovery screen: there is no profile left to refresh. // the recovery screen: there is no profile left to refresh.
let refreshTimer = null; let refreshTimer = null;
// The refresh first takes any change to the network or to "Use Blockscout"
// saved elsewhere (followStoredSettings() below). So it asks Blockscout only if
// the stored setting is on when it starts.
//
// The refresh writes into a copy of the wallets. Inside the save, under the // The refresh writes into a copy of the wallets. Inside the save, under the
// lock, its results are copied onto the address records in `state` only if the // lock, its results are copied onto the address records in `state` only if the
// network it ran on is still the active one, on this page and in the stored // network it ran on is still the active one, on this page and in the stored
// record; the background's switch, after the user approves a site's request, // record; the background's switch, after the user approves a site's request,
// reaches this page only after the stored record (followStoredSettings()). // reaches this page only after the stored record (followNetworkSwitch() below).
// A switch while the refresh ran cleared every address's balances // A switch while the refresh ran cleared every address's balances
// (applyChainSwitchFields() in src/shared/chainSwitchFields.js), and the // (applyChainSwitchFields() in src/shared/chainSwitchFields.js), and the
// previous network's must not come back over them // previous network's must not come back over them
// (https://git.eeqj.de/sneak/AutistMask/issues/577). A dropped refresh is not // (https://git.eeqj.de/sneak/AutistMask/issues/577). The same holds when "Use
// recorded as done. Results are copied by address, so an address added or // Blockscout" changed while the refresh ran. Turning it off dropped the tokens
// removed meanwhile is left as it is. If the stored "Use Blockscout" is off // the user has not tracked (views/settings.js), and this refresh must not bring
// when the refresh saves, the save drops every token the user has not tracked. // them back. A dropped refresh is not recorded as done. Results are copied by
// address, so an address added or removed meanwhile is left as it is.
async function doRefreshAndRender() { async function doRefreshAndRender() {
if (refreshInFlight) return; if (refreshInFlight) return;
refreshInFlight = true; refreshInFlight = true;
try { try {
await followStoredSettings();
const networkId = state.networkId; const networkId = state.networkId;
const useBlockscout = state.useBlockscout;
const wallets = structuredClone(state.wallets); const wallets = structuredClone(state.wallets);
await Promise.all([ await Promise.all([
refreshPrices(), refreshPrices(),
@@ -100,7 +97,7 @@ async function doRefreshAndRender() {
wallets, wallets,
state.rpcUrl, state.rpcUrl,
state.blockscoutUrl, state.blockscoutUrl,
state.useBlockscout, useBlockscout,
state.trackedTokens, state.trackedTokens,
networkId, networkId,
pageClosed.signal, pageClosed.signal,
@@ -115,6 +112,8 @@ async function doRefreshAndRender() {
await saveState((stored) => { await saveState((stored) => {
if (state.networkId !== networkId) return false; if (state.networkId !== networkId) return false;
if (stored.networkId !== networkId) return false; if (stored.networkId !== networkId) return false;
if (state.useBlockscout !== useBlockscout) return false;
if (stored.useBlockscout !== useBlockscout) return false;
for (const wallet of state.wallets) { for (const wallet of state.wallets) {
for (const addr of wallet.addresses) { for (const addr of wallet.addresses) {
const got = refreshed.get(addr.address); const got = refreshed.get(addr.address);
@@ -124,9 +123,6 @@ async function doRefreshAndRender() {
} }
} }
} }
if (!stored.useBlockscout) {
dropUntrackedTokens(state.wallets, stored.trackedTokens);
}
state.lastBalanceRefresh = Date.now(); state.lastBalanceRefresh = Date.now();
return true; return true;
}); });
@@ -238,36 +234,17 @@ function fallbackView() {
// src/shared/chainSwitch.js), so a record naming a network other than this // src/shared/chainSwitch.js), so a record naming a network other than this
// page's always holds a switch saved elsewhere, and a switch the user picks // page's always holds a switch saved elsewhere, and a switch the user picks
// here while this waits for the lock is made after it. // here while this waits for the lock is made after it.
// async function followNetworkSwitch() {
// Another page may change "Use Blockscout" or "Check recipients on Etherscan". let switched = false;
// This page takes either change the same way and draws its screens again. Its
// own changes to them are made under the lock too (views/settings.js,
// views/welcome.js).
async function followStoredSettings() {
let changed = false;
// A failed save is reported by saveState() itself: see onSaveFailure(). // A failed save is reported by saveState() itself: see onSaveFailure().
await saveState((stored) => { await saveState((stored) => {
if (stored.networkId !== state.networkId) { if (stored.networkId === state.networkId) return false;
settings.cutViewStack(); settings.cutViewStack();
takeStoredNetwork(stored); takeStoredNetwork(stored);
clearPopupCaches(); clearPopupCaches();
changed = true; switched = true;
}
if (stored.useBlockscout !== state.useBlockscout) {
settings.setUseBlockscout(stored.useBlockscout);
changed = true;
}
if (
stored.checkRecipientsOnEtherscan !==
state.checkRecipientsOnEtherscan
) {
state.checkRecipientsOnEtherscan =
stored.checkRecipientsOnEtherscan;
changed = true;
}
if (!changed) return false;
}).catch(() => {}); }).catch(() => {});
if (changed) settings.showNewSettings(); if (switched) settings.showNewNetwork();
} }
// The background opens the approval window as wide as the page, but the // The background opens the approval window as wide as the page, but the
@@ -414,24 +391,16 @@ async function init() {
settingsAddToken.init(ctx); settingsAddToken.init(ctx);
deleteAddress.init(ctx); deleteAddress.init(ctx);
// Only a record whose network, "Use Blockscout" or "Check recipients on // Only a record naming a network other than this page's is followed: the
// Etherscan" differs from this page's is followed. A record this page's // one this page's own switch in Settings saves names the network it
// own save wrote matches this page. A record naming no network is no // already has, and a record naming none is no switch.
// switch.
onStorageChanged((changes) => { onStorageChanged((changes) => {
const written = changes.autistmask && changes.autistmask.newValue; const written = changes.autistmask && changes.autistmask.newValue;
if (!written || !written.networkId) return; if (!written || !written.networkId) return;
if ( if (written.networkId !== state.networkId) followNetworkSwitch();
written.networkId !== state.networkId ||
written.useBlockscout !== state.useBlockscout ||
written.checkRecipientsOnEtherscan !==
state.checkRecipientsOnEtherscan
) {
followStoredSettings();
}
}); });
// A change saved since loadState() above, before there was a listener. // A switch saved since loadState() above, before there was a listener.
followStoredSettings(); followNetworkSwitch();
if (!state.hasWallet) { if (!state.hasWallet) {
welcome.show(); welcome.show();
+3 -3
View File
@@ -42,9 +42,9 @@ function markViewRendered(view) {
// asks for one when every screen on the page is out of date: // asks for one when every screen on the page is out of date:
// //
// - after a network switch, since those screens show the previous network // - after a network switch, since those screens show the previous network
// (showNewSettings() in views/settings.js) // (showNewNetwork() in views/settings.js)
// - after "Use Blockscout" or "Check recipients on Etherscan" changes, here // - after "Use Blockscout" or "Check recipients on Etherscan" changes in
// or on another page, since those screens were drawn under the old setting // Settings, since those screens were drawn under the old setting
// //
// The unit tests, which simulate several page loads against one module // The unit tests, which simulate several page loads against one module
// instance, ask for one too. // instance, ask for one too.
+1
View File
@@ -617,6 +617,7 @@ async function checkRecipientHistory(txInfo) {
const asyncWarnings = await getFullWarnings(txInfo.to, provider, { const asyncWarnings = await getFullWarnings(txInfo.to, provider, {
fromAddress: txInfo.from, fromAddress: txInfo.from,
chainId: txInfo.chainId, chainId: txInfo.chainId,
checkRecipientsOnEtherscan: state.checkRecipientsOnEtherscan,
}); });
// The user may have left this transaction while the checks were in // The user may have left this transaction while the checks were in
// flight; its warnings must not reach another recipient's screen. // flight; its warnings must not reach another recipient's screen.
+44 -48
View File
@@ -17,7 +17,6 @@ const {
const { state, saveState, currentNetwork } = require("../../shared/state"); const { state, saveState, currentNetwork } = require("../../shared/state");
const { onChainSwitch } = require("../../shared/chainSwitch"); const { onChainSwitch } = require("../../shared/chainSwitch");
const { restorableStack } = require("../../shared/persistedState"); const { restorableStack } = require("../../shared/persistedState");
const { dropUntrackedTokens } = require("../../shared/balances");
const { resetRenderedViews, markViewRendered } = require("../viewRouter"); const { resetRenderedViews, markViewRendered } = require("../viewRouter");
const { const {
log, log,
@@ -262,8 +261,7 @@ async function renderSiteLists() {
// native token: any other token there was picked on the previous network, so // 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 // 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. // 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() // A change to "Use Blockscout" cuts the stack the same way (init() below).
// below).
function cutViewStack() { function cutViewStack() {
if (!state.hasWallet) return; if (!state.hasWallet) return;
if (state.selectedToken !== "ETH") { if (state.selectedToken !== "ETH") {
@@ -276,39 +274,18 @@ function cutViewStack() {
state.viewStack = restorableStack(state.viewStack, state.currentView); state.viewStack = restorableStack(state.viewStack, state.currentView);
} }
// Sets "Use Blockscout" to `on` on this page. It is called inside a save, // Every screen counts as not yet rendered, so "Back" renders the one it lands
// holding the lock, for the user's change here or another page's // on for the new network. Settings on top is brought up to date in place;
// (followStoredSettings() in src/popup/index.js). // 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
// Send's token dropdown was filled under the old setting, and Back cannot draw // screen is Welcome or AddWallet, which show nothing from a network.
// Send again. So the stack is cut, as for a network switch. Turning the setting function showNewNetwork() {
// 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 the one it lands on under the new
// settings. 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(); resetRenderedViews();
$("welcome-use-blockscout").checked = state.useBlockscout;
$("welcome-check-recipients-on-etherscan").checked =
state.checkRecipientsOnEtherscan;
if (state.currentView === "settings") { if (state.currentView === "settings") {
markViewRendered("settings"); markViewRendered("settings");
$("settings-network").value = state.networkId; $("settings-network").value = state.networkId;
$("settings-rpc").value = state.rpcUrl; $("settings-rpc").value = state.rpcUrl;
$("settings-blockscout").value = state.blockscoutUrl; $("settings-blockscout").value = state.blockscoutUrl;
$("settings-use-blockscout").checked = state.useBlockscout;
$("settings-check-recipients-on-etherscan").checked =
state.checkRecipientsOnEtherscan;
renderTrackedTokens(); renderTrackedTokens();
} else if (state.hasWallet) { } else if (state.hasWallet) {
ctx.restoreView(); ctx.restoreView();
@@ -391,31 +368,51 @@ function init(_ctx) {
showFlash("Saved."); showFlash("Saved.");
}); });
// Each change is made inside the save, holding the lock. Then // The screens under Settings were drawn under the old setting. Each change
// followStoredSettings() in src/popup/index.js never takes this page's // makes every screen but Settings count as not yet drawn, as a network
// unsaved change for another page's. The screens under Settings were drawn // switch does. Back then draws again any screen a reopened popup can draw.
// under the old setting. So each change makes every screen but Settings count as not yet // Welcome offers the same two settings, and Back only unhides it, so its
// drawn, as a network switch does. Back then draws again any screen a // boxes are set here too.
// reopened popup can draw. Back only unhides Welcome, so its boxes are set //
// here too. // A change to "Use Blockscout" also cuts the stack, as a network switch
// does (cutViewStack() above). Send's token dropdown was filled under the
// old setting, and Back cannot draw Send again.
//
// Turning "Use Blockscout" off drops the tokens the user has not tracked
// from every address. Only Blockscout reports them, so no screen lists them
// from then on.
$("settings-use-blockscout").addEventListener("change", async () => { $("settings-use-blockscout").addEventListener("change", async () => {
const on = $("settings-use-blockscout").checked; state.useBlockscout = $("settings-use-blockscout").checked;
await saveState(() => setUseBlockscout(on)); $("welcome-use-blockscout").checked = state.useBlockscout;
$("welcome-use-blockscout").checked = on; if (!state.useBlockscout) {
const tracked = new Set(
state.trackedTokens.map((t) => t.address.toLowerCase()),
);
for (const wallet of state.wallets) {
for (const addr of wallet.addresses) {
addr.tokenBalances = (addr.tokenBalances || []).filter(
(row) => tracked.has(String(row.address).toLowerCase()),
);
}
}
}
cutViewStack();
resetRenderedViews(); resetRenderedViews();
markViewRendered("settings"); markViewRendered("settings");
await saveState();
}); });
$("settings-check-recipients-on-etherscan").addEventListener( $("settings-check-recipients-on-etherscan").addEventListener(
"change", "change",
async () => { async () => {
const on = $("settings-check-recipients-on-etherscan").checked; state.checkRecipientsOnEtherscan = $(
await saveState(() => { "settings-check-recipients-on-etherscan",
state.checkRecipientsOnEtherscan = on; ).checked;
}); $("welcome-check-recipients-on-etherscan").checked =
$("welcome-check-recipients-on-etherscan").checked = on; state.checkRecipientsOnEtherscan;
resetRenderedViews(); resetRenderedViews();
markViewRendered("settings"); markViewRendered("settings");
await saveState();
}, },
); );
@@ -432,7 +429,7 @@ function init(_ctx) {
// Open pages are told by the background, as after a site's approved // Open pages are told by the background, as after a site's approved
// switch request. // switch request.
notify({ type: "AUTISTMASK_NETWORK_CHANGED", chainId: net.chainId }); notify({ type: "AUTISTMASK_NETWORK_CHANGED", chainId: net.chainId });
showNewSettings(); showNewNetwork();
showFlash("Switched to " + net.name + "."); showFlash("Switched to " + net.name + ".");
}); });
@@ -572,6 +569,5 @@ module.exports = {
show, show,
renderSiteLists, renderSiteLists,
cutViewStack, cutViewStack,
setUseBlockscout, showNewNetwork,
showNewSettings,
}; };
+3 -3
View File
@@ -308,9 +308,9 @@ function populateOnChainDetails(txData, chainId) {
} }
} }
// Reads the block, network details and calldata from Blockscout, and decodes // The block, network details, calldata and its decoding, from Blockscout. While
// the calldata. While "Use Blockscout" is off, Blockscout is not asked and none // the user has "Use Blockscout" off, Blockscout is not asked and none of them
// of them is shown. // is shown.
async function loadFullTxDetails(txHash, chainId) { async function loadFullTxDetails(txHash, chainId) {
if (!state.useBlockscout) return; if (!state.useBlockscout) return;
const section = $("tx-detail-calldata-section"); const section = $("tx-detail-calldata-section");
+6 -9
View File
@@ -12,24 +12,21 @@ function show() {
showView("welcome"); showView("welcome");
} }
// Each change is made inside the save, holding the lock, as Settings makes it.
function init(ctx) { function init(ctx) {
$("btn-welcome-add").addEventListener("click", ctx.showAddWalletView); $("btn-welcome-add").addEventListener("click", ctx.showAddWalletView);
$("welcome-use-blockscout").addEventListener("change", async () => { $("welcome-use-blockscout").addEventListener("change", async () => {
const on = $("welcome-use-blockscout").checked; state.useBlockscout = $("welcome-use-blockscout").checked;
await saveState(() => { await saveState();
state.useBlockscout = on;
});
}); });
$("welcome-check-recipients-on-etherscan").addEventListener( $("welcome-check-recipients-on-etherscan").addEventListener(
"change", "change",
async () => { async () => {
const on = $("welcome-check-recipients-on-etherscan").checked; state.checkRecipientsOnEtherscan = $(
await saveState(() => { "welcome-check-recipients-on-etherscan",
state.checkRecipientsOnEtherscan = on; ).checked;
}); await saveState();
}, },
); );
} }
+3
View File
@@ -60,6 +60,8 @@ function getLocalWarnings(address, options = {}) {
* @param {string} [options.fromAddress] - Sender address (for self-send check). * @param {string} [options.fromAddress] - Sender address (for self-send check).
* @param {string} [options.chainId] - Chain id of the network whose explorer * @param {string} [options.chainId] - Chain id of the network whose explorer
* the Etherscan label check asks; the active network's when omitted. * the Etherscan label check asks; the active network's when omitted.
* @param {boolean} [options.checkRecipientsOnEtherscan] - The user's setting
* of that name. The Etherscan label check is made only while it is on.
* @returns {Promise<Array<{type: string, message: string, severity: string}>>} * @returns {Promise<Array<{type: string, message: string, severity: string}>>}
*/ */
async function getFullWarnings(address, provider, options = {}) { async function getFullWarnings(address, provider, options = {}) {
@@ -114,6 +116,7 @@ async function getFullWarnings(address, provider, options = {}) {
try { try {
const etherscanWarning = await checkEtherscanLabel( const etherscanWarning = await checkEtherscanLabel(
address, address,
options.checkRecipientsOnEtherscan,
options.chainId, options.chainId,
); );
if (etherscanWarning) { if (etherscanWarning) {
+1 -17
View File
@@ -2,8 +2,7 @@
// //
// - ETH balances, and the balances of the tokens the user tracks, via RPC // - ETH balances, and the balances of the tokens the user tracks, via RPC
// - the other ERC-20 token balances and the holder counts via Blockscout, // - the other ERC-20 token balances and the holder counts via Blockscout,
// while the user has "Use Blockscout" on. Those balances are dropped when // while the user has "Use Blockscout" on
// it is off.
// - ENS reverse lookup via RPC // - ENS reverse lookup via RPC
const { const {
@@ -427,20 +426,6 @@ async function refreshBalances(
log.debugf("refreshBalances done"); log.debugf("refreshBalances done");
} }
// Drops the balance of each token not in `trackedTokens` from every address.
// Callers run it while "Use Blockscout" is off, since only Blockscout reports
// those tokens.
function dropUntrackedTokens(wallets, trackedTokens) {
const tracked = new Set(trackedTokens.map((t) => t.address.toLowerCase()));
for (const wallet of wallets) {
for (const addr of wallet.addresses || []) {
addr.tokenBalances = (addr.tokenBalances || []).filter((row) =>
tracked.has(String(row.address).toLowerCase()),
);
}
}
}
// Look up token metadata from its contract. // Look up token metadata from its contract.
// Calls symbol() and decimals() to verify it implements ERC-20. // Calls symbol() and decimals() to verify it implements ERC-20.
// `signal` is as for fetchTokenBalances(). // `signal` is as for fetchTokenBalances().
@@ -559,7 +544,6 @@ async function scanForAddresses(xpub, rpcUrl, networkId, signal, gapLimit = 5) {
module.exports = { module.exports = {
fetchTokenBalances, fetchTokenBalances,
refreshBalances, refreshBalances,
dropUntrackedTokens,
lookupTokenInfo, lookupTokenInfo,
getProvider, getProvider,
scanForAddresses, scanForAddresses,
+2 -2
View File
@@ -4,7 +4,7 @@
// //
// - onChainSwitch() below makes the switch the user picks in Settings, with // - onChainSwitch() below makes the switch the user picks in Settings, with
// applyChainSwitchFields() (chainSwitchFields.js). // applyChainSwitchFields() (chainSwitchFields.js).
// - followStoredSettings() in src/popup/index.js takes a switch the background // - followNetworkSwitch() in src/popup/index.js takes a switch the background
// or another popup page saved, from the stored record, with // or another popup page saved, from the stored record, with
// takeStoredNetwork() (state.js), which copies it with // takeStoredNetwork() (state.js), which copies it with
// copyChainSwitchFields() (chainSwitchFields.js). // copyChainSwitchFields() (chainSwitchFields.js).
@@ -45,7 +45,7 @@ function clearPopupCaches() {
// //
// `state` is switched inside the save that stores the switch, holding the // `state` is switched inside the save that stores the switch, holding the
// lock, so it is never switched while a follow of a switch saved elsewhere // lock, so it is never switched while a follow of a switch saved elsewhere
// waits for the lock (followStoredSettings() in src/popup/index.js): that // waits for the lock (followNetworkSwitch() in src/popup/index.js): that
// follow runs first, and this switch is made after it. // follow runs first, and this switch is made after it.
async function onChainSwitch(newNetworkId) { async function onChainSwitch(newNetworkId) {
const { state, saveState } = require("./state"); const { state, saveState } = require("./state");
+9 -8
View File
@@ -3,7 +3,6 @@
// This is a best-effort enrichment: network failures return null silently. // This is a best-effort enrichment: network failures return null silently.
const { networkByChainId } = require("./networks"); const { networkByChainId } = require("./networks");
const { storageGet } = require("./browserApi");
// Patterns in the page title that indicate a flagged address. // Patterns in the page title that indicate a flagged address.
// Title format: "Fake_Phishing184810 | Address: 0x... | Etherscan" // Title format: "Fake_Phishing184810 | Address: 0x... | Etherscan"
@@ -79,18 +78,21 @@ function parseEtherscanPage(html) {
* when it is omitted: etherscan.io on mainnet, sepolia.etherscan.io on * when it is omitted: etherscan.io on mainnet, sepolia.etherscan.io on
* Sepolia. * Sepolia.
* *
* Etherscan is asked only while "Check recipients on Etherscan" is on in the * While the user's "Check recipients on Etherscan" setting is off, Etherscan
* stored profile. The page's own copy of the setting is not used, because * is not asked and the answer is null.
* another page may have just changed it. While it is off, the answer is null.
* *
* @param {string} address - Ethereum address to check. * @param {string} address - Ethereum address to check.
* @param {boolean} checkRecipientsOnEtherscan - The user's setting.
* @param {string} [chainId] - Chain id of the network to look it up on. * @param {string} [chainId] - Chain id of the network to look it up on.
* @returns {Promise<{type: string, message: string, severity: string}|null>} * @returns {Promise<{type: string, message: string, severity: string}|null>}
*/ */
async function checkEtherscanLabel(address, chainId) { async function checkEtherscanLabel(
address,
checkRecipientsOnEtherscan,
chainId,
) {
if (!checkRecipientsOnEtherscan) return null;
try { try {
const { autistmask } = await storageGet("autistmask");
if (!autistmask || !autistmask.checkRecipientsOnEtherscan) return null;
// Lazy require to avoid pulling in chrome.storage at module scope // Lazy require to avoid pulling in chrome.storage at module scope
// (which breaks unit tests that only exercise parseEtherscanPage). // (which breaks unit tests that only exercise parseEtherscanPage).
const { currentNetwork } = require("./state"); const { currentNetwork } = require("./state");
@@ -112,7 +114,6 @@ async function checkEtherscanLabel(address, chainId) {
return null; return null;
} catch { } catch {
// Network errors are expected — Etherscan may rate-limit or block. // Network errors are expected — Etherscan may rate-limit or block.
// A failed storage read lands here too, before Etherscan is asked.
return null; return null;
} }
} }
+6 -4
View File
@@ -414,10 +414,12 @@ function normalizePersisted(saved) {
: 100000; : 100000;
out.utcTimestamps = out.utcTimestamps =
saved.utcTimestamps !== undefined ? saved.utcTimestamps : false; saved.utcTimestamps !== undefined ? saved.utcTimestamps : false;
// Anything but true is off: each setting lets a request reach another out.useBlockscout =
// service. saved.useBlockscout !== undefined ? saved.useBlockscout : false;
out.useBlockscout = saved.useBlockscout === true; out.checkRecipientsOnEtherscan =
out.checkRecipientsOnEtherscan = saved.checkRecipientsOnEtherscan === true; saved.checkRecipientsOnEtherscan !== undefined
? saved.checkRecipientsOnEtherscan
: false;
// A list of contract addresses, floored the same way: send.js builds its // A list of contract addresses, floored the same way: send.js builds its
// fraud set as `(state.fraudContracts || []).map((a) => a.toLowerCase())` // fraud set as `(state.fraudContracts || []).map((a) => a.toLowerCase())`
// and filterTransactions() maps the same list through normalizeAddress(), // and filterTransactions() maps the same list through normalizeAddress(),
+3 -4
View File
@@ -470,9 +470,8 @@ function mergeNetworkEndpoints(base, ours, theirs) {
// (caught by tests/txStatus.test.js). A page's live picture of a field it // (caught by tests/txStatus.test.js). A page's live picture of a field it
// does not own goes on being whatever its last loadState() saw, same as // does not own goes on being whatever its last loadState() saw, same as
// before this fix; only the persisted record is guaranteed current. The // before this fix; only the persisted record is guaranteed current. The
// network, "Use Blockscout" and "Check recipients on Etherscan" are the // network is the exception: the popup takes a switch another context saved
// exceptions: the popup takes a change another context saved through // through `beforeWrite` (followNetworkSwitch() in src/popup/index.js).
// `beforeWrite` (followStoredSettings() in src/popup/index.js).
async function saveStateOnce(beforeWrite) { async function saveStateOnce(beforeWrite) {
const result = await storageGet("autistmask"); const result = await storageGet("autistmask");
// The record in storage right now is about to be merged into and written // The record in storage right now is about to be merged into and written
@@ -604,7 +603,7 @@ function saveState(beforeWrite) {
} }
// Give this page the network switch held by `stored`, a record a save of this // Give this page the network switch held by `stored`, a record a save of this
// page has just read (followStoredSettings() in src/popup/index.js calls this // page has just read (followNetworkSwitch() in src/popup/index.js calls this
// from `beforeWrite`), in `state` and in `baseline` alike, as loadState() does // from `beforeWrite`), in `state` and in `baseline` alike, as loadState() does
// with a whole record. The switch is then not this page's own change, even when // with a whole record. The switch is then not this page's own change, even when
// that save fails to write: a later switch back to the previous network still // that save fails to write: a later switch back to the previous network still
-2
View File
@@ -41,8 +41,6 @@ jest.mock("../src/shared/balances", () => ({
refreshBalances: jest.fn(async () => { refreshBalances: jest.fn(async () => {
mockBalanceRefreshAt.push(Date.now()); mockBalanceRefreshAt.push(Date.now());
}), }),
dropUntrackedTokens: jest.requireActual("../src/shared/balances")
.dropUntrackedTokens,
getProvider: jest.fn(() => ({})), getProvider: jest.fn(() => ({})),
})); }));
+4 -7
View File
@@ -142,8 +142,6 @@ function loadWorker(networkId, opts) {
}, },
refreshBalances: refreshBalances:
options.refreshBalances || jest.fn(async () => undefined), options.refreshBalances || jest.fn(async () => undefined),
dropUntrackedTokens: jest.requireActual("../src/shared/balances")
.dropUntrackedTokens,
})); }));
jest.doMock("../src/shared/phishingDomains", () => ({ jest.doMock("../src/shared/phishingDomains", () => ({
isPhishingDomain: () => false, isPhishingDomain: () => false,
@@ -456,9 +454,9 @@ describe("a balance refresh under a network switch", () => {
describe("a balance refresh under a change of Use Blockscout", () => { describe("a balance refresh under a change of Use Blockscout", () => {
// Turning "Use Blockscout" off drops the tokens the user has not tracked // Turning "Use Blockscout" off drops the tokens the user has not tracked
// (src/popup/views/settings.js). A refresh that started while it was on // (src/popup/views/settings.js). A refresh that started while it was on
// still saves its balances when it returns, but not those tokens. The same // must not write them back when it returns. The same refresh in the popup
// refresh in the popup is in tests/blockscoutEtherscanSettings.test.js. // is in tests/blockscoutEtherscanSettings.test.js.
test("a refresh that returns after Use Blockscout was turned off saves its balances without the untracked token", async () => { test("a refresh that returns after Use Blockscout was turned off writes nothing", async () => {
const roundTrip = deferred(); const roundTrip = deferred();
const started = deferred(); const started = deferred();
const handed = []; const handed = [];
@@ -473,7 +471,6 @@ describe("a balance refresh under a change of Use Blockscout", () => {
handed.push(useBlockscout); handed.push(useBlockscout);
started.resolve(); started.resolve();
await roundTrip.promise; await roundTrip.promise;
wallets[0].addresses[0].balance = "7.0";
wallets[0].addresses[0].tokenBalances = [ wallets[0].addresses[0].tokenBalances = [
{ {
address: "0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", address: "0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
@@ -506,7 +503,7 @@ describe("a balance refresh under a change of Use Blockscout", () => {
expect(handed).toEqual([true]); expect(handed).toEqual([true]);
const after = bg.persisted(); const after = bg.persisted();
expect(after.wallets[0].addresses[0].balance).toBe("7.0");
expect(after.wallets[0].addresses[0].tokenBalances).toEqual([]); expect(after.wallets[0].addresses[0].tokenBalances).toEqual([]);
expect(after.lastBalanceRefresh).toBe(0);
}); });
}); });
+32 -153
View File
@@ -1,22 +1,19 @@
// Blockscout and Etherscan are sent none of the user's data until the user // Blockscout and Etherscan are sent none of the user's data until the user
// turns them on (https://git.eeqj.de/sneak/AutistMask/issues/668). A new // turns them on (https://git.eeqj.de/sneak/AutistMask/issues/668). A new
// profile has "Use Blockscout" and "Check recipients on Etherscan" both off. // profile has "Use Blockscout" and "Check recipients on Etherscan" both off.
// While a setting is off, the code that would ask its service asks nothing. // Each request to either service is skipped where it is made while its setting
// is off. The popup and the background hand the stored setting to the code that
// makes the request. The one exception is the check behind "Save" beside the
// Blockscout URL in Settings. It asks that URL only for `/stats` and carries no
// user data. It is made whether or not the setting is on.
// //
// The one request made with the setting off is the check behind "Save" beside // Only Blockscout reports the tokens the user has not tracked. Turning "Use
// the Blockscout URL in Settings. It asks that URL only for `/stats` and sends // Blockscout" off in Settings drops them from every address, so no screen lists
// none of the user's data. // them from then on.
//
// The balance refresh and the Etherscan check follow the stored setting, not
// the page's copy of it. So a change another page saved applies to them at
// once. An open page also takes such a change and draws its screens again.
//
// Only Blockscout reports the tokens the user has not tracked. While "Use
// Blockscout" is off, no address keeps them and no screen lists them.
// //
// The functions that make the requests are driven directly, against a fetch() // The functions that make the requests are driven directly, against a fetch()
// that records every URL it is asked for. The screens are driven through the // that records every URL it is asked for. The screens are driven through the
// real popup over a stored profile (tests/support/popupBoot.js). Its fetch() // real popup over a stored profile (tests/support/popupBoot.js), whose fetch()
// stub records the same way. // stub records the same way.
"use strict"; "use strict";
@@ -140,26 +137,23 @@ describe("where each request is made", () => {
]); ]);
}); });
test("the Etherscan label check is made only while the stored profile has Check recipients on Etherscan on", async () => { test("the Etherscan label check is made only while Check recipients on Etherscan is on", async () => {
const provider = { const provider = {
getCode: async () => "0x", getCode: async () => "0x",
getTransactionCount: async () => 1, getTransactionCount: async () => 1,
getBalance: async () => 0n, getBalance: async () => 0n,
}; };
const checkWith = async (stored) => { await getFullWarnings(RECIPIENT, provider, { chainId: "0x1" });
globalThis.chrome = { storage: makeStorageStub(stored) }; await getFullWarnings(RECIPIENT, provider, {
try { chainId: "0x1",
await getFullWarnings(RECIPIENT, provider, { chainId: "0x1" }); checkRecipientsOnEtherscan: false,
} finally { });
delete globalThis.chrome;
}
};
await checkWith({});
await checkWith({ autistmask: { checkRecipientsOnEtherscan: false } });
expect(asked).toEqual([]); expect(asked).toEqual([]);
await checkWith({ autistmask: { checkRecipientsOnEtherscan: true } }); await getFullWarnings(RECIPIENT, provider, {
chainId: "0x1",
checkRecipientsOnEtherscan: true,
});
expect(asked).toEqual(["https://etherscan.io/address/" + RECIPIENT]); expect(asked).toEqual(["https://etherscan.io/address/" + RECIPIENT]);
}); });
@@ -460,9 +454,9 @@ describe("the popup", () => {
return profile; return profile;
} }
// The token ids of the options created after the first `from` elements. // The token ids of the options the page has created since it had created
// The page stub keeps no children, so this is where the options Send's // `from` elements. The page stub keeps no children, so the options Send's
// dropdown was filled with are read. // dropdown was filled with are read from there.
function optionsSince(page, from) { function optionsSince(page, from) {
return page.document.created return page.document.created
.slice(from) .slice(from)
@@ -566,20 +560,11 @@ describe("the popup", () => {
} }
}); });
// The token ids of the first address's balances in the stored profile.
function storedTokens(page) {
return page.storage
.read("autistmask")
.wallets[0].addresses[0].tokenBalances.map((row) =>
row.address.toLowerCase(),
);
}
// Turning the setting off dropped the untracked token. A refresh that // Turning the setting off dropped the untracked token. A refresh that
// started while it was on still saves its balances when it returns, but // started while it was on must not bring the token back when it returns.
// not that token. The same refresh in the background is in // The same refresh in the background is in
// tests/backgroundStateIsolation.test.js. // tests/backgroundStateIsolation.test.js.
test("a refresh that returns after Use Blockscout was turned off saves its balances without the untracked token", async () => { test("a refresh that returns after Use Blockscout was turned off saves none of its tokens", async () => {
const page = await bootPopup(onAddressDetail(true)); const page = await bootPopup(onAddressDetail(true));
const { refreshBalances: held } = require("../src/shared/balances"); const { refreshBalances: held } = require("../src/shared/balances");
let answer; let answer;
@@ -587,7 +572,6 @@ describe("the popup", () => {
await new Promise((resolve) => { await new Promise((resolve) => {
answer = resolve; answer = resolve;
}); });
wallets[0].addresses[0].balance = "7.0";
wallets[0].addresses[0].tokenBalances = [ wallets[0].addresses[0].tokenBalances = [
tokenRow(TRACKED, "TRK"), tokenRow(TRACKED, "TRK"),
tokenRow(UNTRACKED, "TST"), tokenRow(UNTRACKED, "TST"),
@@ -598,126 +582,22 @@ describe("the popup", () => {
await page.click("btn-settings"); await page.click("btn-settings");
await setBox(page, "settings-use-blockscout", false); await setBox(page, "settings-use-blockscout", false);
const before = page.storage.read("autistmask").lastBalanceRefresh;
answer(); answer();
await ticking; await ticking;
const stored = page.storage.read("autistmask");
expect( expect(
page.storage.read("autistmask").wallets[0].addresses[0].balance, stored.wallets[0].addresses[0].tokenBalances.map((row) =>
).toBe("7.0"); row.address.toLowerCase(),
expect(storedTokens(page)).toEqual([TRACKED]); ),
).toEqual([TRACKED]);
expect(stored.lastBalanceRefresh).toBe(before);
await page.click("btn-settings-back"); await page.click("btn-settings-back");
expect(page.node("address-balances").innerHTML).not.toContain( expect(page.node("address-balances").innerHTML).not.toContain(
UNTRACKED, UNTRACKED,
); );
}); });
// Another page saves `change` over the stored profile. The browser is not
// made to tell this page, so this page sees the change only by reading the
// stored profile.
function anotherPageSaves(page, change) {
page.storage.write("autistmask", {
...page.storage.read("autistmask"),
...change,
});
}
test.each([
["off", true, false],
["on", false, true],
])(
"after another page turns Use Blockscout %s, this page's next refresh follows the stored setting and is saved",
async (_how, before, after) => {
const page = await bootPopup(onAddressDetail(before));
const { refreshBalances: held } = require("../src/shared/balances");
held.mockImplementationOnce(
async (wallets, rpcUrl, blockscoutUrl, useBlockscout) => {
const addr = wallets[0].addresses[0];
addr.balance = "7.0";
addr.tokenBalances = useBlockscout
? [tokenRow(TRACKED, "TRK"), tokenRow(UNTRACKED, "TST")]
: [tokenRow(TRACKED, "TRK")];
},
);
anotherPageSaves(page, { useBlockscout: after });
await page.tick();
expect(held.mock.calls.at(-1)[3]).toBe(after);
expect(
page.storage.read("autistmask").wallets[0].addresses[0].balance,
).toBe("7.0");
expect(storedTokens(page)).toEqual(
after ? [TRACKED, UNTRACKED] : [TRACKED],
);
expect(page.hidden("address-tokens-note")).toBe(after);
},
);
test.each([
["off", true, false],
["on", false, true],
])(
"after another page turns Check recipients on Etherscan %s, this page's next Etherscan check follows the stored setting",
async (_how, before, after) => {
const page = await bootPopup(
onConfirmScreen({ checkRecipientsOnEtherscan: before }),
);
anotherPageSaves(page, { checkRecipientsOnEtherscan: after });
const from = fetched().length;
require("../src/popup/views/confirmTx").show({
token: "ETH",
from: ADDRESS,
to: RECIPIENT,
amount: "0.1",
chainId: "0x1",
});
await page.settle();
// The page also takes the change and draws ConfirmTx again, which
// checks once more. So each URL asked for is counted once.
const asked = new Set(
fetched().slice(from).filter(blockscoutOrEtherscan),
);
expect([...asked]).toEqual(
after ? ["https://etherscan.io/address/" + RECIPIENT] : [],
);
},
);
// Another page's save of the whole profile with `change`, which the
// browser announces to this page as it announces any write.
async function anotherPageAnnounces(page, change) {
await page.storage.local.set({
autistmask: { ...page.storage.read("autistmask"), ...change },
});
await page.settle();
}
test("a page open on AddressDetail draws it again when another page turns Use Blockscout off", async () => {
const page = await bootPopup(onAddressDetail(true));
expect(page.hidden("address-tokens-note")).toBe(true);
await anotherPageAnnounces(page, { useBlockscout: false });
expect(page.visibleViews()).toEqual(["address"]);
expect(page.hidden("address-tokens-note")).toBe(false);
expect(page.node("address-balances").innerHTML).not.toContain(
UNTRACKED,
);
expect(storedTokens(page)).toEqual([TRACKED]);
});
test("a page open on ConfirmTx draws it again when another page turns Check recipients on Etherscan off", async () => {
const page = await bootPopup(
onConfirmScreen({ checkRecipientsOnEtherscan: true }),
);
expect(page.hidden("confirm-etherscan-note")).toBe(true);
await anotherPageAnnounces(page, { checkRecipientsOnEtherscan: false });
expect(page.visibleViews()).toEqual(["confirm-tx"]);
expect(page.hidden("confirm-etherscan-note")).toBe(false);
});
}); });
describe("the background", () => { describe("the background", () => {
@@ -737,7 +617,6 @@ describe("the background", () => {
jest.doMock("../src/shared/balances", () => ({ jest.doMock("../src/shared/balances", () => ({
getProvider: () => ({}), getProvider: () => ({}),
refreshBalances: async (...args) => handed(args[3]), refreshBalances: async (...args) => handed(args[3]),
dropUntrackedTokens: () => {},
})); }));
let onAlarm = null; let onAlarm = null;
globalThis.chrome = { globalThis.chrome = {
-2
View File
@@ -73,8 +73,6 @@ function loadColdWorker(networkId, opts) {
jest.doMock("../src/shared/balances", () => ({ jest.doMock("../src/shared/balances", () => ({
getProvider: () => ({}), getProvider: () => ({}),
refreshBalances: options.refreshBalances || jest.fn(async () => {}), refreshBalances: options.refreshBalances || jest.fn(async () => {}),
dropUntrackedTokens: jest.requireActual("../src/shared/balances")
.dropUntrackedTokens,
})); }));
jest.doMock("../src/shared/phishingDomains", () => ({ jest.doMock("../src/shared/phishingDomains", () => ({
isPhishingDomain: () => false, isPhishingDomain: () => false,
+1 -1
View File
@@ -303,7 +303,7 @@ function explorerRequests(env) {
}); });
} }
// Waits until the list `listId` shows the line saying "Use Blockscout" is off. // Waits until the list `listId` says what Use Blockscout being off leaves out.
async function waitForHistoryOff(page, listId) { async function waitForHistoryOff(page, listId) {
await page.waitForFunction( await page.waitForFunction(
([id, want]) => document.getElementById(id).textContent.trim() === want, ([id, want]) => document.getElementById(id).textContent.trim() === want,
+5 -10
View File
@@ -618,6 +618,8 @@ const CONTRACT = [
"hideFraudContracts", "hideFraudContracts",
"hideDustTransactions", "hideDustTransactions",
"utcTimestamps", "utcTimestamps",
"useBlockscout",
"checkRecipientsOnEtherscan",
"debugMode", "debugMode",
].map((field) => ({ ].map((field) => ({
field, field,
@@ -625,18 +627,11 @@ const CONTRACT = [
// A flag: only ever tested for truthiness, and written back verbatim. // A flag: only ever tested for truthiness, and written back verbatim.
hostile: [42, "notabool", { a: 1 }], hostile: [42, "notabool", { a: 1 }],
// Both answers to that truthiness test have to be driven, and 0 is a // Both answers to that truthiness test have to be driven, and 0 is a
// value src/ never writes for a flag. For utcTimestamps and debugMode // value src/ never writes for a flag. For utcTimestamps,
// the falsy answer is also the DEFAULT_STATE default. // useBlockscout, checkRecipientsOnEtherscan and debugMode the falsy
// answer is also the DEFAULT_STATE default.
falsy: [0], falsy: [0],
})), })),
// Each lets a request reach another service, so anything but true is off.
...["useBlockscout", "checkRecipientsOnEtherscan"].map((field) => ({
field,
kind: KIND.SCALAR,
hostile: [42, "notabool", { a: 1 }],
floorOnly: [0, null, "true"],
holds: (v) => v === false,
})),
]; ];
function siteMapHolds(v) { function siteMapHolds(v) {
+2 -3
View File
@@ -44,9 +44,8 @@ test("a flash message's timer does not fire after cleanupPopup()", async () => {
// A boot onto a screen that sends a request no mocked module answers: that // A boot onto a screen that sends a request no mocked module answers: that
// request reaches the boot's fetch() stub, never the internet // request reaches the boot's fetch() stub, never the internet
// (https://git.eeqj.de/sneak/AutistMask/issues/559). TransactionDetail asks // (https://git.eeqj.de/sneak/AutistMask/issues/559). Both requests are made
// Blockscout only while "Use Blockscout" is on. ConfirmTx asks Etherscan only // only while the user has the setting for them on.
// while "Check recipients on Etherscan" is on. So the profile turns both on.
function restoringOnto(view, viewData) { function restoringOnto(view, viewData) {
return unversionedValidProfile({ return unversionedValidProfile({
currentView: view, currentView: view,
+7 -9
View File
@@ -523,15 +523,13 @@ describe("a garbage value in a field the gate does not check", () => {
}); });
test("a malformed tokenBalances entry is dropped, and the wallet and its address survive", async () => { test("a malformed tokenBalances entry is dropped, and the wallet and its address survive", async () => {
// "Use Blockscout" is on, or the boot's balance refresh drops the const env = await bootPopup(
// well-formed entry too: the user has not tracked its token. profileWithTokenBalances([
const profile = profileWithTokenBalances([ null,
null, 42,
42, { address: TOKEN_ADDRESS, symbol: "AAA", balance: "2.0" },
{ address: TOKEN_ADDRESS, symbol: "AAA", balance: "2.0" }, ]),
]); );
profile.useBlockscout = true;
const env = await bootPopup(profile);
const stored = env.storage.read("autistmask"); const stored = env.storage.read("autistmask");
expect(stored.wallets[0].encryptedSecret).toBe("encrypted-secret-1"); expect(stored.wallets[0].encryptedSecret).toBe("encrypted-secret-1");
-2
View File
@@ -283,8 +283,6 @@ async function bootPopup(stored, options) {
jest.doMock("../../src/shared/balances", () => ({ jest.doMock("../../src/shared/balances", () => ({
fetchTokenBalances: jest.fn(async () => []), fetchTokenBalances: jest.fn(async () => []),
refreshBalances: jest.fn(async () => {}), refreshBalances: jest.fn(async () => {}),
dropUntrackedTokens: jest.requireActual("../../src/shared/balances")
.dropUntrackedTokens,
lookupTokenInfo: jest.fn(async () => null), lookupTokenInfo: jest.fn(async () => null),
getProvider: () => ({}), getProvider: () => ({}),
scanForAddresses: jest.fn(async () => ({ scanForAddresses: jest.fn(async () => ({