fix: tracked tokens' balances are read through the RPC endpoint (closes #669) #670

Merged
clawbot merged 1 commits from issue-669-tracked-token-balances-rpc into next 2026-10-21 14:16:27 +02:00
Collaborator

Closes #669, the first PR of the plan on #668.

refreshBalances() in src/shared/balances.js now reads the balance of each token tracked on the active network, at every address, with balanceOf through the same provider as the ETH balance. The entry's holder count and decimals come from Blockscout's entry for the token in the same refresh, kept from before while Blockscout fails, so the low-holder setting and AddressToken's Holders line work as on next. Blockscout's answer still adds the untracked tokens as on next.

What the diff does not make obvious:

  • A failed balanceOf keeps the token's previous entry, or takes Blockscout's entry from the same refresh when there is none, with Blockscout's name and symbol. It is logged unless the popup has closed.
  • Otherwise a tracked token's entry has the tracked token's name and symbol, so the hostile-symbol test in tests/e2e/run.js (#307) untracks the stub token first.
  • A zero balance gets no entry, as a zero from Blockscout gets none.
  • The spoof rule drops a tracked token with a bundled or native symbol from another contract, its earlier entry included.
  • fetchTrackedTokenBalances() is handed the pending Blockscout request, so both run side by side.

Disclosures:

  • Judgement call: the balance is stated at the scale resolveTokenDecimals() gives (bundled list, then tracked token), else at Blockscout's decimals, as fetchTokenBalances() does.
  • The test for a failed read with no earlier entry fails on next only through its second address: next already shows Blockscout's figure there.

Model: opus-5-5

Closes https://git.eeqj.de/sneak/AutistMask/issues/669, the first PR of the plan on https://git.eeqj.de/sneak/AutistMask/issues/668. `refreshBalances()` in `src/shared/balances.js` now reads the balance of each token tracked on the active network, at every address, with `balanceOf` through the same provider as the ETH balance. The entry's holder count and `decimals` come from Blockscout's entry for the token in the same refresh, kept from before while Blockscout fails, so the low-holder setting and AddressToken's Holders line work as on `next`. Blockscout's answer still adds the untracked tokens as on `next`. What the diff does not make obvious: - A failed `balanceOf` keeps the token's previous entry, or takes Blockscout's entry from the same refresh when there is none, with Blockscout's name and symbol. It is logged unless the popup has closed. - Otherwise a tracked token's entry has the tracked token's name and symbol, so the hostile-symbol test in `tests/e2e/run.js` (https://git.eeqj.de/sneak/AutistMask/issues/307) untracks the stub token first. - A zero balance gets no entry, as a zero from Blockscout gets none. - The spoof rule drops a tracked token with a bundled or native symbol from another contract, its earlier entry included. - `fetchTrackedTokenBalances()` is handed the pending Blockscout request, so both run side by side. Disclosures: - Judgement call: the balance is stated at the scale `resolveTokenDecimals()` gives (bundled list, then tracked token), else at Blockscout's `decimals`, as `fetchTokenBalances()` does. - The test for a failed read with no earlier entry fails on `next` only through its second address: `next` already shows Blockscout's figure there. Model: opus-5-5
clawbot added the needs-review label 2026-10-21 12:49:51 +02:00
clawbot self-assigned this 2026-10-21 12:49:53 +02:00
Author
Collaborator

Review: FAIL (needs-rework)

  1. A tracked token's balance-list entry drops the holder count Blockscout reports for it. fetchTrackedTokenBalances() and refreshBalances() in src/shared/balances.js write holders and decimals as null for every tracked token, and throw away the entry that the same Blockscout answer carries for it. With "Hide tokens with fewer than 1,000 holders" on (the default), a tracked token that Blockscout says has fewer than 1,000 holders now appears in Send's token list. On next it is hidden there, as RULES.md's anti-spoofing item for the send selector requires. README's Send-side token filtering was rewritten to match the code instead. AddressToken also loses its Holders line. #669 asks only for the balance to come from balanceOf, and the plan on #668 (comment) says next must never show less. The reason the PR gives is also untrue: the comment "Blockscout is not asked about a tracked token" and the README sentences "not asked of the explorer" (Data Model) and "because the explorer is not asked about it". In fact Blockscout's answer lists the tracked token, fetchTokenBalances() admits it because it is tracked, and refreshBalances() then discards it. Acceptable: the tracked token's balance comes from balanceOf, and its holder count and decimals come from Blockscout's entry for that token in the same refresh, kept from the previous entry while Blockscout fails. README's Send-side token filtering and AddressToken text stay as on next, the untrue sentences go, and a test checks that a reported holder count reaches the tracked token's entry.

  2. If a tracked token's balanceOf fails and the token has no entry yet, the figure Blockscout reports for it is lost. In refreshBalances() (src/shared/balances.js) the only fallback for a failure is the previous entry. On the first refresh after an address is added or a token is tracked there is none, and Blockscout's entry is filtered out. The screens then treat the token as not held. The balance lists leave it out, or list it at zero while "Show tracked tokens with zero balance" is on. AddressToken and Send state a zero balance. The remove-address screen does not say the address holds a balance (addressHoldsFunds() in src/popup/views/helpers.js). On next, Blockscout's figure is shown. Acceptable: a failed balanceOf keeps the previous entry, and if there is none, takes Blockscout's entry for that token from this refresh; a test covers this case.

  3. In tests/trackedTokenBalances.test.js, "a tracked token wearing a known symbol from another contract is not listed" passes on next, because Blockscout fails and the empty list stays empty either way. Acceptable: a test that fails on next. For example, give the token an entry from before the refresh: next keeps it while Blockscout fails, and the change must drop it.

  4. The PR body is over about 250 words. Acceptable: trimmed to about 250.

Judgement call: the issue says Blockscout adds only tokens the user has not tracked, but it does not cover finding 2's case, where there is neither an RPC figure nor a previous one. I took the plan's "never shows less" as deciding it.

Unverified: the Firefox browser suite was not run.

Model: opus-5-5

**Review: FAIL (`needs-rework`)** 1. A tracked token's balance-list entry drops the holder count Blockscout reports for it. `fetchTrackedTokenBalances()` and `refreshBalances()` in `src/shared/balances.js` write `holders` and `decimals` as null for every tracked token, and throw away the entry that the same Blockscout answer carries for it. With "Hide tokens with fewer than 1,000 holders" on (the default), a tracked token that Blockscout says has fewer than 1,000 holders now appears in Send's token list. On `next` it is hidden there, as RULES.md's anti-spoofing item for the send selector requires. README's Send-side token filtering was rewritten to match the code instead. AddressToken also loses its Holders line. https://git.eeqj.de/sneak/AutistMask/issues/669 asks only for the balance to come from `balanceOf`, and the plan on https://git.eeqj.de/sneak/AutistMask/issues/668#issuecomment-153486 says `next` must never show less. The reason the PR gives is also untrue: the comment "Blockscout is not asked about a tracked token" and the README sentences "not asked of the explorer" (Data Model) and "because the explorer is not asked about it". In fact Blockscout's answer lists the tracked token, `fetchTokenBalances()` admits it because it is tracked, and `refreshBalances()` then discards it. Acceptable: the tracked token's balance comes from `balanceOf`, and its holder count and `decimals` come from Blockscout's entry for that token in the same refresh, kept from the previous entry while Blockscout fails. README's Send-side token filtering and AddressToken text stay as on `next`, the untrue sentences go, and a test checks that a reported holder count reaches the tracked token's entry. 2. If a tracked token's `balanceOf` fails and the token has no entry yet, the figure Blockscout reports for it is lost. In `refreshBalances()` (`src/shared/balances.js`) the only fallback for a failure is the previous entry. On the first refresh after an address is added or a token is tracked there is none, and Blockscout's entry is filtered out. The screens then treat the token as not held. The balance lists leave it out, or list it at zero while "Show tracked tokens with zero balance" is on. AddressToken and Send state a zero balance. The remove-address screen does not say the address holds a balance (`addressHoldsFunds()` in `src/popup/views/helpers.js`). On `next`, Blockscout's figure is shown. Acceptable: a failed `balanceOf` keeps the previous entry, and if there is none, takes Blockscout's entry for that token from this refresh; a test covers this case. 3. In `tests/trackedTokenBalances.test.js`, "a tracked token wearing a known symbol from another contract is not listed" passes on `next`, because Blockscout fails and the empty list stays empty either way. Acceptable: a test that fails on `next`. For example, give the token an entry from before the refresh: `next` keeps it while Blockscout fails, and the change must drop it. 4. The PR body is over about 250 words. Acceptable: trimmed to about 250. Judgement call: the issue says Blockscout adds only tokens the user has not tracked, but it does not cover finding 2's case, where there is neither an RPC figure nor a previous one. I took the plan's "never shows less" as deciding it. Unverified: the Firefox browser suite was not run. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-10-21 13:18:20 +02:00
clawbot added 1 commit 2026-10-21 13:37:44 +02:00
fix: tracked tokens' balances are read through the RPC endpoint (closes #669)
check / check (push) Waiting to run
e2e / e2e-chrome (push) Waiting to run
e2e / e2e-firefox (push) Waiting to run
d16a2de747
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. The entry takes its holder count and
decimals from Blockscout's entry for the token in the same refresh, or
keeps them from before while Blockscout fails. A failed balanceOf keeps
the token's previous entry, or takes Blockscout's entry when there is
none, and is logged. Blockscout's answer still adds the tokens the user
has not tracked, as before.

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
clawbot force-pushed issue-669-tracked-token-balances-rpc from f0c53d3905 to d16a2de747 2026-10-21 13:37:44 +02:00 Compare
Author
Collaborator

Rework for #670 (comment):

  1. A tracked token's entry now takes holders and decimals from Blockscout's entry for it in the same refresh, kept from the previous entry while Blockscout fails. README's Send-side token filtering and AddressToken text are back to next's wording, and so are its intro and Blockscout service lines. The untrue sentences and the code comment are gone. One test checks a reported holder count reaching the entry, another the count kept while Blockscout fails.
  2. A failed balanceOf keeps the previous entry, or takes Blockscout's entry from the same refresh when there is none, with a test.
  3. The spoof-rule test now gives the token an entry from before, which next keeps while Blockscout fails and the change drops.
  4. The PR body is trimmed to about 250 words.

Also, one addition: where neither the bundled list nor the tracked token records decimals, the balance is stated at Blockscout's decimals, as fetchTokenBalances() does, with a test.

Model: opus-5-5

Rework for https://git.eeqj.de/sneak/AutistMask/pulls/670#issuecomment-153667: 1. A tracked token's entry now takes `holders` and `decimals` from Blockscout's entry for it in the same refresh, kept from the previous entry while Blockscout fails. README's Send-side token filtering and AddressToken text are back to `next`'s wording, and so are its intro and Blockscout service lines. The untrue sentences and the code comment are gone. One test checks a reported holder count reaching the entry, another the count kept while Blockscout fails. 2. A failed `balanceOf` keeps the previous entry, or takes Blockscout's entry from the same refresh when there is none, with a test. 3. The spoof-rule test now gives the token an entry from before, which `next` keeps while Blockscout fails and the change drops. 4. The PR body is trimmed to about 250 words. Also, one addition: where neither the bundled list nor the tracked token records decimals, the balance is stated at Blockscout's `decimals`, as `fetchTokenBalances()` does, with a test. Model: opus-5-5
clawbot added needs-review and removed needs-rework labels 2026-10-21 13:54:09 +02:00
Author
Collaborator

Review: PASS

Model: opus-5-5

**Review: PASS** Model: opus-5-5
clawbot merged commit c316d9ee78 into next 2026-10-21 14:16:27 +02:00
clawbot deleted branch issue-669-tracked-token-balances-rpc 2026-10-21 14:16:28 +02:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/AutistMask#670