Skip to content

fix: add BackendWebsocketDataSource tests for Arbitrum USDC balance update - #9265

Merged
salimtb merged 5 commits into
mainfrom
fix/ws-balance-stale-after-reconnect
Jun 25, 2026
Merged

fix: add BackendWebsocketDataSource tests for Arbitrum USDC balance update#9265
salimtb merged 5 commits into
mainfrom
fix/ws-balance-stale-after-reconnect

Conversation

@salimtb

@salimtb salimtb commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes stale or missing token balances when websocket live updates compete with Accounts API / RPC polling, and when websocket subscriptions are torn down and recreated during account switches or reconnects.

Manual verification (Arbitrum USDC):

  • USDC on Arbitrum (eip155:42161/erc20:…) notification processed correctly after send/receive
  • EVM addresses matched case-insensitively (checksummed vs lowercase) so notifications are not dropped for the wrong account
  • Balance recovers correctly after websocket disconnect → reconnect → resubscribe (no silently dropped notification during the gap)

Explanation

What was broken?

AssetsController merges balances from several sources: websocket push (BackendWebsocketDataSource), Accounts API polling (AccountsApiDataSource), RPC, Snap, etc. Several races caused the UI to show stale values or never recover after a transaction:

  1. Websocket vs polling — A live websocket balance could be overwritten seconds later by a stale Accounts API poll (TanStack Query cache, ~60s staleTime), or polling could “win” right after a websocket update.
  2. Account switch ordering — On account group change we re-subscribed before forcing a fresh fetch, so websocket notifications could arrive before authoritative API data was applied.
  3. Websocket subscription races — Concurrent subscribe/unsubscribe during account switches could interleave; EVM address comparison was case-sensitive, so a checksummed vs lowercase address looked like a “new” account and triggered unnecessary rebinds or missed matches.
  4. Disconnect/reconnect gap — Notifications routed by subscriptionId could be dropped when the server-side subscription ID changed after reconnect; there was no per-channel callback fallback.
  5. forceUpdate still stalegetAssets({ forceUpdate: true }) did not bypass the Accounts API TanStack cache, and websocket freshness guards could block the forced fetch from applying (e.g. receiver switches to account 2 after a send but state still shows the pre-receive balance).

What does this PR do?

AssetsController

  • Tags applied updates with an internal sourceId on DataResponse.
  • After a BackendWebsocketDataSource balance push, marks those accountId:assetId entries fresh for 120s; passive polling sources (AccountsApiDataSource, RpcDataSource, SnapDataSource, StakedBalanceDataSource) cannot overwrite them during that window.
  • getAssets({ forceUpdate: true }) is tagged getAssets:forceUpdate, clears freshness locks for applied balances, and is not subject to the websocket freshness filter — authoritative for account switch / manual refresh.
  • Account group and account-tree refreshes run under a mutex: forceUpdate fetch first, then subscribe, so API balances land before websocket recovery.
  • Account-tree handler skips tree updates with no overlapping account IDs (full group switches still go through #handleAccountGroupChanged).

AccountsApiDataSource

  • When request.forceUpdate is true, passes { staleTime: 0, gcTime: 0 } to fetchV5MultiAccountBalances so forced refreshes bypass the TanStack cache.

BackendWebsocketDataSource

  • Serializes subscribe/unsubscribe with a lock so account switches cannot interleave.
  • Treats EVM addresses as unchanged when only checksumming differs; re-subscribes when the account set actually changes.
  • Registers optional per-channel callbacks when server subscriptionId routing is unreliable after reconnect.
  • Tears down subscriptions inline (without re-acquiring the subscribe lock) and forwards the original DataRequest with balance updates.

Non-obvious details

  • sourceId is internal — added to DataResponse for merge policy only; not a new public messenger action.
  • Freshness guard is polling-only — websocket updates are still applied immediately; the guard only blocks stale poll merges, not other websocket pushes or forceUpdate.
  • Channel callbacks are best-effort — if BackendWebSocketService:addChannelCallback is not delegated in the client messenger, the primary websocket subscription path still works; callbacks are a fallback for reconnect routing mismatches.

Scope

All changes are in @metamask/assets-controller only. No dependency upgrades and no breaking public API changes.

Test plan

  • yarn workspace @metamask/assets-controller test — new/updated unit tests pass
  • yarn workspace @metamask/assets-controller run changelog:validate
  • Manual: send USDC on Arbitrum, confirm balance updates via websocket
  • Manual: disconnect/reconnect websocket, confirm balance recovers after resubscribe
  • Manual: switch to receiver account after inbound transfer, confirm forceUpdate shows correct balance

New unit test coverage:

  • AssetsController: polling does not overwrite recent websocket balance; getAssets forceUpdate wins over websocket freshness
  • AccountsApiDataSource: forceUpdate bypasses TanStack cache
  • BackendWebsocketDataSource: concurrent subscribe serialization, checksummed vs lowercase EVM addresses, channel-callback fallback, disconnect/reconnect chain reclaim

References

  • Fixes stale balance races after transactions, account switches, and websocket reconnects (#9265)

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate (changelog only; behavior is internal merge policy)
  • I've communicated my changes to consumers by updating changelogs for packages I've changed
  • I've introduced breaking changes in this PR and have prepared draft pull requests for clients and consumer packages to resolve them (N/A — no breaking changes)

Note

Medium Risk
Changes how displayed balances are merged across websocket and polling paths and reorders fetch/subscribe on account changes; incorrect freshness or locking could show wrong balances until the next forceUpdate fetch.

Overview
Fixes stale or flickering balances when backend websocket updates compete with Accounts API / RPC polling, and tightens account-switch and websocket subscription ordering so live updates are not dropped after reconnects or rapid resubscribes.

  • AssetsController: internal sourceId tagging; 120s websocket freshness guard for polling sources; getAssets:forceUpdate clears locks and wins; mutex-serialized fetch-then-subscribe on account change
  • AccountsApiDataSource: staleTime: 0 / gcTime: 0 on forceUpdate
  • BackendWebsocketDataSource: subscribe lock, case-insensitive EVM address matching, per-channel callback fallback, clean teardown on resubscribe

Note

Medium Risk
Changes how displayed balances are merged across websocket and polling paths and reorders fetch/subscribe on account changes; incorrect freshness or locking could show wrong balances until the next forceUpdate fetch.

Overview
Fixes stale or flickering token balances when websocket pushes compete with Accounts API / RPC polling, and when subscriptions are recreated on account switch or reconnect.

AssetsController tags applied updates with internal sourceId on DataResponse. After websocket balance pushes, per-asset freshness locks block passive polling sources for 120s; getAssets({ forceUpdate: true }) is tagged getAssets:forceUpdate, skips that filter, and clears locks for applied balances. Account group and account-tree refreshes use a mutex and run force fetch before subscribe; account-tree updates with no overlapping account IDs are ignored.

AccountsApiDataSource passes { staleTime: 0, gcTime: 0 } to multi-account balance fetches when forceUpdate is set.

BackendWebsocketDataSource serializes subscribe/unsubscribe, treats EVM addresses as unchanged when only checksumming differs, registers optional per-channel callbacks for reconnect routing, and passes the original DataRequest with balance updates.

Reviewed by Cursor Bugbot for commit 773e4e8. Bugbot is set up for automated code reviews on this repo. Configure here.

@salimtb
salimtb force-pushed the fix/ws-balance-stale-after-reconnect branch from f6eaa4f to 39a67eb Compare June 25, 2026 09:38
@salimtb salimtb changed the title test(assets-controller): add BackendWebsocketDataSource tests for Arbitrum USDC balance update fix: add BackendWebsocketDataSource tests for Arbitrum USDC balance update Jun 25, 2026
@salimtb
salimtb force-pushed the fix/ws-balance-stale-after-reconnect branch 2 times, most recently from 1cdc5c2 to ca17234 Compare June 25, 2026 09:55
@salimtb

salimtb commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

@metamaskbot publish-preview

@salimtb
salimtb marked this pull request as ready for review June 25, 2026 10:02
@salimtb
salimtb requested review from a team as code owners June 25, 2026 10:02
@salimtb
salimtb temporarily deployed to default-branch June 25, 2026 10:02 — with Image GitHub Actions Inactive
@github-actions

Copy link
Copy Markdown
Contributor

Preview builds have been published. Learn how to use preview builds in other projects.

Expand for full list of packages and versions.
@metamask-previews/account-tree-controller@7.5.3-preview-ca1723428
@metamask-previews/accounts-controller@39.0.3-preview-ca1723428
@metamask-previews/address-book-controller@7.1.2-preview-ca1723428
@metamask-previews/ai-controllers@0.7.0-preview-ca1723428
@metamask-previews/analytics-controller@1.1.1-preview-ca1723428
@metamask-previews/analytics-data-regulation-controller@0.0.0-preview-ca1723428
@metamask-previews/announcement-controller@8.1.0-preview-ca1723428
@metamask-previews/app-metadata-controller@2.0.1-preview-ca1723428
@metamask-previews/approval-controller@9.0.2-preview-ca1723428
@metamask-previews/assets-controller@9.1.0-preview-ca1723428
@metamask-previews/assets-controllers@109.2.2-preview-ca1723428
@metamask-previews/authenticated-user-storage@2.1.0-preview-ca1723428
@metamask-previews/base-controller@9.1.0-preview-ca1723428
@metamask-previews/base-data-service@0.1.3-preview-ca1723428
@metamask-previews/bitcoin-regtest-up@0.0.0-preview-ca1723428
@metamask-previews/bridge-controller@77.0.0-preview-ca1723428
@metamask-previews/bridge-status-controller@73.0.0-preview-ca1723428
@metamask-previews/build-utils@3.0.4-preview-ca1723428
@metamask-previews/chain-agnostic-permission@1.6.2-preview-ca1723428
@metamask-previews/chomp-api-service@3.1.0-preview-ca1723428
@metamask-previews/claims-controller@0.5.3-preview-ca1723428
@metamask-previews/client-controller@1.0.1-preview-ca1723428
@metamask-previews/compliance-controller@2.1.0-preview-ca1723428
@metamask-previews/composable-controller@12.0.1-preview-ca1723428
@metamask-previews/config-registry-controller@0.4.1-preview-ca1723428
@metamask-previews/connectivity-controller@0.2.0-preview-ca1723428
@metamask-previews/controller-utils@12.3.0-preview-ca1723428
@metamask-previews/core-backend@6.3.3-preview-ca1723428
@metamask-previews/delegation-controller@3.0.2-preview-ca1723428
@metamask-previews/earn-controller@12.2.1-preview-ca1723428
@metamask-previews/eip-5792-middleware@3.0.4-preview-ca1723428
@metamask-previews/eip-7702-internal-rpc-middleware@0.1.1-preview-ca1723428
@metamask-previews/eip1193-permission-middleware@2.0.1-preview-ca1723428
@metamask-previews/ens-controller@19.1.4-preview-ca1723428
@metamask-previews/eth-block-tracker@15.0.1-preview-ca1723428
@metamask-previews/eth-json-rpc-middleware@23.1.3-preview-ca1723428
@metamask-previews/eth-json-rpc-provider@6.0.1-preview-ca1723428
@metamask-previews/foundryup@1.0.1-preview-ca1723428
@metamask-previews/gas-fee-controller@26.2.3-preview-ca1723428
@metamask-previews/gator-permissions-controller@4.2.1-preview-ca1723428
@metamask-previews/geolocation-controller@0.1.3-preview-ca1723428
@metamask-previews/java-tron-up@0.0.0-preview-ca1723428
@metamask-previews/json-rpc-engine@10.5.0-preview-ca1723428
@metamask-previews/json-rpc-middleware-stream@8.0.8-preview-ca1723428
@metamask-previews/keyring-controller@27.1.0-preview-ca1723428
@metamask-previews/local-node-utils@0.0.0-preview-ca1723428
@metamask-previews/logging-controller@8.0.2-preview-ca1723428
@metamask-previews/message-manager@14.1.2-preview-ca1723428
@metamask-previews/messenger@1.2.0-preview-ca1723428
@metamask-previews/messenger-cli@0.2.0-preview-ca1723428
@metamask-previews/money-account-balance-service@2.1.1-preview-ca1723428
@metamask-previews/money-account-controller@0.3.3-preview-ca1723428
@metamask-previews/money-account-upgrade-controller@2.1.0-preview-ca1723428
@metamask-previews/multichain-account-service@11.1.0-preview-ca1723428
@metamask-previews/multichain-api-middleware@3.1.5-preview-ca1723428
@metamask-previews/multichain-network-controller@3.2.0-preview-ca1723428
@metamask-previews/multichain-transactions-controller@7.1.1-preview-ca1723428
@metamask-previews/name-controller@9.1.2-preview-ca1723428
@metamask-previews/network-controller@33.0.0-preview-ca1723428
@metamask-previews/network-enablement-controller@5.4.0-preview-ca1723428
@metamask-previews/notification-services-controller@24.2.0-preview-ca1723428
@metamask-previews/passkey-controller@2.0.1-preview-ca1723428
@metamask-previews/permission-controller@13.1.1-preview-ca1723428
@metamask-previews/permission-log-controller@5.1.0-preview-ca1723428
@metamask-previews/perps-controller@9.0.0-preview-ca1723428
@metamask-previews/phishing-controller@17.2.0-preview-ca1723428
@metamask-previews/polling-controller@16.0.7-preview-ca1723428
@metamask-previews/preferences-controller@23.1.0-preview-ca1723428
@metamask-previews/profile-metrics-controller@4.0.0-preview-ca1723428
@metamask-previews/profile-sync-controller@28.2.0-preview-ca1723428
@metamask-previews/ramps-controller@15.0.0-preview-ca1723428
@metamask-previews/rate-limit-controller@7.0.1-preview-ca1723428
@metamask-previews/react-data-query@0.2.1-preview-ca1723428
@metamask-previews/remote-feature-flag-controller@4.2.2-preview-ca1723428
@metamask-previews/sample-controllers@5.0.2-preview-ca1723428
@metamask-previews/seedless-onboarding-controller@10.0.2-preview-ca1723428
@metamask-previews/selected-network-controller@26.1.4-preview-ca1723428
@metamask-previews/shield-controller@5.1.2-preview-ca1723428
@metamask-previews/signature-controller@39.2.6-preview-ca1723428
@metamask-previews/smart-transactions-controller@24.2.3-preview-ca1723428
@metamask-previews/snap-account-service@1.0.0-preview-ca1723428
@metamask-previews/social-controllers@2.3.1-preview-ca1723428
@metamask-previews/solana-test-validator-up@0.0.0-preview-ca1723428
@metamask-previews/storage-service@1.0.2-preview-ca1723428
@metamask-previews/subscription-controller@6.2.0-preview-ca1723428
@metamask-previews/transaction-controller@68.2.0-preview-ca1723428
@metamask-previews/transaction-pay-controller@23.16.1-preview-ca1723428
@metamask-previews/user-operation-controller@41.2.5-preview-ca1723428
@metamask-previews/wallet@5.0.0-preview-ca1723428
@metamask-previews/wallet-cli@0.0.0-preview-ca1723428

Comment thread packages/assets-controller/src/data-sources/BackendWebsocketDataSource.ts Outdated
Comment thread packages/assets-controller/src/data-sources/BackendWebsocketDataSource.ts Outdated
@salimtb
salimtb force-pushed the fix/ws-balance-stale-after-reconnect branch from 432b1cd to 4e4e438 Compare June 25, 2026 14:24
Comment thread packages/assets-controller/src/AssetsController.ts
@salimtb

salimtb commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

@metamaskbot publish-preview

@salimtb
salimtb enabled auto-merge June 25, 2026 15:05
@salimtb
salimtb added this pull request to the merge queue Jun 25, 2026
Merged via the queue into main with commit 504a3cb Jun 25, 2026
396 checks passed
@salimtb
salimtb deleted the fix/ws-balance-stale-after-reconnect branch June 25, 2026 15:07
@github-actions

Copy link
Copy Markdown
Contributor

Preview builds have been published. Learn how to use preview builds in other projects.

Expand for full list of packages and versions.
@metamask-previews/account-tree-controller@7.5.3-preview-773e4e83d
@metamask-previews/accounts-controller@39.0.3-preview-773e4e83d
@metamask-previews/address-book-controller@7.1.2-preview-773e4e83d
@metamask-previews/ai-controllers@0.7.0-preview-773e4e83d
@metamask-previews/analytics-controller@1.2.0-preview-773e4e83d
@metamask-previews/analytics-data-regulation-controller@0.0.0-preview-773e4e83d
@metamask-previews/announcement-controller@8.1.0-preview-773e4e83d
@metamask-previews/app-metadata-controller@2.0.1-preview-773e4e83d
@metamask-previews/approval-controller@9.0.2-preview-773e4e83d
@metamask-previews/assets-controller@9.1.0-preview-773e4e83d
@metamask-previews/assets-controllers@109.2.2-preview-773e4e83d
@metamask-previews/authenticated-user-storage@2.1.0-preview-773e4e83d
@metamask-previews/base-controller@9.1.0-preview-773e4e83d
@metamask-previews/base-data-service@0.1.3-preview-773e4e83d
@metamask-previews/bitcoin-regtest-up@0.0.0-preview-773e4e83d
@metamask-previews/bridge-controller@77.0.0-preview-773e4e83d
@metamask-previews/bridge-status-controller@73.0.0-preview-773e4e83d
@metamask-previews/build-utils@3.0.4-preview-773e4e83d
@metamask-previews/chain-agnostic-permission@1.6.2-preview-773e4e83d
@metamask-previews/chomp-api-service@3.1.0-preview-773e4e83d
@metamask-previews/claims-controller@0.5.3-preview-773e4e83d
@metamask-previews/client-controller@1.0.1-preview-773e4e83d
@metamask-previews/compliance-controller@2.1.0-preview-773e4e83d
@metamask-previews/composable-controller@12.0.1-preview-773e4e83d
@metamask-previews/config-registry-controller@0.4.1-preview-773e4e83d
@metamask-previews/connectivity-controller@0.2.0-preview-773e4e83d
@metamask-previews/controller-utils@12.3.0-preview-773e4e83d
@metamask-previews/core-backend@6.3.3-preview-773e4e83d
@metamask-previews/delegation-controller@3.0.2-preview-773e4e83d
@metamask-previews/earn-controller@12.2.1-preview-773e4e83d
@metamask-previews/eip-5792-middleware@3.0.4-preview-773e4e83d
@metamask-previews/eip-7702-internal-rpc-middleware@0.1.1-preview-773e4e83d
@metamask-previews/eip1193-permission-middleware@2.0.1-preview-773e4e83d
@metamask-previews/ens-controller@19.1.4-preview-773e4e83d
@metamask-previews/eth-block-tracker@15.0.1-preview-773e4e83d
@metamask-previews/eth-json-rpc-middleware@23.1.3-preview-773e4e83d
@metamask-previews/eth-json-rpc-provider@6.0.1-preview-773e4e83d
@metamask-previews/foundryup@1.0.1-preview-773e4e83d
@metamask-previews/gas-fee-controller@26.2.3-preview-773e4e83d
@metamask-previews/gator-permissions-controller@4.2.1-preview-773e4e83d
@metamask-previews/geolocation-controller@0.1.3-preview-773e4e83d
@metamask-previews/java-tron-up@0.0.0-preview-773e4e83d
@metamask-previews/json-rpc-engine@10.5.0-preview-773e4e83d
@metamask-previews/json-rpc-middleware-stream@8.0.8-preview-773e4e83d
@metamask-previews/keyring-controller@27.1.0-preview-773e4e83d
@metamask-previews/local-node-utils@0.0.0-preview-773e4e83d
@metamask-previews/logging-controller@8.0.2-preview-773e4e83d
@metamask-previews/message-manager@14.1.2-preview-773e4e83d
@metamask-previews/messenger@1.2.0-preview-773e4e83d
@metamask-previews/messenger-cli@0.2.0-preview-773e4e83d
@metamask-previews/money-account-balance-service@2.1.1-preview-773e4e83d
@metamask-previews/money-account-controller@0.3.3-preview-773e4e83d
@metamask-previews/money-account-upgrade-controller@2.1.0-preview-773e4e83d
@metamask-previews/multichain-account-service@11.1.0-preview-773e4e83d
@metamask-previews/multichain-api-middleware@3.1.5-preview-773e4e83d
@metamask-previews/multichain-network-controller@3.2.0-preview-773e4e83d
@metamask-previews/multichain-transactions-controller@7.1.1-preview-773e4e83d
@metamask-previews/name-controller@9.1.2-preview-773e4e83d
@metamask-previews/network-controller@33.0.0-preview-773e4e83d
@metamask-previews/network-enablement-controller@5.4.0-preview-773e4e83d
@metamask-previews/notification-services-controller@24.2.0-preview-773e4e83d
@metamask-previews/passkey-controller@2.0.1-preview-773e4e83d
@metamask-previews/permission-controller@13.1.1-preview-773e4e83d
@metamask-previews/permission-log-controller@5.1.0-preview-773e4e83d
@metamask-previews/perps-controller@9.0.0-preview-773e4e83d
@metamask-previews/phishing-controller@17.2.0-preview-773e4e83d
@metamask-previews/polling-controller@16.0.7-preview-773e4e83d
@metamask-previews/preferences-controller@23.1.0-preview-773e4e83d
@metamask-previews/profile-metrics-controller@4.0.0-preview-773e4e83d
@metamask-previews/profile-sync-controller@28.2.0-preview-773e4e83d
@metamask-previews/ramps-controller@15.0.0-preview-773e4e83d
@metamask-previews/rate-limit-controller@7.0.1-preview-773e4e83d
@metamask-previews/react-data-query@0.2.1-preview-773e4e83d
@metamask-previews/remote-feature-flag-controller@4.2.2-preview-773e4e83d
@metamask-previews/sample-controllers@5.0.2-preview-773e4e83d
@metamask-previews/seedless-onboarding-controller@10.0.3-preview-773e4e83d
@metamask-previews/selected-network-controller@26.1.4-preview-773e4e83d
@metamask-previews/shield-controller@5.1.2-preview-773e4e83d
@metamask-previews/signature-controller@39.2.6-preview-773e4e83d
@metamask-previews/smart-transactions-controller@24.2.3-preview-773e4e83d
@metamask-previews/snap-account-service@1.0.0-preview-773e4e83d
@metamask-previews/social-controllers@2.3.1-preview-773e4e83d
@metamask-previews/solana-test-validator-up@0.0.0-preview-773e4e83d
@metamask-previews/storage-service@1.0.2-preview-773e4e83d
@metamask-previews/subscription-controller@6.2.0-preview-773e4e83d
@metamask-previews/transaction-controller@68.2.0-preview-773e4e83d
@metamask-previews/transaction-pay-controller@23.16.1-preview-773e4e83d
@metamask-previews/user-operation-controller@41.2.5-preview-773e4e83d
@metamask-previews/wallet@5.0.0-preview-773e4e83d
@metamask-previews/wallet-cli@0.0.0-preview-773e4e83d

pull Bot pushed a commit to dmrazzy/core that referenced this pull request Jul 23, 2026
…equest (MetaMask#9591)

## Explanation

Current behavior:
<img width="1917" height="631" alt="image"
src="https://github.com/user-attachments/assets/a9eebaab-1ff2-48c4-b788-2ca169c7826e"
/>

After PR:
<img width="1917" height="631" alt="image"
src="https://github.com/user-attachments/assets/7f27a1cf-ccae-43d2-b9d2-70e41d3d0c8a"
/>



Balance refreshes triggered on-demand go through
`AssetsController.getAssets(..., { forceUpdate: true })`, which flows
down to `AccountsApiDataSource.fetch` as TanStack Query `fetchOptions`.
Previously the `forceUpdate` path used `{ staleTime: 0, gcTime: 0 }`.
These values has been added via
MetaMask#9265 but it was mainly to fix a
websocket issue.

That was too aggressive:

- `staleTime: 0` marks the query as stale immediately, so **every**
forced refresh refetches — even two identical requests fired
microseconds apart.
- `gcTime: 0` evicts the cache entry the instant the query goes inactive
(which, for imperative `fetchQuery`, is right after it resolves).
Because the forced fetch uses the **same queryKey** as the 30s polling
subscription, it not only bypasses the cache but also tears down the
entry the poll would have reused.

In practice almost every internal trigger calls `getAssets` with
`forceUpdate: true` (unlock, account switch, network change, tx
confirmation, price refresh, etc.), and several of these fire
near-simultaneously. With `0/0` each trigger produced its own Accounts
API request, causing bursts of duplicate `multiaccount/balances` calls.

This PR changes the `forceUpdate` path to `{ staleTime: 200, gcTime: 200
}`. The small 200ms window lets a burst of near-simultaneous forced
refreshes de-duplicate into a single Accounts API request while keeping
the data effectively fresh (well within a user-perceptible refresh).
Keeping `gcTime >= staleTime` also ensures the entry survives its
freshness window instead of being evicted immediately.

## References

N/A

## Checklist

- [ ] I've updated the test suite for new or updated code as appropriate
- [x] I've updated documentation (JSDoc, Markdown, etc.) for new or
updated code as appropriate
- [x] I've communicated my changes to consumers by [updating changelogs
for packages I've
changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md)
- [ ] I've introduced [breaking
changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md)
in this PR and have prepared draft pull requests for clients and
consumer packages to resolve them



<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Narrow change to query cache options for forced balance fetches;
balances may share a single response within ~100ms, which is an
intentional tradeoff for fewer API calls.
> 
> **Overview**
> **Forced balance refreshes** (`getAssets` with `forceUpdate: true` →
`AccountsApiDataSource.fetch`) no longer pass TanStack Query `{
staleTime: 0, gcTime: 0 }`. They now use **`staleTime` and `gcTime` of
100ms**, so near-simultaneous triggers (unlock, account/network switch,
tx confirmation, etc.) **collapse to one** `multiaccount/balances`
request instead of a burst of duplicates, while still avoiding the
default long-lived cache.
> 
> The unit test and changelog are updated to match the new short-lived
cache window (replacing the “fully bypass cache” expectation from
[MetaMask#9265](MetaMask#9265)).
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
1659296. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants