fix: Blockscout and Etherscan lookups are off until the user turns them on (closes #668)
Out of the box the wallet sent the user's addresses to Blockscout and each recipient's address to Etherscan. Two settings, "Use Blockscout" and "Check recipients on Etherscan", now gate every request that sends the user's data to those services. Both start off and are offered unticked on Welcome and in Settings. Each request reads its setting from storage before it asks. An answer that lands after the setting went off is dropped. While a setting is off, the screens that rely on it say so. ENS lookups ask only the RPC endpoint, so a name resolved off-chain does not resolve. RULES.md's External Communication item is now out of date. Its new wording will be proposed on #211 once this lands. Model: opus-5-5 (implementation, summary); fable-5-1 (prose)
This commit was merged in pull request #671.
This commit is contained in:
@@ -4,6 +4,7 @@ const {
|
||||
showFlash,
|
||||
balanceLinesForAddress,
|
||||
txCounterpartyHtml,
|
||||
HISTORY_OFF_TEXT,
|
||||
addressTitle,
|
||||
escapeHtml,
|
||||
displaySymbol,
|
||||
@@ -83,6 +84,8 @@ function show() {
|
||||
ctx.showAddressToken();
|
||||
});
|
||||
});
|
||||
// Only Blockscout reports the tokens the user has not tracked.
|
||||
$("address-tokens-note").classList.toggle("hidden", !!state.useBlockscout);
|
||||
renderSendTokenSelect(addr);
|
||||
$("tx-list").innerHTML =
|
||||
'<div class="text-muted text-xs py-1">Loading...</div>';
|
||||
@@ -155,6 +158,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>';
|
||||
@@ -229,7 +236,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");
|
||||
updateSendBalance();
|
||||
resetSendValidation();
|
||||
|
||||
@@ -7,6 +7,7 @@ const {
|
||||
showFlash,
|
||||
flashCopyFeedback,
|
||||
txCounterpartyHtml,
|
||||
HISTORY_OFF_TEXT,
|
||||
addressTitle,
|
||||
escapeHtml,
|
||||
displaySymbol,
|
||||
@@ -239,6 +240,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>';
|
||||
@@ -324,7 +329,7 @@ function init(_ctx) {
|
||||
$("send-token").value = tokenId;
|
||||
}
|
||||
// Hide dropdown, show static token display
|
||||
$("send-token").classList.add("hidden");
|
||||
$("send-token-dropdown").classList.add("hidden");
|
||||
let staticHtml = `<div class="font-bold">${escapeHtml(displaySymbol(currentSymbol))}</div>`;
|
||||
if (tokenId !== "ETH") {
|
||||
staticHtml += `<div class="text-xs">${renderAddressHtml(tokenId)}</div>`;
|
||||
|
||||
@@ -11,6 +11,7 @@ const {
|
||||
onViewLeave,
|
||||
formatFee,
|
||||
tokenLabel,
|
||||
blockscoutOffTokenNoteHtml,
|
||||
tokenSourcesByChainId,
|
||||
} = require("./helpers");
|
||||
const { state, saveState, currentNetwork } = require("../../shared/state");
|
||||
@@ -401,6 +402,10 @@ function showTxApproval(details) {
|
||||
if (d.address) {
|
||||
if (d.isToken) {
|
||||
detailsHtml += `<div class="font-bold">${escapeHtml(tokenLabel(d.address, approvedTx.chainId) || "Unknown token")}</div>`;
|
||||
detailsHtml += blockscoutOffTokenNoteHtml(
|
||||
d.address,
|
||||
approvedTx.chainId,
|
||||
);
|
||||
}
|
||||
detailsHtml += approvalAddressHtml(
|
||||
d.address,
|
||||
@@ -623,10 +628,7 @@ function permitWarningHtml(types, primaryType, domain, message) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const sources = {
|
||||
trackedTokens: state.trackedTokens,
|
||||
wallets: state.wallets,
|
||||
};
|
||||
const sources = tokenSourcesByChainId();
|
||||
const spender = addressOrNull(
|
||||
declaredField(types, primaryType, message, "spender")?.value,
|
||||
);
|
||||
@@ -655,6 +657,7 @@ function permitWarningHtml(types, primaryType, domain, message) {
|
||||
html += `<div class="mb-2"><div>Token</div>`;
|
||||
if (token) {
|
||||
html += `<div>${escapeHtml(tokenLabel(token) || "Unknown token")}</div>`;
|
||||
html += blockscoutOffTokenNoteHtml(token);
|
||||
html += approvalAddressHtml(token);
|
||||
} else {
|
||||
html += `<div>Unknown</div>`;
|
||||
|
||||
@@ -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. A line in its place says the check is off.
|
||||
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)) {
|
||||
|
||||
@@ -119,6 +119,11 @@ function show(walletIdx, addrIdx) {
|
||||
|
||||
$("delete-address-recovery").textContent = recoveryPathText(wallet);
|
||||
$("delete-address-balance").innerHTML = balanceWarningHtml(addr);
|
||||
// Only Blockscout reports the tokens the user has not tracked.
|
||||
$("delete-address-tokens-note").classList.toggle(
|
||||
"hidden",
|
||||
!!state.useBlockscout,
|
||||
);
|
||||
|
||||
setFlash("");
|
||||
showView("delete-address-confirm");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,10 @@ const {
|
||||
truncateAmountNeverZero,
|
||||
isBelowOneMillionth,
|
||||
} = require("../../shared/amountDisplay");
|
||||
const { resolveTokenSymbol } = require("../../shared/approvalAmount");
|
||||
const {
|
||||
resolveTokenDecimals,
|
||||
resolveTokenSymbol,
|
||||
} = require("../../shared/approvalAmount");
|
||||
const { getProvider } = require("../../shared/balances");
|
||||
const { DEBUG } = require("../../shared/constants");
|
||||
const { escapeHtml } = require("../../shared/html");
|
||||
@@ -306,10 +309,16 @@ function nativeCurrency() {
|
||||
// names a token on one network only, and every token balance held is the
|
||||
// active network's, so another network gets the tokens tracked on it
|
||||
// (state.networkTrackedTokens, see applyChainSwitchFields()) and no balances.
|
||||
//
|
||||
// The wallets' balances hold what Blockscout reported for each token. So the
|
||||
// wallets are returned only while "Use Blockscout" is on.
|
||||
function tokenSourcesByChainId(chainId) {
|
||||
const net = chainId ? networkByChainId(chainId) : currentNetwork();
|
||||
if (net && net.id === state.networkId) {
|
||||
return { trackedTokens: state.trackedTokens, wallets: state.wallets };
|
||||
return {
|
||||
trackedTokens: state.trackedTokens,
|
||||
wallets: state.useBlockscout ? state.wallets : [],
|
||||
};
|
||||
}
|
||||
return {
|
||||
trackedTokens: (net && state.networkTrackedTokens[net.id]) || [],
|
||||
@@ -317,11 +326,39 @@ function tokenSourcesByChainId(chainId) {
|
||||
};
|
||||
}
|
||||
|
||||
// The symbol shown for a token line on the network with `chainId`, or on the
|
||||
// active network when none is given: from the bundled list, the tokens the
|
||||
// user tracks, and the explorer's report, the sources the amount line's scale
|
||||
// comes from. Null when no source names one, so the token lines keep saying
|
||||
// `Unknown token` for a token nothing knows.
|
||||
// The line shown under a token while "Use Blockscout" is off. The token gets
|
||||
// the line only when the bundled list and the tracked tokens lack its name or
|
||||
// decimals. Otherwise this returns "". The token is a contract address on the
|
||||
// network with `chainId`, or on the 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 restored from storage unchecked.
|
||||
function blockscoutOffTokenNoteHtml(token, chainId) {
|
||||
if (typeof token !== "string") return "";
|
||||
if (state.useBlockscout || !token || token === "ETH") return "";
|
||||
const sources = tokenSourcesByChainId(chainId);
|
||||
if (
|
||||
resolveTokenSymbol(token, sources) !== null &&
|
||||
resolveTokenDecimals(token, sources) !== null
|
||||
) {
|
||||
return "";
|
||||
}
|
||||
return (
|
||||
'<div class="text-xs text-muted">Blockscout\'s report of your balances' +
|
||||
' is not used to name this token, because "Use Blockscout" is off in' +
|
||||
" Settings.</div>"
|
||||
);
|
||||
}
|
||||
|
||||
// The symbol shown for a token line, from these sources in order:
|
||||
//
|
||||
// - the bundled list
|
||||
// - the tokens the user tracks
|
||||
// - the explorer's report, while "Use Blockscout" is on
|
||||
//
|
||||
// The network is the one with `chainId`, or the active network when none is
|
||||
// given. The amount line's scale comes from the same sources. Null when no
|
||||
// source names one, so the token lines keep saying `Unknown token` for a token
|
||||
// nothing knows.
|
||||
function tokenLabel(address, chainId) {
|
||||
return resolveTokenSymbol(address, tokenSourcesByChainId(chainId));
|
||||
}
|
||||
@@ -534,6 +571,12 @@ function addressTitle(address, wallets) {
|
||||
const CONTRACT_CREATION_TEXT =
|
||||
"This transaction creates a new contract. It has no recipient.";
|
||||
|
||||
// The line shown in place of the transaction history on Home, AddressDetail and
|
||||
// AddressToken while "Use Blockscout" is off. That 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
|
||||
@@ -791,6 +834,7 @@ module.exports = {
|
||||
unknownableAmount,
|
||||
nativeCurrency,
|
||||
tokenSourcesByChainId,
|
||||
blockscoutOffTokenNoteHtml,
|
||||
tokenLabel,
|
||||
formatFee,
|
||||
addressDotHtml,
|
||||
@@ -799,6 +843,7 @@ module.exports = {
|
||||
displaySymbol,
|
||||
addressTitle,
|
||||
CONTRACT_CREATION_TEXT,
|
||||
HISTORY_OFF_TEXT,
|
||||
txCounterpartyHtml,
|
||||
formatAddressHtml,
|
||||
renderAddressHtml,
|
||||
|
||||
@@ -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>';
|
||||
@@ -300,6 +305,8 @@ function walletListHtml() {
|
||||
|
||||
function render(ctx) {
|
||||
const container = $("wallet-list");
|
||||
// Only Blockscout reports the tokens the user has not tracked.
|
||||
$("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>';
|
||||
@@ -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();
|
||||
|
||||
+12
-7
@@ -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 = `<option value="ETH">${escapeHtml(nativeCurrency())}</option>`;
|
||||
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();
|
||||
});
|
||||
|
||||
+73
-12
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 += `<div class="font-bold">${escapeHtml(sym)}</div>`;
|
||||
html += blockscoutOffTokenNoteHtml(d.address, chainId);
|
||||
html += toAddressHtml(d.address, chainId);
|
||||
} else {
|
||||
html += toAddressHtml(d.address, chainId);
|
||||
@@ -345,6 +352,7 @@ function showError(txInfo, txHash, message) {
|
||||
state.viewData = {
|
||||
amount: txInfo.amount,
|
||||
symbol: symbol,
|
||||
token: txInfo.token,
|
||||
to: txInfo.to,
|
||||
hash: txHash || null,
|
||||
message: message,
|
||||
@@ -358,6 +366,10 @@ function renderError() {
|
||||
if (!d || !d.message) return;
|
||||
$("error-tx-summary").textContent =
|
||||
d.amount + (d.symbol ? " " + d.symbol : "");
|
||||
$("error-tx-token-note").innerHTML = blockscoutOffTokenNoteHtml(
|
||||
d.token,
|
||||
d.chainId,
|
||||
);
|
||||
$("error-tx-to").innerHTML = toAddressHtml(d.to, d.chainId);
|
||||
$("error-tx-message").textContent = d.message;
|
||||
|
||||
|
||||
@@ -1,7 +1,37 @@
|
||||
const { $ } = require("./helpers");
|
||||
// The first screen, shown while there is no wallet. It offers "Use Blockscout"
|
||||
// and "Check recipients on Etherscan", which Settings offers too. Both are off
|
||||
// until the user ticks them.
|
||||
|
||||
function init(ctx) {
|
||||
$("btn-welcome-add").addEventListener("click", ctx.showAddWalletView);
|
||||
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");
|
||||
}
|
||||
|
||||
module.exports = { init };
|
||||
// Each change is made inside the save, holding the lock, as Settings makes it.
|
||||
function init(ctx) {
|
||||
$("btn-welcome-add").addEventListener("click", ctx.showAddWalletView);
|
||||
|
||||
$("welcome-use-blockscout").addEventListener("change", async () => {
|
||||
const on = $("welcome-use-blockscout").checked;
|
||||
await saveState(() => {
|
||||
state.useBlockscout = on;
|
||||
});
|
||||
});
|
||||
|
||||
$("welcome-check-recipients-on-etherscan").addEventListener(
|
||||
"change",
|
||||
async () => {
|
||||
const on = $("welcome-check-recipients-on-etherscan").checked;
|
||||
await saveState(() => {
|
||||
state.checkRecipientsOnEtherscan = on;
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = { init, show };
|
||||
|
||||
Reference in New Issue
Block a user