fix: store an absent explorer decimals as unknown instead of fabricating 18 (closes #349)
fetchTokenBalances() did parseInt(item.token.decimals || "18", 10) before writing to state.wallets[].addresses[].tokenBalances[].decimals, so a token whose decimals() reverts -- one the block explorer reports no scale for -- was stored with a fabricated 18 that no reader could tell from a real one. That is upstream of a rule already merged. #306 made the ERC-20 approval amount line resolve the real scale or refuse to format, and #340 extended it to the swap lines; both read this stored value as an authoritative source, so the guess walked straight past refusals that were intact and simply never fired. A 1,000-unit approval of such a token rendered 0.000000001 on the one screen whose job is to state what is being authorized. The stored value is now the explorer's own answer or null, never a default. Both approval paths reach unknownDecimalsAmount() on a null, using the refusal that was already there. The history list's token transfers carried the same || "18" and now state exact base units with the scale unknown rather than a quantity at a guessed one. A holding whose scale nothing knows has no quantity either, so its balance is stored as null -- unknown, never zero -- and the balance list, the address USD total, the Send screen and the confirmation screen each say so rather than printing 0.0000 for money that is really there. The zero-balance filter moved onto the base-unit integer, where it needs no scale at all. The bundled token list and the user's tracked tokens already outrank the explorer, so a token either of them knows still displays its real quantity when the explorer's entry omits decimals; only what none of the three knows is unknown. The uint8 check is one shared toDecimals() rather than three copies of it, and it answers 0 for a real scale of zero: || "18" collapsed that to eighteen, the falsy-collapse trap of #246. Existing installs hold 18s that cannot be told apart retroactively -- that is the defect, and no migration can undo it. They display exactly as they do today until the next balance refresh, which rewrites tokenBalances wholesale and needs no user action. The schema version is not bumped: version 1 records stay valid and are read exactly as before. The only 18s left in src/ are native ETH's real scale in uniswap.js and the fixed-point comparison scale in txValidation.js.
This commit is contained in:
27
TODO.md
27
TODO.md
@@ -97,6 +97,33 @@ but the review is broader than any of them.
|
||||
`src/shared/restorableViews.js`, since `persistedState.js` requires it and
|
||||
that module is in the background bundle.
|
||||
|
||||
- 2026-08-23: An explorer that reports no `decimals` for a token no longer has a
|
||||
scale invented for it before storage
|
||||
([#349](https://git.eeqj.de/sneak/AutistMask/issues/349)).
|
||||
`fetchTokenBalances()` did `parseInt(item.token.decimals || "18", 10)` on the
|
||||
way in, so a token whose `decimals()` reverts was written to
|
||||
`tokenBalances[].decimals` as a fabricated `18` that no reader could tell from
|
||||
a real one. That is upstream of the resolve-or-refuse rule
|
||||
([#306](https://git.eeqj.de/sneak/AutistMask/issues/306),
|
||||
[#340](https://git.eeqj.de/sneak/AutistMask/issues/340)): both approval paths
|
||||
read this stored value as an authoritative source, so the guess walked past
|
||||
refusals that were intact and simply never fired. The stored value is now the
|
||||
explorer's own answer or `null`, and both the ERC-20 amount line and the swap
|
||||
lines reach `unknownDecimalsAmount()` on it. The history list's token
|
||||
transfers carried the same `|| "18"` and now state base units with the scale
|
||||
unknown rather than a quantity. A holding whose scale nothing knows carries
|
||||
`balance: null` — unknown, not zero — and the balance list, the USD total, the
|
||||
Send screen and the confirmation screen each say so instead of printing
|
||||
`0.0000` for money that is really there. The uint8 check is one shared
|
||||
`toDecimals()` rather than three copies, and it answers `0` for a real scale
|
||||
of zero: `|| "18"` collapsed that to eighteen, the trap of
|
||||
[#246](https://git.eeqj.de/sneak/AutistMask/issues/246). Existing installs
|
||||
hold `18`s that cannot be told apart retroactively; they display exactly as
|
||||
they do today until the next balance refresh, which rewrites `tokenBalances`
|
||||
wholesale and needs no user action. The only `18`s left in `src/` are native
|
||||
ETH's real scale in `src/shared/uniswap.js` and the fixed-point comparison
|
||||
scale in `src/shared/txValidation.js`.
|
||||
|
||||
- 2026-08-23: The background no longer reads or writes the shared `state`
|
||||
singleton ([#324](https://git.eeqj.de/sneak/AutistMask/issues/324)), which
|
||||
also closes the cold-worker wrong-chain send
|
||||
|
||||
Reference in New Issue
Block a user