feat(assets-controller): clean and optimize data fetching#7950
feat(assets-controller): clean and optimize data fetching#7950
Conversation
42c77bd to
94338fd
Compare
94338fd to
789b774
Compare
| [result.accountId]: newBalances, | ||
| }, | ||
| assetsInfo, | ||
| updateMode: 'full', |
There was a problem hiding this comment.
RPC subscription updates wipe balances from other sources
High Severity
Both #handleBalanceUpdate and #handleDetectionUpdate set updateMode: 'full' on their subscription responses. These responses contain data for only a single chain (per BalanceFetchResult.chainId) or only newly detected tokens. When this reaches #updateState in 'full' mode, balances[accountId] = effective replaces the entire account's balances with only what's in the response plus custom assets — wiping out balances from other chains and other data sources (e.g., AccountsAPI, Snap). These incremental subscription updates need 'merge' mode, not 'full'.
Additional Locations (1)
| }; | ||
| const fallbackRequests = partitionChainsBySource( | ||
| fallbackRequest, | ||
| sources, |
There was a problem hiding this comment.
Fallback retries the same failed source
Medium Severity
The fallback round calls partitionChainsBySource(fallbackRequest, sources) with the same sources array used in round 1. Since partitionChainsBySource assigns each chain to the first source that supports it, the source that originally failed gets the same chains again in the fallback. The stated intent — letting lower-priority sources try failed chains — is not achieved because the partitioning logic is identical both rounds.
| merged.detectedAssets = { | ||
| ...(merged.detectedAssets ?? {}), | ||
| ...response.detectedAssets, | ||
| }; |
There was a problem hiding this comment.
Shallow merge of detectedAssets loses per-account arrays
Low Severity
mergeDataResponses uses a shallow object spread for detectedAssets, which replaces per-account arrays rather than concatenating them. If two responses contain detectedAssets for the same accountId, the first response's array is silently discarded. This is inconsistent with how assetsBalance is correctly deep-merged per account (lines 26–35). Since detectedAssets is Record<AccountId, Caip19AssetId[]>, the per-account arrays need to be concatenated (with deduplication), not replaced.


Explanation
What is the current state of things and why does it need to change?
What is the solution your changes offer and how does it work?
Parallel middlewares (
@metamask/assets-controller)createParallelBalanceMiddleware: Runs balance data sources (Accounts API, Snap, RPC) in parallel with chain partitioning and limited concurrency (p-limit, concurrency 3). Failed chains get a fallback round. Responses are merged viamergeDataResponses.createParallelMiddleware: Runs TokenDataSource and PriceDataSource in parallel for the same request and merges their responses.Both use the new
DataResponse.updateModeso merged results preserve'full'when any source uses it.Full vs merge update modes
DataResponsegets an optionalupdateMode:'full'|'merge'(typeAssetsUpdateMode).updateModeis omitted is'merge'.ClientController integration
@metamask/assets-controlleradds a dependency on@metamask/client-controllerand subscribes toClientController:stateChange.Method-action-types script
scripts/generate-method-action-types.tsno longer takes a per-package path. It discovers all controllers withMESSENGER_EXPOSED_METHODSunderpackages/. Thegenerate-method-action-typesscript andtsxdevDependency are removed from individual packages (e.g. assets-controller, client-controller, analytics-controller, etc.) so generation is centralized.Are there any changes whose purpose might not be obvious to those unfamiliar with the domain?
mergeDataResponses: Merges multipleDataResponseobjects (e.g. from parallel balance sources). If any response hasupdateMode: 'full', the merged result is'full'; otherwise it defaults to'merge'.ClientController:getStateandClientController:stateChangeon the assets-controller messenger; otherwise asset tracking will not run when the UI is open.If your primary goal was to update one package but you found you had to update another one along the way, why did you do so?
scripts/generate-method-action-types.tsaffects the whole monorepo, so all packages that previously had a localgenerate-method-action-typesscript and used it with a path were updated to drop that script and thetsxdevDependency.assets-controllersCHANGELOG was updated to reflect dependency/version bumps (e.g.network-enablement-controller) that are part of this work.If you had to upgrade a dependency, why did you do so?
@metamask/client-controller: New dependency for assets-controller to gate asset tracking on UI visibility.p-limit: Added to assets-controller to cap concurrency in the parallel balance middleware.tsxfrom several packages: script execution is centralized; the root or a single package can run the generator.Integration PR in extension: MetaMask/metamask-extension#40233
References
Checklist
Note
Medium Risk
Touches core asset fetching/concurrency and changes when tracking starts/stops based on UI/keyring state, which can impact freshness and subscription behavior across chains. Update-mode semantics also affect how balances are cleared/merged, so regressions could surface as missing/stale assets if misclassified by a data source.
Overview
Optimizes asset data fetching and state application. Adds
ParallelMiddlewareutilities to run balance sources with chain partitioning + fallback and to fetch token metadata and prices concurrently, with concurrency caps viap-limitand mergedDataResponses.Introduces
DataResponse.updateMode('full'|'merge') and updatesAssetsControllerstate updates to respect authoritative vs incremental responses (preserving custom assets on full refresh), while marking appropriate data-source responses and forcinggetAssets(..., forceUpdate)to apply asfull.Integrates
@metamask/client-controllerso tracking/subscriptions only run when both the UI is open and the keyring is unlocked, and updates tests/tooling exports/config to support the new lifecycle and middleware.Written by Cursor Bugbot for commit 789b774. This will update automatically on new commits. Configure here.