fix: tracked tokens' balances are read through the RPC endpoint (closes #669)
refreshBalances() now reads the balance of every token tracked on the active network, at every address, with balanceOf through the same provider as the ETH balance, at the scale the screens read that token at. Blockscout's answer adds only the tokens the user has not tracked, admitted as before. A failed balanceOf keeps that token's previous row and is logged; while Blockscout fails, the untracked rows from before stay. A tracked token's row carries no decimals or holder count from the explorer. The browser test for a hostile symbol now untracks the stub token first, because a tracked token is listed under the symbol it was added with. Model: opus-5-5
This commit is contained in:
@@ -17,11 +17,11 @@ don't implement any crypto, and don't send user-specific data anywhere but a
|
||||
(user-configurable) Ethereum RPC endpoint (which defaults to a public node). The
|
||||
extension contacts three user-configurable services: the configured RPC node for
|
||||
blockchain interactions, a public CoinDesk API (no API key) for realtime price
|
||||
information, and a Blockscout block-explorer API for transaction history and
|
||||
token balances. It also performs best-effort Etherscan address label lookups
|
||||
during transaction confirmation. A community-maintained phishing domain
|
||||
blocklist is built into the extension at build time and checked locally; nothing
|
||||
is fetched for it at runtime.
|
||||
information, and a Blockscout block-explorer API for transaction history and the
|
||||
balances of tokens the user has not tracked. It also performs best-effort
|
||||
Etherscan address label lookups during transaction confirmation. A
|
||||
community-maintained phishing domain blocklist is built into the extension at
|
||||
build time and checked locally; nothing is fetched for it at runtime.
|
||||
|
||||
In the extension is a hardcoded list of the top ERC20 contract addresses. You
|
||||
can add any ERC20 contract by contract address if you wish, but the hardcoded
|
||||
@@ -845,7 +845,7 @@ src/
|
||||
views/ — one JS module per screen (home, send, approval, etc.)
|
||||
shared/ — modules used by both popup and background
|
||||
alarms.js — recurring background jobs (extension alarms API)
|
||||
balances.js — ETH + ERC-20 balance fetching via RPC + Blockscout
|
||||
balances.js — ETH and tracked token balances via RPC, others via Blockscout
|
||||
constants.js — chain IDs, default RPC endpoint, ERC-20 ABI
|
||||
ens.js — ENS forward/reverse resolution (popup only)
|
||||
holders.js — holder-count parsing and the low-holder rule
|
||||
@@ -1155,13 +1155,14 @@ printing `0.0000` for money that is really there. `0` is a real scale and is
|
||||
never treated as absent.
|
||||
|
||||
`tokenBalances[].decimals` is therefore the explorer's own answer and nothing
|
||||
else, which is not the same question as the scale a screen should render at.
|
||||
Anything that needs the second one calls `resolveTokenDecimals()` — the balance
|
||||
list, the approval and swap lines, and the Send screen, which carries the
|
||||
resolved scale onto the pending transaction for `transferAmount.js` to encode
|
||||
and compare against. Reading the stored field directly instead answers `null`
|
||||
for a bundled or tracked token the explorer merely omitted, which is not a
|
||||
refusal the wallet has any reason to make.
|
||||
else, and `null` in the entry of a tracked token, whose balance is read through
|
||||
the RPC endpoint and not asked of the explorer. It is not the same question as
|
||||
the scale a screen should render at. Anything that needs the second one calls
|
||||
`resolveTokenDecimals()` — the balance list, the approval and swap lines, and
|
||||
the Send screen, which carries the resolved scale onto the pending transaction
|
||||
for `transferAmount.js` to encode and compare against. Reading the stored field
|
||||
directly instead answers `null` for a bundled or tracked token the explorer
|
||||
merely omitted, which is not a refusal the wallet has any reason to make.
|
||||
|
||||
The Send screen also consults every address's explorer reports, so a contract
|
||||
two addresses report different `decimals` for has no scale there, and the stored
|
||||
@@ -1379,25 +1380,31 @@ The core hierarchy is **Wallets → Addresses**:
|
||||
- The user can have multiple wallets, each with multiple addresses (HD) or a
|
||||
single address (key).
|
||||
|
||||
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"`, and every network's `nativeCurrency`, such
|
||||
as `"SepoliaETH"`, on every network). 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. A token's holder count is
|
||||
unknown when the explorer reports none, or reports anything other than a whole
|
||||
number written in digits alone, such as `1,000` or `1e3` (`parseHoldersCount()`
|
||||
in `src/shared/holders.js`). This list does not take an unknown count as 1,000
|
||||
or more, so such a token is shown only when it is on the bundled list or
|
||||
tracked. `fetchTokenBalances()` stores every nonzero holding of a token it
|
||||
admits, however small, but a holding below 0.000001 is left out of the balance
|
||||
lists, the send-screen token selector, the address total and the remove-address
|
||||
warning (`isBelowOneMillionth()` in `src/shared/amountDisplay.js`). The Send and
|
||||
Which tokens an address shows is decided on every refresh by `refreshBalances()`
|
||||
in `src/shared/balances.js`. The balance of each token tracked on the active
|
||||
network is read with `balanceOf` through the RPC endpoint, for every address; a
|
||||
read that fails leaves that token's previous figure, as a failed ETH balance
|
||||
does. Every other token comes from the Blockscout `token-balances` response
|
||||
(`fetchTokenBalances()`), so tokens do appear without the user adding them, and
|
||||
they keep their previous figures while Blockscout fails. A tracked token is
|
||||
shown when its balance is nonzero, and any other ERC-20 when its balance is
|
||||
nonzero and it is in the bundled known-token list or has 1,000 or more holders.
|
||||
A token claiming a symbol from the bundled list from any other contract address
|
||||
is always dropped, tracked or not, and so is any token claiming a symbol that
|
||||
belongs to the native asset and therefore has no legitimate contract at all
|
||||
(`"ETH"`, and every network's `nativeCurrency`, such as `"SepoliaETH"`, on every
|
||||
network). 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. A token's holder count is unknown when the explorer
|
||||
reports none, or reports anything other than a whole number written in digits
|
||||
alone, such as `1,000` or `1e3` (`parseHoldersCount()` in
|
||||
`src/shared/holders.js`); a tracked token's entry has none, because the explorer
|
||||
is not asked about it. This list does not take an unknown count as 1,000 or
|
||||
more, so such a token is shown only when it is on the bundled list or tracked.
|
||||
Every nonzero holding of a token shown is stored exactly, however small, but a
|
||||
holding below 0.000001 is left out of the balance lists, the send-screen token
|
||||
selector, the address total and the remove-address warning
|
||||
(`isBelowOneMillionth()` in `src/shared/amountDisplay.js`). The Send and
|
||||
confirmation screens show it when its token is the one being sent. Tracked
|
||||
tokens with a zero balance are listed as well while "Show tracked tokens with
|
||||
zero balance" is on.
|
||||
@@ -1781,7 +1788,8 @@ view would leave a wallet one click from deletion.
|
||||
etherscan link) plus name, symbol, decimals, holder count and project
|
||||
website where known. The "Holders:" row is left out, not shown as 0, when
|
||||
the token's balance-list entry has no holder count: the explorer did not
|
||||
report a readable one, or the token is not in the balance list
|
||||
report a readable one, the token is tracked, or the token is not in the
|
||||
balance list
|
||||
- Token-filtered transaction list (only this token's transfers)
|
||||
- **Transitions**:
|
||||
- "Send" → **Send** (token locked: the dropdown is replaced by a static
|
||||
@@ -2662,7 +2670,8 @@ AutistMask is not a fully self-contained offline tool. It necessarily
|
||||
communicates with three external services to function as a wallet:
|
||||
|
||||
- **Ethereum JSON-RPC endpoint**: The extension needs an Ethereum node to query
|
||||
balances (`eth_getBalance`), read ERC-20 token contracts (`eth_call`),
|
||||
balances (`eth_getBalance`), read ERC-20 token contracts (`eth_call`), among
|
||||
them the balance of every tracked token at every address (`balanceOf`),
|
||||
estimate gas (`eth_estimateGas`), fetch nonces (`eth_getTransactionCount`),
|
||||
broadcast transactions (`eth_sendRawTransaction`), and check transaction
|
||||
receipts. The default endpoint is a public RPC (configurable by the user to
|
||||
@@ -2680,9 +2689,10 @@ communicates with three external services to function as a wallet:
|
||||
addresses or user-specific data.
|
||||
|
||||
- **Blockscout block-explorer API**: Used to fetch transaction history (normal
|
||||
transactions and ERC-20 token transfers), ERC-20 token balances, and token
|
||||
holder counts (for spam filtering). The default endpoint is
|
||||
`https://eth.blockscout.com/api/v2` (configurable by the user in Settings).
|
||||
transactions and ERC-20 token transfers), the balances of ERC-20 tokens the
|
||||
user has not tracked, and token holder counts (for spam filtering). The
|
||||
default endpoint is `https://eth.blockscout.com/api/v2` (configurable by the
|
||||
user in Settings).
|
||||
- **Data sent**: Ethereum addresses. Blockscout receives the user's
|
||||
addresses to query their transaction history and token balances. No
|
||||
private keys, passwords, or signing operations are sent.
|
||||
@@ -2989,8 +2999,10 @@ indexes it as a real token transfer.
|
||||
excluded from the token selector on the send screen. This prevents users from
|
||||
accidentally interacting with a spoofed token that appeared in their balance
|
||||
via a fake Transfer event. A token whose holder count is unknown is kept in
|
||||
the selector. The selector offers only tokens in the balance list, so such a
|
||||
token is one on the bundled list or one the user tracks.
|
||||
the selector, and so is every token the user tracks, whose entry carries no
|
||||
holder count (see Data Model). The selector offers only tokens in the balance
|
||||
list, so a token with an unknown count is one on the bundled list or one the
|
||||
user tracks.
|
||||
|
||||
- **Dust transaction filtering**: A second wave of the same attack used real
|
||||
native ETH transfers instead of fake tokens. Transaction
|
||||
|
||||
Reference in New Issue
Block a user