fix: the address screen shows a token just added at its fetched balance, not 0.0000 (closes #516)
check / check (push) Canceled after 0s
e2e / e2e-chrome (push) Canceled after 0s
e2e / e2e-firefox (push) Canceled after 0s

A balance refresh that returns now draws the balances on AddressDetail or
AddressToken again when that screen is up, not only the wallet list; their
transaction lists are left alone. Every add path on AddToken and
SettingsAddToken tracks a token through one step, trackToken(), which also
marks it not yet fetched: its line reads `quantity unknown` instead of 0.0000
until a refresh that started with it tracked has fetched every address's
token balances. To tell an address whose token balances the explorer did not
return, the refresh takes them off its copy of the wallets before fetching.

Model: opus-5-5
This commit is contained in:
2026-10-21 03:17:01 +00:00
parent e2965469ba
commit 33d99ef94d
10 changed files with 404 additions and 53 deletions
+9 -2
View File
@@ -1,4 +1,11 @@
const { $, showView, showFlash, escapeHtml, goBack } = require("./helpers");
const {
$,
showView,
showFlash,
escapeHtml,
goBack,
trackToken,
} = require("./helpers");
const { getTopTokens } = require("../../shared/tokenList");
const { state, saveState, currentNetwork } = require("../../shared/state");
const { lookupTokenInfo } = require("../../shared/balances");
@@ -72,7 +79,7 @@ function init(ctx) {
return;
}
log.infof("Adding token", info.symbol, contractAddr);
state.trackedTokens.push({
trackToken({
address: contractAddr,
symbol: info.symbol,
decimals: info.decimals,
+17 -8
View File
@@ -65,11 +65,25 @@ function show() {
});
$("address-line").dataset.full = addr.address;
attachCopyHandlers($("address-line"));
const usdTotal = formatAddressTotal(getAddressValue(addr));
$("address-usd-total").innerHTML = escapeHtml(usdTotal) || " ";
const ensEl = $("address-ens");
// ENS is now shown inside renderAddressHtml, hide the separate element
ensEl.classList.add("hidden");
renderBalances();
renderSendTokenSelect(addr);
$("tx-list").innerHTML =
'<div class="text-muted text-xs py-1">Loading...</div>';
showView("address");
loadTransactions(addr.address);
}
// The USD total and the balance list. Drawn again by each balance refresh
// that returns while this screen is up (doRefreshAndRender() in
// src/popup/index.js), which leaves the rest of the screen as it is.
function renderBalances() {
const addr =
state.wallets[state.selectedWallet].addresses[state.selectedAddress];
const usdTotal = formatAddressTotal(getAddressValue(addr));
$("address-usd-total").innerHTML = escapeHtml(usdTotal) || "&nbsp;";
$("address-balances").innerHTML = balanceLinesForAddress(
addr,
state.trackedTokens,
@@ -83,11 +97,6 @@ function show() {
ctx.showAddressToken();
});
});
renderSendTokenSelect(addr);
$("tx-list").innerHTML =
'<div class="text-muted text-xs py-1">Loading...</div>';
showView("address");
loadTransactions(addr.address);
}
let loadedTxs = [];
@@ -280,4 +289,4 @@ function init(_ctx) {
exportPrivkey.init();
}
module.exports = { init, show };
module.exports = { init, show, renderBalances };
+46 -32
View File
@@ -13,6 +13,7 @@ const {
nativeCurrency,
balanceLine,
unknownableAmount,
tokensNotFetched,
renderAddressHtml,
attachCopyHandlers,
goBack,
@@ -48,37 +49,18 @@ function show() {
const addr = wallet.addresses[state.selectedAddress];
const ai = state.selectedAddress;
const tokenId = state.selectedToken;
// Determine token symbol and balance
let symbol, amount, price;
const knownToken = TOKEN_BY_ADDRESS.get(tokenId.toLowerCase());
if (tokenId === "ETH") {
symbol = nativeCurrency();
amount = parseFloat(addr.balance || "0");
price = getPrice("ETH");
} else {
const tb = (addr.tokenBalances || []).find(
(t) => t.address.toLowerCase() === tokenId.toLowerCase(),
);
symbol = resolveSymbol(
tokenId,
addr.tokenBalances,
state.trackedTokens,
);
// null when the scale is unknown: no quantity to show, and none to
// price. balanceLine() states that rather than printing 0.0000.
amount = tb ? unknownableAmount(tb.balance) : 0;
price = getPrice(symbol);
}
currentSymbol = symbol;
currentSymbol =
tokenId === "ETH"
? nativeCurrency()
: resolveSymbol(tokenId, addr.tokenBalances, state.trackedTokens);
$("address-token-title").textContent =
wallet.name +
" \u2014 Address " +
(ai + 1) +
" \u2014 " +
displaySymbol(symbol);
displaySymbol(currentSymbol);
// Blockie
const blockieEl = $("address-token-jazzicon");
@@ -100,13 +82,7 @@ function show() {
$("address-token-line").dataset.full = addr.address;
attachCopyHandlers($("address-token-line"));
// USD total for this token only
const usdVal = price && amount !== null ? amount * price : null;
const usdStr = formatUsd(usdVal);
$("address-token-usd-total").innerHTML = escapeHtml(usdStr) || "&nbsp;";
// Single token balance line (no tokenId — not clickable here)
$("address-token-balance").innerHTML = balanceLine(symbol, amount, price);
renderBalance();
// Token contract details (ERC-20 only)
const contractInfo = $("address-token-contract-info");
@@ -168,6 +144,44 @@ function show() {
loadTransactions(addr.address, tokenId);
}
// The USD total and the balance line of the selected token. Drawn again by
// each balance refresh that returns while this screen is up
// (doRefreshAndRender() in src/popup/index.js), which leaves the rest of the
// screen as it is.
function renderBalance() {
const addr =
state.wallets[state.selectedWallet].addresses[state.selectedAddress];
const tokenId = state.selectedToken;
let amount, price;
if (tokenId === "ETH") {
amount = parseFloat(addr.balance || "0");
price = getPrice("ETH");
} else {
const id = tokenId.toLowerCase();
const tb = (addr.tokenBalances || []).find(
(t) => t.address.toLowerCase() === id,
);
// null when the scale is unknown, or when no refresh has fetched a
// token just added: no quantity to show, and none to price.
// balanceLine() states that rather than printing 0.0000.
if (tb) amount = unknownableAmount(tb.balance);
else amount = tokensNotFetched.has(id) ? null : 0;
price = getPrice(currentSymbol);
}
// USD total for this token only
const usdVal = price && amount !== null ? amount * price : null;
const usdStr = formatUsd(usdVal);
$("address-token-usd-total").innerHTML = escapeHtml(usdStr) || "&nbsp;";
// Single token balance line (no tokenId — not clickable here)
$("address-token-balance").innerHTML = balanceLine(
currentSymbol,
amount,
price,
);
}
async function loadTransactions(address, tokenId) {
try {
const rawTxs = await fetchRecentTransactions(
@@ -343,4 +357,4 @@ function init(_ctx) {
});
}
module.exports = { init, show };
module.exports = { init, show, renderBalance };
+22 -3
View File
@@ -380,6 +380,22 @@ function balanceLine(symbol, amount, price, tokenId) {
);
}
// Contract addresses, lowercased, of the tokens the user added that no balance
// refresh has fetched yet. An address with no balance row for one of them has
// no known balance of it, so its line says the quantity is unknown rather than
// 0.0000. trackToken() adds a token here, and doRefreshAndRender() in
// src/popup/index.js takes it off once a refresh that started with it tracked
// has fetched every address's token balances
// (https://git.eeqj.de/sneak/AutistMask/issues/516).
const tokensNotFetched = new Set();
// Tracks `token` on the active network, as every add path on AddToken and
// SettingsAddToken does. No refresh has fetched its balance yet.
function trackToken(token) {
state.trackedTokens.push(token);
tokensNotFetched.add(token.address.toLowerCase());
}
function balanceLinesForAddress(addr, trackedTokens, showZero) {
let html = balanceLine(
nativeCurrency(),
@@ -409,12 +425,13 @@ function balanceLinesForAddress(addr, trackedTokens, showZero) {
}
if (showZero && trackedTokens) {
for (const t of trackedTokens) {
if (seen.has(t.address.toLowerCase())) continue;
const id = t.address.toLowerCase();
if (seen.has(id)) continue;
html += balanceLine(
t.symbol,
0,
tokensNotFetched.has(id) ? null : 0,
getPrice(t.symbol),
t.address.toLowerCase(),
id,
);
}
}
@@ -786,6 +803,8 @@ module.exports = {
flashCopyFeedback,
balanceLine,
balanceLinesForAddress,
tokensNotFetched,
trackToken,
addressHoldsFunds,
keyMaterialName,
unknownableAmount,
+11 -4
View File
@@ -1,4 +1,11 @@
const { $, showView, showFlash, escapeHtml, goBack } = require("./helpers");
const {
$,
showView,
showFlash,
escapeHtml,
goBack,
trackToken,
} = require("./helpers");
const { getTopTokens } = require("../../shared/tokenList");
const { state, saveState, currentNetwork } = require("../../shared/state");
const { lookupTokenInfo } = require("../../shared/balances");
@@ -43,7 +50,7 @@ function renderTop10() {
decimals: parseInt(btn.dataset.decimals, 10),
name: btn.dataset.name || btn.dataset.symbol,
};
state.trackedTokens.push(token);
trackToken(token);
await saveState();
showFlash("Added " + token.symbol);
renderTop10();
@@ -110,7 +117,7 @@ function init(_ctx) {
decimals: parseInt(opt.dataset.decimals, 10),
name: opt.dataset.name || opt.dataset.symbol,
};
state.trackedTokens.push(token);
trackToken(token);
await saveState();
showFlash("Added " + token.symbol);
renderTop10();
@@ -156,7 +163,7 @@ function init(_ctx) {
return;
}
log.infof("Adding token", info.symbol, addr);
state.trackedTokens.push({
trackToken({
address: addr,
symbol: info.symbol,
decimals: info.decimals,