Skip to content

Fix: improve assets controller snap accounts subscription and websocket#8430

Draft
salimtb wants to merge 6 commits intomainfrom
fix/assets-controller-snap-accounts-subscription
Draft

Fix: improve assets controller snap accounts subscription and websocket#8430
salimtb wants to merge 6 commits intomainfrom
fix/assets-controller-snap-accounts-subscription

Conversation

@salimtb
Copy link
Copy Markdown
Contributor

@salimtb salimtb commented Apr 12, 2026

Explanation

Current state

AssetsController had three related issues that prevented balances from loading correctly in certain scenarios:

  1. Snap accounts not subscribed after startup: On app launch, #start() was called before AccountTreeController.init() had finished building the account tree, so snap accounts weren't yet in the selected account group. Because #start() is idempotent (it returns early when subscriptions already exist), snap accounts were never picked up when they became available, leaving Solana, Tron, and Bitcoin balances missing.

  2. Custom assets filtered by spam heuristics: TokenDataSource applied the MIN_TOKEN_OCCURRENCES threshold (EVM ERC-20) and Blockaid bulk scan (non-EVM fungible tokens) to all tokens uniformly. This meant tokens manually imported by the user and stored in customAssets state were incorrectly filtered out as potential spam.

  3. No polling fallback when WebSocket is disabled: BackendWebsocketDataSource and AccountsApiDataSource both call the same fetchV2SupportedNetworks API to determine which chains they support. Because BackendWebsocketDataSource has higher priority in the chain-claiming loop, it would claim all supported chains at initialization — even when the WebSocket never connected. This left AccountsApiDataSource with no chains to poll, so users with the WebSocket disabled or with a broken connection received no balance updates.

Solution

  1. Re-subscribe on AccountTree state change: Added #handleAccountTreeStateChange() which subscribes to AccountTreeController:stateChange. When the account tree updates (e.g. after snap accounts are hydrated), the method forces a full re-subscription and re-fetch with the complete account list. Added AccountsController:getSelectedAccount as a fallback in #getSelectedAccounts() for cases where the account tree is not yet initialized.

  2. Custom assets bypass spam filters: TokenDataSource now reads customAssets from state and builds a customAssetIds set. Tokens in that set skip the occurrences >= 3 EVM filter and are excluded from the non-EVM Blockaid scan input, ensuring user-imported assets always appear.

  3. Connection-aware chain claiming in BackendWebsocketDataSource: Added an #isConnected flag (initially false). #initializeActiveChains and #refreshActiveChains now store supported chains in #supportedChains but only call updateActiveChains when connected. On disconnect, activeChains is cleared so the chain-claiming loop re-assigns those chains to AccountsApiDataSource for polling. On reconnect, chains are restored from #supportedChains before pending WebSocket subscriptions are reprocessed.

References

  • Related to MetaMask/metamask-extension (assets multichain rollout)

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
  • 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

salimtb added 3 commits April 13, 2026 00:11
…ountTree initializes late

- Convert `#selectedAccounts` getter to `#getSelectedAccounts()` method with
  a fallback to `AccountsController:getSelectedAccount` when the AccountTree
  returns empty (covers the window before the tree is initialized)
- Add `#handleAccountTreeStateChange()`: when `AccountTreeController:stateChange`
  fires and subscriptions already exist (created before snap accounts arrived),
  re-subscribe and re-fetch so snap/BTC/SOL accounts are included
- Add `AccountsControllerGetSelectedAccountAction` to `AllowedActions` and
  `@metamask/accounts-controller` as a dependency to support the fallback
- Add mock for `AccountsController:getSelectedAccount` in test helper

Previously, data sources reported their active chains before the AccountTree
was initialized, causing subscriptions to be created with only the fallback
EVM account. When the tree later fired `stateChange` with 4 accounts (EVM +
snap accounts), `#start()` returned early due to its idempotency guard, so
snap accounts were never subscribed or fetched.
…okenDataSource

Custom assets (user-imported tokens in `customAssets` state) now bypass
both the EVM occurrence-count filter and the non-EVM Blockaid bulk scan.
Only auto-detected tokens are subject to spam filtering.
…n websocket is disconnected or disabled

BackendWebsocketDataSource now only claims chains when the websocket is
actually connected. On disconnect, chains are released so the
chain-claiming loop assigns them to AccountsApiDataSource for polling.
On reconnect, chains are reclaimed from the stored supportedChains list.
@salimtb salimtb changed the title Fix/assets controller snap accounts subscription Fix: improve assets controller snap accounts subscription and websocket Apr 12, 2026
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.

1 participant