fix: show balances and fees below 0.000001 as nonzero on the send screens (closes #343)
check / check (push) Successful in 2m28s
e2e / e2e-chrome (push) Successful in 3m15s
e2e / e2e-firefox (push) Successful in 2m38s

The stored ETH and token balances and the send-confirm screen's fee were each
cut to six decimal places, so a value below 0.000001 read as zero. Balances are
now stored exactly; a token declaring more than 18 decimals is cut to 18, the
most the balance check reads. A token holding below 0.000001 is still left off
the lists as dust, except for a token the user tracks, whose holding now
reaches the Send screen and is what the send is checked against. The send and
send-confirm screens' balances, reserve and insufficient-balance messages go
through truncateAmountNeverZero(). The send-confirm and approval screens both
render the fee through formatFee(), which prices the exact fee in USD. The
balance lists still round with toFixed(4).

Model: opus-5-5
This commit is contained in:
2026-10-04 04:45:02 +00:00
committed by sneak
parent 49a7da87e8
commit 090a5e785c
10 changed files with 494 additions and 82 deletions
+25 -13
View File
@@ -892,12 +892,22 @@ shows is a V4 exact-in `amountIn` of zero.
The rule and its exception live in `src/shared/amountDisplay.js` as
`truncateAmount()` and `truncateAmountNeverZero()`. Everything the approval and
confirmation screens display goes through the floored one — the ERC-20 amount,
the ETH value and max fee (`src/popup/views/approval.js`), and the swap's
`Amount` and `Min. received` lines (`src/shared/uniswap.js`). The history and
balance lists (`src/shared/transactions.js`) use the unfloored one: the
transaction detail view is the authoritative record and already shows exact
precision. The 4-decimal rule is unchanged everywhere else, including for
amounts at or above the floor on the approval screens.
the ETH value and max fee (`src/popup/views/approval.js`), the swap's `Amount`
and `Min. received` lines (`src/shared/uniswap.js`), the Send screen's
`Current balance` (`src/popup/views/send.js`), and the balance and network fee
on the confirmation screen for the wallet's own send
(`src/popup/views/confirmTx.js`). Both screens render a network fee through
`formatFee()` in `src/popup/views/helpers.js`, which prices the exact fee in USD
rather than its truncated figure, so the same fee reads the same on both, USD
value included. Balances are stored exactly (`src/shared/balances.js`), so a
balance below the floor reaches these screens as it is; the one cut is that a
token declaring more than 18 decimals is stored to 18, the most the confirmation
screen's balance check reads. The history list (`src/shared/transactions.js`)
uses the unfloored one: the transaction detail view is the authoritative record
and already shows exact precision. The balance lists use neither: they round to
four places with `toFixed(4)` (`balanceLine()` in `src/popup/views/helpers.js`).
The 4-decimal rule is unchanged everywhere else, including for amounts at or
above the floor on the approval screens.
The floor applies only where the token's scale is known. Where it is not, the
approval screen states base units instead of a quantity — see Unknown token
@@ -1050,13 +1060,15 @@ Which tokens an address shows is decided by `fetchTokenBalances()` in
`src/shared/balances.js`, from the Blockscout `token-balances` response, so
tokens do appear without the user adding them. An ERC-20 is shown when its
balance is nonzero and it is in the bundled known-token list, is tracked by the
user, or has 1,000 or more holders; a token claiming a symbol from the bundled
list from any other contract address is always dropped, and so is any token
claiming a symbol that belongs to the native asset and therefore has no
legitimate contract at all (`"ETH"`). That filter is unconditional — the "Hide
tokens with fewer than 1,000 holders" setting governs the transaction history
and the send-screen token selector, not this list. Tracked tokens with a zero
balance are listed as well while "Show tracked tokens with zero balance" is on.
user, or has 1,000 or more holders. A holding below 0.000001 of a token the user
does not track is dust and is not shown; a tracked token's holding is shown
whatever its size. A token claiming a symbol from the bundled list from any
other contract address is always dropped, and so is any token claiming a symbol
that belongs to the native asset and therefore has no legitimate contract at all
(`"ETH"`). That filter is unconditional — the "Hide tokens with fewer than 1,000
holders" setting governs the transaction history and the send-screen token
selector, not this list. Tracked tokens with a zero balance are listed as well
while "Show tracked tokens with zero balance" is on.
#### Stored state and its version