fix: Blockscout and Etherscan lookups are off until the user turns them on (closes #668)
check / check (push) Waiting to run
e2e / e2e-chrome (push) Waiting to run
e2e / e2e-firefox (push) Waiting to run

Out of the box the wallet sent the user's addresses to Blockscout for history
and token lookups, and 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 profile 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; after either changes in Settings, Back
draws the screen it lands on again. README and the user guide say what is
contacted by default and what each setting adds.

Model: opus-5-5
This commit is contained in:
2026-10-21 15:33:38 +00:00
parent c316d9ee78
commit c1daf872f8
49 changed files with 1349 additions and 139 deletions
+8
View File
@@ -4,6 +4,7 @@ const {
showFlash,
balanceLinesForAddress,
txCounterpartyHtml,
HISTORY_OFF_TEXT,
addressTitle,
escapeHtml,
displaySymbol,
@@ -83,6 +84,8 @@ function show() {
ctx.showAddressToken();
});
});
// Tokens the user has not tracked are Blockscout's to report.
$("address-tokens-note").classList.toggle("hidden", !!state.useBlockscout);
renderSendTokenSelect(addr);
$("tx-list").innerHTML =
'<div class="text-muted text-xs py-1">Loading...</div>';
@@ -99,6 +102,7 @@ async function loadTransactions(address) {
const rawTxs = await fetchRecentTransactions(
address,
state.blockscoutUrl,
state.useBlockscout,
currentNetwork().chainId,
);
const result = filterTransactions(rawTxs, {
@@ -155,6 +159,10 @@ async function loadTransactions(address) {
function renderTransactions(txs) {
const list = $("tx-list");
if (!state.useBlockscout) {
list.innerHTML = `<div class="text-muted text-xs py-1">${escapeHtml(HISTORY_OFF_TEXT)}</div>`;
return;
}
if (txs.length === 0) {
list.innerHTML =
'<div class="text-muted text-xs py-1">No transactions found.</div>';
+6
View File
@@ -7,6 +7,7 @@ const {
showFlash,
flashCopyFeedback,
txCounterpartyHtml,
HISTORY_OFF_TEXT,
addressTitle,
escapeHtml,
displaySymbol,
@@ -173,6 +174,7 @@ async function loadTransactions(address, tokenId) {
const rawTxs = await fetchRecentTransactions(
address,
state.blockscoutUrl,
state.useBlockscout,
currentNetwork().chainId,
);
const result = filterTransactions(rawTxs, {
@@ -239,6 +241,10 @@ async function loadTransactions(address, tokenId) {
function renderTransactions(txs) {
const list = $("address-token-tx-list");
if (!state.useBlockscout) {
list.innerHTML = `<div class="text-muted text-xs py-1">${escapeHtml(HISTORY_OFF_TEXT)}</div>`;
return;
}
if (txs.length === 0) {
list.innerHTML =
'<div class="text-muted text-xs py-1">No transactions found.</div>';
+6
View File
@@ -334,6 +334,11 @@ function show(txInfo) {
$("confirm-contract-warning").style.visibility = "hidden";
$("confirm-burn-warning").style.visibility = "hidden";
$("confirm-etherscan-warning").style.visibility = "hidden";
// While "Check recipients on Etherscan" is off there is no Etherscan
// warning to reserve space for, and a line in its place says so.
const etherscanOn = !!state.checkRecipientsOnEtherscan;
$("confirm-etherscan-warning").classList.toggle("hidden", !etherscanOn);
$("confirm-etherscan-note").classList.toggle("hidden", etherscanOn);
// Show burn warning via reserved element (in addition to inline warning)
if (isBurnAddress(txInfo.to)) {
@@ -612,6 +617,7 @@ async function checkRecipientHistory(txInfo) {
const asyncWarnings = await getFullWarnings(txInfo.to, provider, {
fromAddress: txInfo.from,
chainId: txInfo.chainId,
checkRecipientsOnEtherscan: state.checkRecipientsOnEtherscan,
});
// The user may have left this transaction while the checks were in
// flight; its warnings must not reach another recipient's screen.
+2 -1
View File
@@ -16,6 +16,7 @@ const {
broadcastActiveChanged,
} = require("../../shared/walletDelete");
const { INVISIBLE_CHARACTERS } = require("../../shared/symbolSpoof");
const welcome = require("./welcome");
let deleteWalletIndex = null;
let lostPasswordIndex = null;
@@ -135,7 +136,7 @@ async function finishDelete(walletIdx) {
// Save before broadcasting: the background reads the active
// address back out of storage to build accountsChanged.
if (activeAddressChanged) broadcastActiveChanged();
showView("welcome");
welcome.show();
return;
}
+6
View File
@@ -534,6 +534,11 @@ function addressTitle(address, wallets) {
const CONTRACT_CREATION_TEXT =
"This transaction creates a new contract. It has no recipient.";
// What the transaction lists on Home, AddressDetail and AddressToken say while
// the user has "Use Blockscout" off: the history comes from Blockscout alone.
const HISTORY_OFF_TEXT =
'No transaction history is shown, because "Use Blockscout" is off in Settings.';
// The last two lines of a transaction history row: the counterparty's colour
// dot and name beside the amount, then its full address. A contract creation
// the user sent has no counterparty (its `to` is ""), so its row has the
@@ -799,6 +804,7 @@ module.exports = {
displaySymbol,
addressTitle,
CONTRACT_CREATION_TEXT,
HISTORY_OFF_TEXT,
txCounterpartyHtml,
formatAddressHtml,
renderAddressHtml,
+8
View File
@@ -7,6 +7,7 @@ const {
timeAgo,
addressDotHtml,
txCounterpartyHtml,
HISTORY_OFF_TEXT,
addressTitle,
escapeHtml,
displaySymbol,
@@ -100,6 +101,10 @@ let homeTxs = [];
function renderHomeTxList(ctx) {
const list = $("home-tx-list");
if (!list) return;
if (!state.useBlockscout) {
list.innerHTML = `<div class="text-muted text-xs py-1">${escapeHtml(HISTORY_OFF_TEXT)}</div>`;
return;
}
if (homeTxs.length === 0) {
list.innerHTML =
'<div class="text-muted text-xs py-1">No transactions found.</div>';
@@ -192,6 +197,7 @@ async function loadHomeTxs(ctx) {
fetchRecentTransactions(
addr,
state.blockscoutUrl,
state.useBlockscout,
currentNetwork().chainId,
),
);
@@ -300,6 +306,8 @@ function walletListHtml() {
function render(ctx) {
const container = $("wallet-list");
// Tokens the user has not tracked are Blockscout's to report.
$("home-tokens-note").classList.toggle("hidden", !!state.useBlockscout);
if (state.wallets.length === 0) {
container.innerHTML =
'<p class="text-muted py-2">No wallets yet. Add one to get started.</p>';
+31
View File
@@ -201,6 +201,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();
@@ -363,6 +367,33 @@ function init(_ctx) {
showFlash("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, and Back draws the one it lands on again. Welcome
// offers the same two settings and is only unhidden on Back, so its boxes
// are set here too.
$("settings-use-blockscout").addEventListener("change", async () => {
state.useBlockscout = $("settings-use-blockscout").checked;
$("welcome-use-blockscout").checked = state.useBlockscout;
resetRenderedViews();
markViewRendered("settings");
await saveState();
});
$("settings-check-recipients-on-etherscan").addEventListener(
"change",
async () => {
state.checkRecipientsOnEtherscan = $(
"settings-check-recipients-on-etherscan",
).checked;
$("welcome-check-recipients-on-etherscan").checked =
state.checkRecipientsOnEtherscan;
resetRenderedViews();
markViewRendered("settings");
await saveState();
},
);
const networkSelect = $("settings-network");
networkSelect.addEventListener("change", async () => {
// Whether `state` already has the network picked is asked only once
+9
View File
@@ -191,6 +191,11 @@ function render() {
if (el) el.classList.add("hidden");
}
// Those details are Blockscout's; this line says why there are none.
$("tx-detail-blockscout-note").classList.toggle(
"hidden",
!!state.useBlockscout,
);
loadFullTxDetails(tx.hash, tx.chainId);
const isoStr = isoDate(tx.timestamp);
@@ -303,7 +308,11 @@ function populateOnChainDetails(txData, chainId) {
}
}
// The block, network details, calldata and its decoding, from Blockscout. While
// the user has "Use Blockscout" off, Blockscout is not asked and none of them
// is shown.
async function loadFullTxDetails(txHash, chainId) {
if (!state.useBlockscout) return;
const section = $("tx-detail-calldata-section");
const actionEl = $("tx-detail-calldata-action");
const detailsEl = $("tx-detail-calldata-details");
+29 -2
View File
@@ -1,7 +1,34 @@
const { $ } = require("./helpers");
// The first screen, shown while there is no wallet. It offers the two lookups
// at services other than the RPC endpoint, both off until the user ticks them,
// which Settings offers as well.
const { $, showView } = require("./helpers");
const { state, saveState } = require("../../shared/state");
function show() {
$("welcome-use-blockscout").checked = state.useBlockscout;
$("welcome-check-recipients-on-etherscan").checked =
state.checkRecipientsOnEtherscan;
showView("welcome");
}
function init(ctx) {
$("btn-welcome-add").addEventListener("click", ctx.showAddWalletView);
$("welcome-use-blockscout").addEventListener("change", async () => {
state.useBlockscout = $("welcome-use-blockscout").checked;
await saveState();
});
$("welcome-check-recipients-on-etherscan").addEventListener(
"change",
async () => {
state.checkRecipientsOnEtherscan = $(
"welcome-check-recipients-on-etherscan",
).checked;
await saveState();
},
);
}
module.exports = { init };
module.exports = { init, show };