Repository navigation
Conversation
getAccountBalances throws the expected-state BalanceNotSynchronizedError while the wallet still sits on the genesis checkpoint, instead of answering a confident zero, and the cron holds back balance events for such accounts. The error is matched by its stable message marker (the keyring SDK re-creates it as a plain Error) and is never sent to Sentry.
This was referenced Oct 26, 2026
fix(stellar): run account reads under the fetch policy and report stale balances as unavailable
#525
Closed
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
@metamask/bitcoin-wallet-snapExplanation
Bug
A Bitcoin account whose first chain scan failed shows 0 BTC, which looks exactly like an empty account ("my bitcoin is gone"). The snap answers
getAccountBalancesfrom a wallet that never synced: genesis checkpoint, empty UTXO set.How to reproduce
release/13.54.0(acf000d) withyarn build:dev. For the "after" case, also build this branch (yarn workspace @metamask/bitcoin-wallet-snap build) and copy itsdist/bundle.js,snap.manifest.jsonandpreinstalled-snap.jsonintonode_modules/@metamask/bitcoin-wallet-snap. To see the wallet-side "Balance unavailable" + Retry, add core PR 10 and extension PR 12.bitcoin-mainnet.infura.iocan reuse the HTTP/2 connection already open to another*.infura.iohost (in our runs the Solana snap usually opened it first). The reply is an empty 404 without CORS headers, so the snap seesFailed to fetch. We reproduced this on a test machine where every*.infura.ioname resolved to the same IP. It depends on the network path and may not reproduce on every network. In our runs we moved the shared connection between hosts on purpose: we dropped the test browser's sockets while briefly blocking the other Infura hosts, which "heals" one chain and breaks the others.--disable-http2give no coalescing, and all four balances are correct.Expected: while the account has never synced, the snap reports the balance as unavailable (the wallet shows "Balance unavailable", not 0), and the expected state is not sent to Sentry.
Actual (before this change): BTC shows 0 for as long as the requests fail. In the unfixed A run it was 0 in all 17 snapshots over 15 minutes, and still 0 after a reload.
Without a browser:
KeyringHandler.test.ts›getAccountBalances›fails instead of reporting 0 when the balance is still unknownreproduces the fake zero with a never-synced account mock.Summary
getAccountBalancesno longer answers a confident0 BTCfor an account whose wallet has never synced with the chain. It throwsBalanceNotSynchronizedErrorinstead. This is an expected state: it reaches the client as "balance unavailable" and is never sent to Sentry. The cron also stops pushingAccountBalancesUpdatedfor such accounts.Why
In Chrome, HTTP/2 connection coalescing across
*.infura.iocan send the snap's Esplora request over a connection opened for another Infura host. The provider router answers with an empty 404 and no CORS headers, which the snap sees asFailed to fetch. When the first scan of a fresh account failed that way, the BDK wallet stayed on its genesis checkpoint with an empty UTXO set.getAccountBalancesread that empty wallet and reported 0 BTC, a fake zero that looked exactly like an empty account. The error path also reported every request to Sentry, flooding it with a state that is expected while the network is broken.What changed
entities/account.ts,infra/BdkAccountAdapter.ts,infra/StoredAccountAdapter.ts: newhasSynchronized(), true once the wallet applied a chain update (checkpoint height > 0). The metadata-onlyStoredAccountAdapteralways answers true, so it never hides a known balance.use-cases/AccountUseCases.ts:isBalanceUnknown(account)=!account.hasSynchronized(). PR 03 extends it with "first full scan still pending".handlers/KeyringHandler.ts:getAccountBalancesthrowsBalanceNotSynchronizedErrorwhile the balance is unknown.entities/error.ts:BalanceNotSynchronizedError(anExternalServiceError, so the client sees it as disconnected/unavailable);BALANCE_NOT_SYNCHRONIZED_MESSAGEmarker;isBalanceNotSynchronizedError(), which matches the class, the message, thecausechain or a serialized error. This matters because the keyring SDK'shandleKeyringRequestre-creates every non-SnapErroras a plainError.handlers/HandlerMiddleware.ts,utils/errors.ts: the expected state is logged at debug level and skipped bytrackError/shouldTrackError.handlers/CronHandler.ts: the batched balance event only includes accounts whose balance is known.Errorpath), utils/errors, CronHandler, AccountUseCases.Validation
Checked locally: the test files changed in this PR were run with the parent branch's source (tests from this branch, every other file from the parent), then on this branch.
Unit tests that fail before and pass after (parent:
80b21f6)KeyringHandler › getAccountBalances ›fails instead of reporting 0 when the balance is still unknown,throws the untracked BalanceNotSynchronizedError when the balance is unknownAccountUseCases › isBalanceUnknown ›is unknown while the wallet never synchronized,is known once the wallet synchronizedCronHandler › synchronizeAccounts › repair rescan ›holds back the balance of a synced account whose balance is still unknown,emits no balance event when every synced balance is unknownHandlerMiddleware › handle ›does not track or log as an error the expected "not synchronized yet" state, plus 2 tests on the real keyring request path (the SDK re-creates the error as a plainError)errors › shouldTrackError › returns false for the "not synchronized yet" state however it was wrapped (v3)Full run on this branch
jest --runInBand --testPathIgnorePatterns integration-testinpackages/bitcoin-wallet-snap: 26 suites, 671 tests pass. The 4 docker-based integration suites need the local regtest stack, so they are excluded.tsc --noEmit(package): clean.Manual A/B
A = Chrome with default HTTP/2 (reproduces the coalescing); B = Chrome with
--disable-http2(control, no coalescing). Every run used the same test wallet and a localyarn build:devof extension 13.54.0 (acf000d), with the snaps and assets-controller wired into node_modules by hand. "Unfixed 13.54.0" is that build with no fixes. The fixes-vN builds are cumulative: each one has every fix up to that pass, so an "after" number shows this PR together with the others in that build. Run names (A-v6, B-v7, ...) are our local test passes on those builds; times are CEST.snap_trackErrorRisks
hasSynchronized()reads the BDK checkpoint height. A wallet restored from a persisted changeset keeps its checkpoint, so already-synced accounts are unaffected.Changelog (
packages/bitcoin-wallet-snap/CHANGELOG.md, Unreleased → Fixed)References
Dependencies
snap.manifest.jsonshasum (yarn workspace @metamask/bitcoin-wallet-snap build). It is not included here.Stale non-EVM balances series (Chrome HTTP/2 coalescing across
*.infura.io):Checklist