fix: transaction screens name a token from the tokens of the transaction's network (closes #629)
check / check (push) Canceled after 0s
e2e / e2e-chrome (push) Canceled after 0s
e2e / e2e-firefox (push) Canceled after 0s

decodeCalldata() looked a token's symbol and decimals up in the active
network's tracked tokens and balances, whatever chain id it was given, so
TransactionDetail and TxApproval showed a token tracked on another network
by address only, with its amount in base units. The lookup now uses what the
wallet knows of the tokens on the transaction's network: the active
network's tracked tokens and balances, or for another network the tokens
tracked on it and no balances. TxApproval's token labels, the symbol it
hands the wait, success and error screens, and SuccessTx's decoded token
label use the same lookup.

Model: opus-5-5
This commit is contained in:
2026-10-20 01:18:49 +00:00
parent 3aed2bb5f0
commit 7c4f2e5525
7 changed files with 351 additions and 43 deletions
+17 -16
View File
@@ -1116,22 +1116,23 @@ with the same floor (`.5` reads `.5000`).
**Specific Exception — unknown token scale:** Calldata carries base units and no
scale, so every amount on the dApp approval screen needs the token's `decimals`.
It is resolved from the bundled token list, then from the tokens the user
tracks, then from what the block explorer reported for the contract
(`resolveTokenDecimals()` in `src/shared/approvalAmount.js`). Where none of them
answers, the amount is not formatted: the line reads
`5000000000 base units (decimals unknown)`. A guessed scale is not an
approximation but a different number — 1,000 units of a 6-decimal token
formatted at 18 decimals reads `0.000000001` — on the screen whose only job is
to state what is being authorized. Both amount paths of that screen take this
rule: the ERC-20 `transfer`/`approve` line (`src/popup/views/approval.js`) and
the swap's `Amount` and `Min. received` lines (`src/shared/uniswap.js`). The
token permission warning on the signature screen takes the same rule for its
amounts. An unbounded allowance or permit needs no scale to describe and is
still shown as `Unlimited`. A source's answer counts only if it is a whole
number from 0 to 80: `decimals()` returns a `uint8`, but `formatUnits()` cannot
format more than 80 decimal places, so a token that reports 81 to 255 is shown
as one whose scale nothing knows.
It is resolved from the bundled token list, then from the tokens the user tracks
on the network the transaction is for, then, when that is the active network,
from what the block explorer reported for the contract in the wallet's balances,
which are the active network's only (`resolveTokenDecimals()` in
`src/shared/approvalAmount.js`). Where none of them answers, the amount is not
formatted: the line reads `5000000000 base units (decimals unknown)`. A guessed
scale is not an approximation but a different number — 1,000 units of a
6-decimal token formatted at 18 decimals reads `0.000000001` — on the screen
whose only job is to state what is being authorized. Both amount paths of that
screen take this rule: the ERC-20 `transfer`/`approve` line
(`src/popup/views/approval.js`) and the swap's `Amount` and `Min. received`
lines (`src/shared/uniswap.js`). The token permission warning on the signature
screen takes the same rule for its amounts. An unbounded allowance or permit
needs no scale to describe and is still shown as `Unlimited`. A source's answer
counts only if it is a whole number from 0 to 80: `decimals()` returns a
`uint8`, but `formatUnits()` cannot format more than 80 decimal places, so a
token that reports 81 to 255 is shown as one whose scale nothing knows.
The rule holds only if nothing invents a scale UPSTREAM of it. Those three
sources are read as authoritative, so a value written into one of them cannot be