Show tracked tokens with zero balance on main and address pages
Some checks failed
check / check (push) Has been cancelled

Add showZeroBalanceTokens setting (default: on). When enabled,
balanceLinesForAddress merges state.trackedTokens with the address's
tokenBalances, showing 0.0000 lines for tracked tokens that have no
balance on that address. This gives users visibility into all tokens
they're watching across all addresses.
This commit is contained in:
2026-02-26 15:37:39 +07:00
parent 6c4b2753d9
commit 47e690f466
6 changed files with 45 additions and 4 deletions

View File

@@ -502,6 +502,19 @@
</button>
</div>
<div class="bg-well p-3 mx-1 mb-3">
<h3 class="font-bold mb-1">Display</h3>
<label
class="text-xs flex items-center gap-1 cursor-pointer"
>
<input
type="checkbox"
id="settings-show-zero-balances"
/>
Show tracked tokens with zero balance
</label>
</div>
<div class="bg-well p-3 mx-1 mb-3">
<h3 class="font-bold mb-1">Ethereum RPC</h3>
<p class="text-xs text-muted mb-1">

View File

@@ -38,7 +38,11 @@ function show() {
} else {
ensEl.classList.add("hidden");
}
$("address-balances").innerHTML = balanceLinesForAddress(addr);
$("address-balances").innerHTML = balanceLinesForAddress(
addr,
state.trackedTokens,
state.showZeroBalanceTokens,
);
renderSendTokenSelect(addr);
$("tx-list").innerHTML =
'<div class="text-muted text-xs py-1">Loading...</div>';

View File

@@ -85,16 +85,24 @@ function balanceLine(symbol, amount, price) {
);
}
function balanceLinesForAddress(addr) {
function balanceLinesForAddress(addr, trackedTokens, showZero) {
let html = balanceLine(
"ETH",
parseFloat(addr.balance || "0"),
getPrice("ETH"),
);
const seen = new Set();
for (const t of addr.tokenBalances || []) {
const bal = parseFloat(t.balance || "0");
if (bal === 0) continue;
if (bal === 0 && !showZero) continue;
html += balanceLine(t.symbol, bal, getPrice(t.symbol));
seen.add(t.address.toLowerCase());
}
if (showZero && trackedTokens) {
for (const t of trackedTokens) {
if (seen.has(t.address.toLowerCase())) continue;
html += balanceLine(t.symbol, 0, getPrice(t.symbol));
}
}
return html;
}

View File

@@ -57,7 +57,11 @@ function render(ctx) {
html += `<span class="flex items-center" style="width:42ch;max-width:100%">${addr.ensName ? "" : dot}${truncateMiddle(addr.address, 40)}</span>`;
html += `<span class="text-right text-muted flex-1">${addrUsd}</span>`;
html += `</div>`;
html += balanceLinesForAddress(addr);
html += balanceLinesForAddress(
addr,
state.trackedTokens,
state.showZeroBalanceTokens,
);
html += `</div>`;
});

View File

@@ -113,6 +113,12 @@ function init(ctx) {
showFlash("Saved.");
});
$("settings-show-zero-balances").checked = state.showZeroBalanceTokens;
$("settings-show-zero-balances").addEventListener("change", async () => {
state.showZeroBalanceTokens = $("settings-show-zero-balances").checked;
await saveState();
});
$("settings-hide-low-holders").checked = state.hideLowHolderTokens;
$("settings-hide-low-holders").addEventListener("change", async () => {
state.hideLowHolderTokens = $("settings-hide-low-holders").checked;

View File

@@ -18,6 +18,7 @@ const DEFAULT_STATE = {
allowedSites: {},
deniedSites: {},
rememberSiteChoice: true,
showZeroBalanceTokens: true,
hideLowHolderTokens: true,
hideFraudContracts: true,
hideDustTransactions: true,
@@ -44,6 +45,7 @@ async function saveState() {
allowedSites: state.allowedSites,
deniedSites: state.deniedSites,
rememberSiteChoice: state.rememberSiteChoice,
showZeroBalanceTokens: state.showZeroBalanceTokens,
hideLowHolderTokens: state.hideLowHolderTokens,
hideFraudContracts: state.hideFraudContracts,
hideDustTransactions: state.hideDustTransactions,
@@ -78,6 +80,10 @@ async function loadState() {
saved.rememberSiteChoice !== undefined
? saved.rememberSiteChoice
: true;
state.showZeroBalanceTokens =
saved.showZeroBalanceTokens !== undefined
? saved.showZeroBalanceTokens
: true;
state.hideLowHolderTokens =
saved.hideLowHolderTokens !== undefined
? saved.hideLowHolderTokens