Repository navigation
feat(wallet): initialize AssetsController by default and start it from the client - #10885
dawnseeker8 wants to merge 45 commits into
Conversation
Export an AssetsController initialization config so clients can register it after NetworkEnablementController, which the wallet does not initialize yet. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
|
@metamaskbot publish-preview |
|
Preview builds have been published. Learn how to use preview builds in other projects. Expand for full list of packages and versions. |
…et-assets-controller Co-authored-by: Cursor <cursoragent@cursor.com>
…ontroller' into feat/assets-3641-wallet-assets-controller
|
@metamaskbot publish-preview |
|
Preview builds have been published. Learn how to use preview builds in other projects. Expand for full list of packages and versions. |
…Mask#10997) ## Explanation `@metamask/wallet` now constructs `NetworkEnablementController` during default initialization. The constructor only needs a messenger and optional persisted state, so there is no new `instanceOptions` field. ### Breaking change `Wallet.init()` calls `NetworkEnablementController.init()`. That method reads `MultichainNetworkController:getState`. The wallet does not construct `MultichainNetworkController`, so each client must register that action on the wallet messenger before `wallet.init()`. `NetworkEnablementController` is a default initialization configuration and is not exported. Construction order for `AssetsController` is handled in MetaMask#10885 by moving the `NetworkEnablementController:getState` read into `AssetsController.init()`. ### What this pull request changes - Add `NetworkEnablementController` to the default initialization configurations. - Delegate `NetworkController:getState`, `MultichainNetworkController:getState`, and `ConfigRegistryController:getState`, plus the network and transaction events the controller subscribes to. - Import `@metamask/slip44` with a JSON import attribute so Node.js 22 and 24 do not throw `ERR_IMPORT_ATTRIBUTE_MISSING` when loading `NetworkEnablementController`. - Generate the CODEOWNERS entry from `codeowners.ts`. `@metamask/wallet-cli` was removed on `main` (MetaMask#11001), so this pull request no longer changes that package. `AssetsController` initialization stays in MetaMask#10885 and will be updated after this merges. ## References * [ASSETS-4169](https://consensyssoftware.atlassian.net/browse/ASSETS-4169) * Follow-up: MetaMask#10885 ## Checklist - [x] 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 [ASSETS-4169]: https://consensyssoftware.atlassian.net/browse/ASSETS-4169?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Breaking wallet init contract: clients must pre-register `MultichainNetworkController:getState` or initialization fails; otherwise changes are localized wiring and a Node ESM import fix. > > **Overview** > **`@metamask/wallet` now wires `NetworkEnablementController` into default initialization**, so `Wallet.init()` constructs it alongside the other default controllers. The new `networkEnablementController` configuration delegates messenger actions (`ConfigRegistryController:getState`, `NetworkController:getState`, **`MultichainNetworkController:getState`**) and network/transaction events the controller listens to. **Breaking:** the wallet does not create `MultichainNetworkController`, so hosts must register `MultichainNetworkController:getState` on the wallet root messenger before `wallet.init()`. > > The package adds `@metamask/network-enablement-controller` (^7.0.2), exports the init config from the instances barrel, and updates CODEOWNERS/`codeowners.ts` for the new initialization path. > > **`@metamask/network-enablement-controller`** fixes Node 22/24 ESM loading by importing `@metamask/slip44` with `with { type: 'json' }` in `Slip44Service`, avoiding `ERR_IMPORT_ATTRIBUTE_MISSING` when the wallet daemon loads the controller. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 41650ee. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Xiaoming Wang <dawnseeker8@users.noreply.github.com>
…et-assets-controller Co-authored-by: Cursor <cursoragent@cursor.com> # Conflicts: # packages/wallet/CHANGELOG.md
AssetsController can be constructed before NetworkEnablementController exists. init() and the stateChange subscription apply enabled chains once that state is available. Co-authored-by: Cursor <cursoragent@cursor.com>
The new test used a widened CAIP-19 id and an extra publish argument, which lint:tsc:check treats as unsuppressed errors. Co-authored-by: Cursor <cursoragent@cursor.com>
The new assertions used a short cast parameter and nullish fallbacks, which oxlint reports as unsuppressed violations. Co-authored-by: Cursor <cursoragent@cursor.com>
|
@metamaskbot publish-preview |
|
Preview builds have been published. Learn how to use preview builds in other projects. Expand for full list of packages and versions. |
Changelog checks only accept the open pull request under Unreleased, and oxfmt needs the enabled-chain assertion wrapped. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
|
@metamaskbot publish-preview |
|
Preview builds have been published. Learn how to use preview builds in other projects. Expand for full list of packages and versions. |
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
|
@metamaskbot publish-preview |
…rce.ts Co-authored-by: Christian Tran <christian.trn@gmail.com>
Co-authored-by: Xiaoming Wang <dawnseeker8@users.noreply.github.com>
…et-assets-controller Co-authored-by: Cursor <cursoragent@cursor.com> # Conflicts: # packages/assets-controller/CHANGELOG.md
Co-authored-by: Xiaoming Wang <dawnseeker8@users.noreply.github.com>
…ontroller' into feat/assets-3641-wallet-assets-controller Co-authored-by: Xiaoming Wang <dawnseeker8@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
AssetsControllerInstanceOptions already requires queryApiClient, and WalletOptions requires instanceOptions.assetsController, so omitting the client is a type error. Remove the runtime throw and the test that asserted it. Co-authored-by: Xiaoming Wang <dawnseeker8@users.noreply.github.com>
|
@metamaskbot publish-preview |
|
Preview builds have been published. Learn how to use preview builds in other projects. Expand for full list of packages and versions. |
…et-assets-controller Keep both Unreleased assets-controller changelog entries. Call AssetsController.init() in the launchpad filtering integration test so supported-network discovery still runs after messenger reads moved out of the constructor. Co-authored-by: Xiaoming Wang <dawnseeker8@users.noreply.github.com>
…f the constructor The constructor no longer calls messenger actions, creates data sources or subscribes to external events, so construction does not depend on the order in which the Wallet constructs controllers. A new public `start()` method (also the `AssetsController:start` messenger action) reads enabled networks, creates the data sources, subscribes to external events and basic-functionality changes, runs the unlock cleanups when the keyring is already unlocked, and starts asset tracking. Clients call it once their own controllers (AccountTreeController, SnapController, ...) are registered, since `wallet.init()` runs before those exist. Data sources are unchanged from main: their constructors still read other controllers, only later. This replaces the per-data-source `init()` methods and the "controller not registered" fallbacks, which are removed. Entry points that can run before `start()` are guarded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… fetch start() created AccountsApiDataSource and force-fetched before its supported-networks request settled, so the first fetch ran with an empty activeChains list and skipped the Accounts API. Co-authored-by: Xiaoming Wang <dawnseeker8@users.noreply.github.com>
…he first fetch" This reverts commit 8093c7c. Co-authored-by: Xiaoming Wang <dawnseeker8@users.noreply.github.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 8093c7c. Configure here.
… fetch `start()` created `AccountsApiDataSource` and could begin tracking before the source's async supported-networks fetch resolved. The first forced fetch then saw no Accounts API chains and routed every chain to the Snap/RPC lane. `start()` now awaits `AccountsApiDataSource.refreshActiveChains()` (which shares the in-flight `fetchQuery` request) alongside the unlock cleanups, and ignores data source chain updates until it has finished, so an early update cannot subscribe ahead of the startup refresh. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@metamaskbot publish-preview |
|
Preview builds have been published. Learn how to use preview builds in other projects. Expand for full list of packages and versions. |

Explanation
ASSETS-3641 asks
@metamask/walletto construct@metamask/assets-controlleras part of its shared initialization flow. This PR addsAssetsControllerto the Wallet's default initialization configurations and changes how the controller starts, so that construction no longer depends on controller order.The problem
AssetsControllerand its data sources read other controllers when they are constructed.NetworkController,NetworkEnablementControllerandRemoteFeatureFlagControllerare Wallet defaults.SnapController,PermissionControllerandAccountTreeControllerare not: the clients register them after the Wallet is built.Doing that work in
init()does not fix it either. Both clients callwallet.init()right afternew Wallet(), before their own controllers are registered (extensionapp/scripts/wallet-init/initialization.ts, mobileapp/core/Engine/wallet-init/initialization.ts). For example, snap discovery would run beforeSnapControllerexists, and Solana, Bitcoin and Tron balances would stay empty.The approach: construct minimally, then
start()Constructor: no messenger actions, no data sources, no subscriptions to external events. It sets up state, stores the options and registers action handlers. A test asserts that construction makes no
messenger.call.start(): a new public method, also exposed as theAssetsController:startmessenger action. The client calls it once its own controllers are registered. It:subscribeToBasicFunctionalityChange;Repeated calls share one promise. Calls after
destroy()do nothing.data-sources/is unchanged frommain. Each data source still reads other controllers in its constructor, as before; the only difference is thatstart()constructs them later. This replaces the earlier per-data-sourceinit()/refreshActiveChains...()methods and the fallbacks for unregistered controllers. Those are removed: theisUnregisteredActionErrorchecks, theAccountsController:selectedAccountChangefallback, and treating a missingAccountTreeControlleras initialized.Inside the controller, the data-source fields become private getters over a single
#sourcesobject, so existing call sites are unchanged. Entry points that can run beforestart()are guarded:getAssets({ forceUpdate: true })returns what is in state without fetching. The@trackAssetsLoadingdecorator moved to a private#getAssetsTracked, so this call does not settleassetsLoadingStatustoloadedearly.handleAssetsUpdate,syncAssetsPrices,subscribeAssetsPriceandhandleBasicFunctionalityChangedo nothing.setSelectedCurrencyupdates state without refreshing prices.Wallet
AssetsControlleris in the default configurations and requiresinstanceOptions.assetsController.queryApiClient.Wallet.init()does not start it.AssetsControllerhas noinit(), sowallet.init()returns the same number of results as before.AssetsControllerMessengerdeclares, including those ofAccountTreeControllerandSnapController, which the client registers.Client follow-up
Clients must call
AssetsController:startafter registering and initializing their controllers. Hosts that already constructAssetsControllerthemselves must remove that, or messenger registration will collide. The extension draft is MetaMask/metamask-extension#47585. It currently callsAssetsController.init()a second time, and that call will be replaced withstart()once a preview build of this PR is available.References
@metamask/assets-controllerinto the Core Wallet packageNetworkEnablementController: MetaMask/core#10997Checklist
🤖 Generated with Claude Code
Note
Medium Risk
Breaking controller lifecycle and default Wallet registration change asset-tracking timing and can cause messenger collisions; clients must call
start()at the right point in initialization.Overview
Breaking:
AssetsControllerno longer creates data sources, calls other controllers, or subscribes in the constructor. Clients must callstart()(orAssetsController:start) after dependent controllers (e.g.AccountTreeController,SnapController) exist. Until then, forced fetches read persisted state, pushed updates are ignored, and price/sync paths are no-ops.start()is idempotent, waits for Accounts API supported networks, runs unlock cleanups if already unlocked, then begins tracking when UI is open, keyring unlocked, and the account tree is ready.@metamask/walletnow constructsAssetsControllerby default (requiredinstanceOptions.assetsController.queryApiClient), delegates its external messenger actions/events, and does not invokestart()duringWallet.init(). Hosts that already registerAssetsControllermust remove duplicate registration before upgrading.Reviewed by Cursor Bugbot for commit 713f7d7. Bugbot is set up for automated code reviews on this repo. Configure here.