The approval and transaction-status screens read a token's scale from the
bundled list, the tokens the user tracks, then the block explorer, but read
its symbol from the bundled list alone. A token the user added by hand was
scaled correctly yet labelled "Unknown token", and a non-bundled ERC-20 was
carried onto the wait screen as ETH.
resolveTokenSymbol() now draws the symbol through the same sources and
precedence as the scale, and the ERC-20 and Uniswap swap lines both use it. A
tracked or explorer-reported name stays subject to the spoof rule, so it
cannot claim a bundled or native ticker.
Folds in #354.
Model: opus-4-8
decodeCalldata consulted only the 512-entry bundled list and defaulted to 18
decimals, so a transfer of 5,000 units of a 6-decimal token rendered
"Amount 0.0000" and the user confirmed a drain reading zero. The same
understatement applied to approve, where an unbounded allowance also rendered
0.0000.
Decimals now resolve from the bundled list, then trackedTokens, then the
address's explorer-reported entry, with uint8 validation and a refusal when
sources for one contract disagree. When no source knows the scale, no
formatUnits call is reached at all: the line renders raw base units with an
explicit "decimals unknown" warning, and the same string reaches
pendingTxDetails.amount so the status screens carry no formatted figure either.
Verified failing first two independent ways: restoring the old
`token ? token.decimals : 18` fails 6 of 15 new tests with the unknown case
reporting "0.0000"; making the resolver return 18 rather than null on the
unknown path fails a different 6, spanning resolver and render levels.