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
trackToken(), which gives the tracked record `balanceNotFetched: true`. While
the field is set, the token's line reads `quantity unknown` instead of 0.0000.
refreshBalances() now resolves with the tracked tokens whose balance it read
at every address. The popup's refresh deletes the field from those tokens in
the stored record and leaves the rest of the stored list as it is. Removing
the token removes the field, and each network keeps its own records.

Model: opus-5-5
This commit is contained in:
2026-11-02 23:24:57 +00:00
parent 664629a1b6
commit 49f4c9d759
19 changed files with 670 additions and 63 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");
@@ -88,7 +95,7 @@ function init(ctx) {
return;
}
log.infof("Adding token", info.symbol, contractAddr);
state.trackedTokens.push({
trackToken({
address: contractAddr,
symbol: info.symbol,
decimals: info.decimals,
+19 -10
View File
@@ -67,11 +67,27 @@ 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();
// 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>';
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,
@@ -85,13 +101,6 @@ 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>';
showView("address");
loadTransactions(addr.address);
}
let loadedTxs = [];
@@ -320,4 +329,4 @@ function init(_ctx) {
exportPrivkey.init();
}
module.exports = { init, show };
module.exports = { init, show, renderBalances };
+48 -32
View File
@@ -50,37 +50,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(tokenId);
}
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");
@@ -102,13 +83,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");
@@ -170,6 +145,47 @@ 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,
);
const tracked = state.trackedTokens.find(
(t) => t.address.toLowerCase() === id,
);
// null when the scale is unknown, or when no refresh has read the
// balance of 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 = tracked && tracked.balanceNotFetched ? null : 0;
price = getPrice(tokenId);
}
// 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) {
const networkId = state.networkId;
try {
@@ -383,4 +399,4 @@ function init(_ctx) {
});
}
module.exports = { init, show };
module.exports = { init, show, renderBalance };
+11 -1
View File
@@ -418,6 +418,15 @@ function balanceLine(symbol, amount, price, tokenId) {
);
}
// Tracks `token` on the active network, as every add path on AddToken and
// SettingsAddToken does. The tracked record carries `balanceNotFetched: true`
// until a balance refresh has read the token's balance at every address
// (doRefreshAndRender() in src/popup/index.js). Until then, an address with no
// balance row for the token shows `quantity unknown` for it, not 0.0000.
function trackToken(token) {
state.trackedTokens.push({ ...token, balanceNotFetched: true });
}
function balanceLinesForAddress(addr, trackedTokens, showZero) {
let html = balanceLine(
nativeCurrency(),
@@ -453,7 +462,7 @@ function balanceLinesForAddress(addr, trackedTokens, showZero) {
if (isSpoofedSymbol(t.symbol, t.address)) continue;
html += balanceLine(
t.symbol,
0,
t.balanceNotFetched ? null : 0,
getPrice(t.address),
t.address.toLowerCase(),
);
@@ -833,6 +842,7 @@ module.exports = {
flashCopyFeedback,
balanceLine,
balanceLinesForAddress,
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");
@@ -44,7 +51,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();
@@ -111,7 +118,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();
@@ -167,7 +174,7 @@ function init(_ctx) {
return;
}
log.infof("Adding token", info.symbol, addr);
state.trackedTokens.push({
trackToken({
address: addr,
symbol: info.symbol,
decimals: info.decimals,