From 744318f8fc388bde18a6f92226ddb9b5ac12ee0e Mon Sep 17 00:00:00 2001 From: Xiaoming Wang Date: Thu, 22 Oct 2026 20:00:07 +0800 Subject: [PATCH 1/4] feat(wallet): initialize NetworkEnablementController by default Register NetworkEnablementController in the default wallet initialization, and keep daemon startup and CODEOWNERS generation working with that controller. Co-authored-by: Cursor --- .github/CODEOWNERS | 1 + codeowners.ts | 1 + .../CHANGELOG.md | 1 + .../src/services/Slip44Service.ts | 5 +- packages/wallet-cli/CHANGELOG.md | 4 + packages/wallet-cli/package.json | 1 + .../wallet-cli/src/daemon/wallet-factory.ts | 31 ++++ packages/wallet-cli/tsconfig.build.json | 3 + packages/wallet-cli/tsconfig.json | 3 + packages/wallet-cli/tsconfig.lint.json | 3 + packages/wallet/CHANGELOG.md | 7 + packages/wallet/package.json | 1 + packages/wallet/src/Wallet.test.ts | 2 +- packages/wallet/src/index.ts | 1 + .../src/initialization/instances/index.ts | 1 + .../network-enablement-controller.test.ts | 169 ++++++++++++++++++ .../network-enablement-controller.ts | 41 +++++ packages/wallet/tsconfig.build.json | 3 + packages/wallet/tsconfig.json | 3 + packages/wallet/tsconfig.lint.json | 3 + yarn.lock | 2 + 21 files changed, 284 insertions(+), 2 deletions(-) create mode 100644 packages/wallet/src/initialization/instances/network-enablement-controller/network-enablement-controller.test.ts create mode 100644 packages/wallet/src/initialization/instances/network-enablement-controller/network-enablement-controller.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d8bf683f86a..9a86c307fdd 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -998,6 +998,7 @@ /packages/wallet/src/initialization/instances/keyring-controller/ @MetaMask/accounts-engineers @MetaMask/core-platform /packages/wallet/src/initialization/instances/logging-controller/ @MetaMask/confirmations /packages/wallet/src/initialization/instances/network-controller/ @MetaMask/core-platform @MetaMask/metamask-assets +/packages/wallet/src/initialization/instances/network-enablement-controller/ @MetaMask/metamask-assets /packages/wallet/src/initialization/instances/passkey-controller/ @MetaMask/web3auth /packages/wallet/src/initialization/instances/permission-controller/ @MetaMask/core-platform /packages/wallet/src/initialization/instances/remote-feature-flag-controller/ @MetaMask/core-platform @MetaMask/extension-platform @MetaMask/mobile-platform diff --git a/codeowners.ts b/codeowners.ts index a8ce28dbd67..397a0aa3875 100644 --- a/codeowners.ts +++ b/codeowners.ts @@ -255,6 +255,7 @@ const config = { }, 'network-enablement-controller': { teams: ['@MetaMask/metamask-assets'], + initializationPath: 'network-enablement-controller', }, 'node-utils': { teams: ['@MetaMask/core-platform'], diff --git a/packages/network-enablement-controller/CHANGELOG.md b/packages/network-enablement-controller/CHANGELOG.md index abffecf33e6..b632ad8f9ee 100644 --- a/packages/network-enablement-controller/CHANGELOG.md +++ b/packages/network-enablement-controller/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Import `@metamask/slip44` with a JSON import attribute so Node.js ESM consumers do not throw `ERR_IMPORT_ATTRIBUTE_MISSING` when importing `NetworkEnablementController` ([#10997](https://github.com/MetaMask/core/pull/10997)) - Preserve existing network enablement when an auto-enabled network from the config registry is added ([#10763](https://github.com/MetaMask/core/pull/10763)) ## [7.0.2] diff --git a/packages/network-enablement-controller/src/services/Slip44Service.ts b/packages/network-enablement-controller/src/services/Slip44Service.ts index affebea52c7..f83b10f1915 100644 --- a/packages/network-enablement-controller/src/services/Slip44Service.ts +++ b/packages/network-enablement-controller/src/services/Slip44Service.ts @@ -1,6 +1,9 @@ import { fetchWithErrorHandling } from '@metamask/controller-utils'; +// `@metamask/slip44` publishes a JSON file as its entry point. Node.js ESM +// rejects that import unless it carries a JSON import attribute, which crashes +// the wallet daemon on Node 22 and 24. // @ts-expect-error: No type definitions for '@metamask/slip44' -import slip44 from '@metamask/slip44'; +import slip44 from '@metamask/slip44' with { type: 'json' }; const CHAINID_NETWORK_URL = 'https://chainid.network/chains.json'; diff --git a/packages/wallet-cli/CHANGELOG.md b/packages/wallet-cli/CHANGELOG.md index aa78d1ed092..eb70b214215 100644 --- a/packages/wallet-cli/CHANGELOG.md +++ b/packages/wallet-cli/CHANGELOG.md @@ -49,4 +49,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bump `better-sqlite3` from `^12.9.0` to `^13.0.3` ([#10528](https://github.com/MetaMask/core/pull/10528), [#10598](https://github.com/MetaMask/core/pull/10598)) - Bump `@metamask/rpc-errors` from `^7.0.2` to `^7.0.3` ([#10718](https://github.com/MetaMask/core/pull/10718)) +### Fixed + +- Register a `MultichainNetworkController:getState` handler on the daemon wallet so `NetworkEnablementController.init()` can finish. The daemon does not run `MultichainNetworkController` ([#10997](https://github.com/MetaMask/core/pull/10997)) + [Unreleased]: https://github.com/MetaMask/core/ diff --git a/packages/wallet-cli/package.json b/packages/wallet-cli/package.json index cf913973811..c36394fc525 100644 --- a/packages/wallet-cli/package.json +++ b/packages/wallet-cli/package.json @@ -56,6 +56,7 @@ "@metamask/base-controller": "^10.1.0", "@metamask/config-registry-controller": "^5.0.0", "@metamask/messenger": "^3.0.0", + "@metamask/multichain-network-controller": "^4.0.0", "@metamask/remote-feature-flag-controller": "^7.0.0", "@metamask/rpc-errors": "^7.0.3", "@metamask/scure-bip39": "^2.1.1", diff --git a/packages/wallet-cli/src/daemon/wallet-factory.ts b/packages/wallet-cli/src/daemon/wallet-factory.ts index ce525b2e36d..276b557bb3f 100644 --- a/packages/wallet-cli/src/daemon/wallet-factory.ts +++ b/packages/wallet-cli/src/daemon/wallet-factory.ts @@ -5,6 +5,8 @@ import type { } from '@metamask/analytics-controller'; import { ConfigRegistryApiEnv } from '@metamask/config-registry-controller'; import { Messenger } from '@metamask/messenger'; +import { getDefaultMultichainNetworkControllerState } from '@metamask/multichain-network-controller'; +import type { MultichainNetworkControllerGetStateAction } from '@metamask/multichain-network-controller'; import { ClientConfigApiService, ClientType, @@ -187,6 +189,33 @@ function registerAnalyticsStubHandlers(messenger: Wallet['messenger']): void { ); } +/** + * Register a handler for `MultichainNetworkController:getState`. + * `NetworkEnablementController.init()` reads that action while syncing enabled + * networks. The daemon does not run `MultichainNetworkController`, so the + * handler returns that controller's default state and keeps initialization + * from aborting. + * + * @param messenger - The wallet's root messenger. + */ +function registerMultichainNetworkStubHandlers( + messenger: Wallet['messenger'], +): void { + const multichainMessenger = new Messenger< + 'MultichainNetworkController', + MultichainNetworkControllerGetStateAction, + never, + RootMessenger + >({ + namespace: 'MultichainNetworkController', + parent: messenger as RootMessenger, + }); + multichainMessenger.registerActionHandler( + 'MultichainNetworkController:getState', + getDefaultMultichainNetworkControllerState, + ); +} + /** * Create a configured `Wallet` for daemon use, backed by a SQLite key-value * store for controller-state persistence. @@ -260,6 +289,7 @@ export async function createWallet({ instanceOptions: buildInstanceOptions(infuraProjectId), }); registerAnalyticsStubHandlers(wallet.messenger); + registerMultichainNetworkStubHandlers(wallet.messenger); persistenceUnsubscribe = subscribeToChanges( wallet.messenger, wallet.controllerMetadata, @@ -383,6 +413,7 @@ async function loadPersistedState( instanceOptions: buildInstanceOptions(infuraProjectId), }); registerAnalyticsStubHandlers(probe.messenger); + registerMultichainNetworkStubHandlers(probe.messenger); try { return loadState(store, probe.controllerMetadata); } finally { diff --git a/packages/wallet-cli/tsconfig.build.json b/packages/wallet-cli/tsconfig.build.json index d592a7d8614..64cf9419c02 100644 --- a/packages/wallet-cli/tsconfig.build.json +++ b/packages/wallet-cli/tsconfig.build.json @@ -17,6 +17,9 @@ { "path": "../messenger/tsconfig.build.json" }, + { + "path": "../multichain-network-controller/tsconfig.build.json" + }, { "path": "../remote-feature-flag-controller/tsconfig.build.json" }, diff --git a/packages/wallet-cli/tsconfig.json b/packages/wallet-cli/tsconfig.json index 13596f814a7..f10f9414fe4 100644 --- a/packages/wallet-cli/tsconfig.json +++ b/packages/wallet-cli/tsconfig.json @@ -16,6 +16,9 @@ { "path": "../messenger" }, + { + "path": "../multichain-network-controller" + }, { "path": "../remote-feature-flag-controller" }, diff --git a/packages/wallet-cli/tsconfig.lint.json b/packages/wallet-cli/tsconfig.lint.json index 6a34d99009d..2b77dff067f 100644 --- a/packages/wallet-cli/tsconfig.lint.json +++ b/packages/wallet-cli/tsconfig.lint.json @@ -8,6 +8,9 @@ { "path": "../messenger/tsconfig.lint.json" }, + { + "path": "../multichain-network-controller/tsconfig.lint.json" + }, { "path": "../storage-service/tsconfig.lint.json" }, diff --git a/packages/wallet/CHANGELOG.md b/packages/wallet/CHANGELOG.md index 4c9968b6d7f..4418bd35307 100644 --- a/packages/wallet/CHANGELOG.md +++ b/packages/wallet/CHANGELOG.md @@ -7,8 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- **BREAKING:** Add `NetworkEnablementController` to the default initialization configurations ([#10997](https://github.com/MetaMask/core/pull/10997)) + - `Wallet.init()` calls `NetworkEnablementController.init()`, which reads `MultichainNetworkController:getState`. The wallet does not construct `MultichainNetworkController`. Clients must register that action on the wallet messenger before calling `wallet.init()`. + - Export `networkEnablementController` so clients can pass it through `initializationConfigurations` before `assetsController`. Additional configurations initialize before defaults, and `AssetsController` reads `NetworkEnablementController:getState` during construction. + ### Changed +- Add `@metamask/network-enablement-controller` as a dependency ([#10997](https://github.com/MetaMask/core/pull/10997)) - Bump `@metamask/claims-controller` from `^1.0.3` to `^1.0.4` ([#10925](https://github.com/MetaMask/core/pull/10925)) - Bump `@metamask/shield-controller` from `^7.0.4` to `^7.0.5` ([#10925](https://github.com/MetaMask/core/pull/10925)) - Bump `@metamask/subscription-controller` from `^14.0.1` to `^14.0.2` ([#10925](https://github.com/MetaMask/core/pull/10925)) diff --git a/packages/wallet/package.json b/packages/wallet/package.json index 5b410b28fee..8ff35efe343 100644 --- a/packages/wallet/package.json +++ b/packages/wallet/package.json @@ -65,6 +65,7 @@ "@metamask/logging-controller": "^10.0.0", "@metamask/messenger": "^3.0.0", "@metamask/network-controller": "^37.1.1", + "@metamask/network-enablement-controller": "^7.0.2", "@metamask/passkey-controller": "^4.1.0", "@metamask/permission-controller": "^14.0.0", "@metamask/remote-feature-flag-controller": "^7.0.0", diff --git a/packages/wallet/src/Wallet.test.ts b/packages/wallet/src/Wallet.test.ts index 74c36d70c69..725fb4248b7 100644 --- a/packages/wallet/src/Wallet.test.ts +++ b/packages/wallet/src/Wallet.test.ts @@ -187,7 +187,7 @@ describe('Wallet', () => { const results = await wallet.init(); - expect(results).toHaveLength(6); + expect(results).toHaveLength(7); }); it('disallows modifying the messenger', async () => { diff --git a/packages/wallet/src/index.ts b/packages/wallet/src/index.ts index 50e9b004160..64f137ad7d4 100644 --- a/packages/wallet/src/index.ts +++ b/packages/wallet/src/index.ts @@ -1,6 +1,7 @@ export { Wallet } from './Wallet.js'; export { AlwaysOnlineAdapter } from './initialization/instances/connectivity-controller/always-online-adapter.js'; export { importSecretRecoveryPhrase } from './utilities.js'; +export { networkEnablementController } from './initialization/instances/network-enablement-controller/network-enablement-controller.js'; export type { WalletOptions } from './types.js'; export type { DefaultActions, diff --git a/packages/wallet/src/initialization/instances/index.ts b/packages/wallet/src/initialization/instances/index.ts index 8c5471620e5..a5378e63ca2 100644 --- a/packages/wallet/src/initialization/instances/index.ts +++ b/packages/wallet/src/initialization/instances/index.ts @@ -11,6 +11,7 @@ export { gasFeeController } from './gas-fee-controller/gas-fee-controller.js'; export { keyringController } from './keyring-controller/keyring-controller.js'; export { loggingController } from './logging-controller/logging-controller.js'; export { networkController } from './network-controller/network-controller.js'; +export { networkEnablementController } from './network-enablement-controller/network-enablement-controller.js'; export { passkeyController } from './passkey-controller/passkey-controller.js'; export { permissionController } from './permission-controller/permission-controller.js'; export { subjectMetadataController } from './permission-controller/subject-metadata-controller.js'; diff --git a/packages/wallet/src/initialization/instances/network-enablement-controller/network-enablement-controller.test.ts b/packages/wallet/src/initialization/instances/network-enablement-controller/network-enablement-controller.test.ts new file mode 100644 index 00000000000..74fff842c4e --- /dev/null +++ b/packages/wallet/src/initialization/instances/network-enablement-controller/network-enablement-controller.test.ts @@ -0,0 +1,169 @@ +import { Messenger } from '@metamask/messenger'; +import { NetworkEnablementController } from '@metamask/network-enablement-controller'; +import type { NetworkEnablementControllerState } from '@metamask/network-enablement-controller'; + +import * as walletExports from '../../../index.js'; +import { defaultConfigurations } from '../../defaults.js'; +import type { + DefaultActions, + DefaultEvents, + RootMessenger, +} from '../../defaults.js'; +import { networkEnablementController } from './network-enablement-controller.js'; + +type ActionHandler = (...args: unknown[]) => unknown; + +type AnyMessenger = Messenger; + +const PERSISTED_STATE: NetworkEnablementControllerState = { + enabledNetworkMap: { + eip155: { + '0x1': false, + }, + }, + nativeAssetIdentifiers: { + 'eip155:1': 'eip155:1/slip44:60', + }, +}; + +/** + * Creates a root messenger for use in tests. + * + * @returns A root messenger. + */ +function getRootMessenger(): RootMessenger { + return new Messenger({ namespace: 'Root' }); +} + +describe('networkEnablementController', () => { + it('is registered as a default initialization configuration', () => { + expect(Object.values(defaultConfigurations)).toContain( + networkEnablementController, + ); + }); + + it('is exported from the package entry point', () => { + expect(walletExports.networkEnablementController).toBe( + networkEnablementController, + ); + }); + + it('initializes a NetworkEnablementController with default state', () => { + const messenger = + networkEnablementController.getMessenger(getRootMessenger()); + + const instance = networkEnablementController.init({ + state: undefined, + messenger, + options: {}, + }); + + expect(instance).toBeInstanceOf(NetworkEnablementController); + expect(instance.state.enabledNetworkMap.eip155?.['0x1']).toBe(true); + expect(instance.state.nativeAssetIdentifiers).toStrictEqual({}); + }); + + it('forwards provided state over the defaults', () => { + const messenger = + networkEnablementController.getMessenger(getRootMessenger()); + + const instance = networkEnablementController.init({ + state: PERSISTED_STATE, + messenger, + options: {}, + }); + + expect(instance.state.enabledNetworkMap.eip155?.['0x1']).toBe(false); + expect(instance.state.nativeAssetIdentifiers).toStrictEqual( + PERSISTED_STATE.nativeAssetIdentifiers, + ); + }); + + it('exposes its state through the root messenger', () => { + const rootMessenger = getRootMessenger(); + const messenger = networkEnablementController.getMessenger(rootMessenger); + + networkEnablementController.init({ + state: undefined, + messenger, + options: {}, + }); + + expect( + rootMessenger.call('NetworkEnablementController:getState') + .enabledNetworkMap.eip155?.['0x1'], + ).toBe(true); + }); + + it.each([ + 'ConfigRegistryController:getState', + 'MultichainNetworkController:getState', + 'NetworkController:getState', + ])('delegates the %s action', (actionType) => { + const rootMessenger = getRootMessenger(); + const handler = jest.fn().mockReturnValue('result'); + registerActionHandler(rootMessenger, actionType, handler); + const messenger = networkEnablementController.getMessenger(rootMessenger); + + const result = ( + messenger as unknown as { call(type: string): unknown } + ).call(actionType); + + expect(handler).toHaveBeenCalledTimes(1); + expect(result).toBe('result'); + }); + + it.each([ + 'NetworkController:networkAdded', + 'NetworkController:networkRemoved', + 'NetworkController:stateChange', + 'TransactionController:transactionSubmitted', + ])('delegates the %s event', (eventType) => { + const rootMessenger = getRootMessenger(); + const messenger = networkEnablementController.getMessenger(rootMessenger); + const listener = jest.fn(); + ( + messenger as unknown as { + subscribe(type: string, handler: ActionHandler): void; + } + ).subscribe(eventType, listener); + + publishEvent(rootMessenger, eventType, 'payload'); + + expect(listener).toHaveBeenCalledWith('payload'); + }); +}); + +function registerActionHandler( + parent: RootMessenger, + actionType: string, + handler: ActionHandler, +): void { + const messenger = new Messenger({ + namespace: actionType.split(':')[0], + parent: parent as unknown as AnyMessenger, + }); + + ( + messenger as unknown as { + registerActionHandler(type: string, handler: ActionHandler): void; + } + ).registerActionHandler(actionType, handler); +} + +function publishEvent( + parent: RootMessenger, + eventType: string, + ...payload: unknown[] +): void { + const messenger = new Messenger({ + namespace: eventType.split(':')[0], + parent: parent as unknown as AnyMessenger, + }); + + ( + messenger as unknown as { + publish(type: string, ...payload: unknown[]): void; + } + ).publish(eventType, ...payload); +} diff --git a/packages/wallet/src/initialization/instances/network-enablement-controller/network-enablement-controller.ts b/packages/wallet/src/initialization/instances/network-enablement-controller/network-enablement-controller.ts new file mode 100644 index 00000000000..cef810b1e55 --- /dev/null +++ b/packages/wallet/src/initialization/instances/network-enablement-controller/network-enablement-controller.ts @@ -0,0 +1,41 @@ +import { Messenger } from '@metamask/messenger'; +import { NetworkEnablementController } from '@metamask/network-enablement-controller'; +import type { NetworkEnablementControllerMessenger } from '@metamask/network-enablement-controller'; + +import type { InitializationConfiguration } from '../../types.js'; + +export const networkEnablementController: InitializationConfiguration< + NetworkEnablementController, + NetworkEnablementControllerMessenger +> = { + name: 'NetworkEnablementController', + reference: NetworkEnablementController, + init: ({ state, messenger }) => + new NetworkEnablementController({ + state, + messenger, + }), + getMessenger: (parent) => { + const messenger: NetworkEnablementControllerMessenger = new Messenger({ + namespace: 'NetworkEnablementController', + parent, + }); + + parent.delegate({ + messenger, + actions: [ + 'ConfigRegistryController:getState', + 'MultichainNetworkController:getState', + 'NetworkController:getState', + ], + events: [ + 'NetworkController:networkAdded', + 'NetworkController:networkRemoved', + 'NetworkController:stateChange', + 'TransactionController:transactionSubmitted', + ], + }); + + return messenger; + }, +}; diff --git a/packages/wallet/tsconfig.build.json b/packages/wallet/tsconfig.build.json index 68efc97ee72..8e108e66aa2 100644 --- a/packages/wallet/tsconfig.build.json +++ b/packages/wallet/tsconfig.build.json @@ -47,6 +47,9 @@ { "path": "../network-controller/tsconfig.build.json" }, + { + "path": "../network-enablement-controller/tsconfig.build.json" + }, { "path": "../passkey-controller/tsconfig.build.json" }, diff --git a/packages/wallet/tsconfig.json b/packages/wallet/tsconfig.json index a8b09405b97..584c3b651e0 100644 --- a/packages/wallet/tsconfig.json +++ b/packages/wallet/tsconfig.json @@ -46,6 +46,9 @@ { "path": "../network-controller" }, + { + "path": "../network-enablement-controller" + }, { "path": "../passkey-controller" }, diff --git a/packages/wallet/tsconfig.lint.json b/packages/wallet/tsconfig.lint.json index 34b96909506..ff95ae81070 100644 --- a/packages/wallet/tsconfig.lint.json +++ b/packages/wallet/tsconfig.lint.json @@ -50,6 +50,9 @@ { "path": "../network-controller/tsconfig.lint.json" }, + { + "path": "../network-enablement-controller/tsconfig.lint.json" + }, { "path": "../passkey-controller/tsconfig.lint.json" }, diff --git a/yarn.lock b/yarn.lock index d1b04aacac2..9f4081496ed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9793,6 +9793,7 @@ __metadata: "@metamask/config-registry-controller": "npm:^5.0.0" "@metamask/foundryup": "npm:^2.0.0" "@metamask/messenger": "npm:^3.0.0" + "@metamask/multichain-network-controller": "npm:^4.0.0" "@metamask/remote-feature-flag-controller": "npm:^7.0.0" "@metamask/rpc-errors": "npm:^7.0.3" "@metamask/scure-bip39": "npm:^2.1.1" @@ -9871,6 +9872,7 @@ __metadata: "@metamask/logging-controller": "npm:^10.0.0" "@metamask/messenger": "npm:^3.0.0" "@metamask/network-controller": "npm:^37.1.1" + "@metamask/network-enablement-controller": "npm:^7.0.2" "@metamask/passkey-controller": "npm:^4.1.0" "@metamask/permission-controller": "npm:^14.0.0" "@metamask/remote-feature-flag-controller": "npm:^7.0.0" From 5865b8d855bc8a6b0561ba53c32858c71acbf15b Mon Sep 17 00:00:00 2001 From: Xiaoming Wang Date: Fri, 23 Oct 2026 17:32:54 +0800 Subject: [PATCH 2/4] fix(wallet): use Vitest APIs in network enablement test Co-authored-by: Cursor --- .../network-enablement-controller.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/wallet/src/initialization/instances/network-enablement-controller/network-enablement-controller.test.ts b/packages/wallet/src/initialization/instances/network-enablement-controller/network-enablement-controller.test.ts index 74fff842c4e..51e81a87187 100644 --- a/packages/wallet/src/initialization/instances/network-enablement-controller/network-enablement-controller.test.ts +++ b/packages/wallet/src/initialization/instances/network-enablement-controller/network-enablement-controller.test.ts @@ -1,6 +1,7 @@ import { Messenger } from '@metamask/messenger'; import { NetworkEnablementController } from '@metamask/network-enablement-controller'; import type { NetworkEnablementControllerState } from '@metamask/network-enablement-controller'; +import { describe, expect, it, vi } from 'vitest'; import * as walletExports from '../../../index.js'; import { defaultConfigurations } from '../../defaults.js'; @@ -101,7 +102,7 @@ describe('networkEnablementController', () => { 'NetworkController:getState', ])('delegates the %s action', (actionType) => { const rootMessenger = getRootMessenger(); - const handler = jest.fn().mockReturnValue('result'); + const handler = vi.fn().mockReturnValue('result'); registerActionHandler(rootMessenger, actionType, handler); const messenger = networkEnablementController.getMessenger(rootMessenger); @@ -121,7 +122,7 @@ describe('networkEnablementController', () => { ])('delegates the %s event', (eventType) => { const rootMessenger = getRootMessenger(); const messenger = networkEnablementController.getMessenger(rootMessenger); - const listener = jest.fn(); + const listener = vi.fn(); ( messenger as unknown as { subscribe(type: string, handler: ActionHandler): void; From 8c9c3428171da204851f973521276bde712e7976 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 26 Oct 2026 15:02:21 +0000 Subject: [PATCH 3/4] docs(wallet): document controllers Wallet does not construct NetworkEnablementController delegates MultichainNetworkController:getState, and Wallet does not construct that controller. The changelog now says hosts must register the action before wallet.init(), and names the delegated controllers Wallet already constructs. Co-authored-by: Xiaoming Wang --- packages/wallet/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/wallet/CHANGELOG.md b/packages/wallet/CHANGELOG.md index e91b74bf46d..e80631f8da2 100644 --- a/packages/wallet/CHANGELOG.md +++ b/packages/wallet/CHANGELOG.md @@ -10,7 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - **BREAKING:** Add `NetworkEnablementController` to the default initialization configurations ([#10997](https://github.com/MetaMask/core/pull/10997)) - - `Wallet.init()` calls `NetworkEnablementController.init()`, which reads `MultichainNetworkController:getState`. The wallet does not construct `MultichainNetworkController`. Clients must register that action on the wallet messenger before calling `wallet.init()`. + - Delegates `MultichainNetworkController:getState`. `Wallet` does not construct `MultichainNetworkController`. Clients must register that action on the wallet messenger before calling `wallet.init()`. `NetworkEnablementController.init()` reads it during `Wallet.init()`, and `enableAllPopularNetworks` and `listPopularMultichainNetworks` read it later. + - Also delegates `ConfigRegistryController:getState`, `NetworkController:getState`, `NetworkController:networkAdded`, `NetworkController:networkRemoved`, `NetworkController:stateChange`, and `TransactionController:transactionSubmitted`. `Wallet` constructs those controllers during default initialization. - Export `networkEnablementController` so clients can pass it through `initializationConfigurations` before `assetsController`. Additional configurations initialize before defaults, and `AssetsController` reads `NetworkEnablementController:getState` during construction. ### Changed From 41650ee6db2a3dbf29640dfc8d6e50577883bacd Mon Sep 17 00:00:00 2001 From: Xiaoming Wang Date: Tue, 27 Oct 2026 12:03:16 +0800 Subject: [PATCH 4/4] fix(wallet): drop the networkEnablementController package export The export existed so hosts could construct NetworkEnablementController before AssetsController. That constructor read moves into AssetsController.init() in a follow-up. Co-authored-by: Cursor --- packages/wallet/CHANGELOG.md | 1 - packages/wallet/src/index.ts | 1 - .../network-enablement-controller.test.ts | 7 ------- 3 files changed, 9 deletions(-) diff --git a/packages/wallet/CHANGELOG.md b/packages/wallet/CHANGELOG.md index d73c1b5f0ab..fab293d3b65 100644 --- a/packages/wallet/CHANGELOG.md +++ b/packages/wallet/CHANGELOG.md @@ -12,7 +12,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **BREAKING:** Add `NetworkEnablementController` to the default initialization configurations ([#10997](https://github.com/MetaMask/core/pull/10997)) - Delegates `MultichainNetworkController:getState`. `Wallet` does not construct `MultichainNetworkController`. Clients must register that action on the wallet messenger before calling `wallet.init()`. `NetworkEnablementController.init()` reads it during `Wallet.init()`, and `enableAllPopularNetworks` and `listPopularMultichainNetworks` read it later. - Also delegates `ConfigRegistryController:getState`, `NetworkController:getState`, `NetworkController:networkAdded`, `NetworkController:networkRemoved`, `NetworkController:stateChange`, and `TransactionController:transactionSubmitted`. `Wallet` constructs those controllers during default initialization. - - Export `networkEnablementController` so clients can pass it through `initializationConfigurations` before `assetsController`. Additional configurations initialize before defaults, and `AssetsController` reads `NetworkEnablementController:getState` during construction. ### Changed diff --git a/packages/wallet/src/index.ts b/packages/wallet/src/index.ts index 64f137ad7d4..50e9b004160 100644 --- a/packages/wallet/src/index.ts +++ b/packages/wallet/src/index.ts @@ -1,7 +1,6 @@ export { Wallet } from './Wallet.js'; export { AlwaysOnlineAdapter } from './initialization/instances/connectivity-controller/always-online-adapter.js'; export { importSecretRecoveryPhrase } from './utilities.js'; -export { networkEnablementController } from './initialization/instances/network-enablement-controller/network-enablement-controller.js'; export type { WalletOptions } from './types.js'; export type { DefaultActions, diff --git a/packages/wallet/src/initialization/instances/network-enablement-controller/network-enablement-controller.test.ts b/packages/wallet/src/initialization/instances/network-enablement-controller/network-enablement-controller.test.ts index 51e81a87187..eb14befe35a 100644 --- a/packages/wallet/src/initialization/instances/network-enablement-controller/network-enablement-controller.test.ts +++ b/packages/wallet/src/initialization/instances/network-enablement-controller/network-enablement-controller.test.ts @@ -3,7 +3,6 @@ import { NetworkEnablementController } from '@metamask/network-enablement-contro import type { NetworkEnablementControllerState } from '@metamask/network-enablement-controller'; import { describe, expect, it, vi } from 'vitest'; -import * as walletExports from '../../../index.js'; import { defaultConfigurations } from '../../defaults.js'; import type { DefaultActions, @@ -43,12 +42,6 @@ describe('networkEnablementController', () => { ); }); - it('is exported from the package entry point', () => { - expect(walletExports.networkEnablementController).toBe( - networkEnablementController, - ); - }); - it('initializes a NetworkEnablementController with default state', () => { const messenger = networkEnablementController.getMessenger(getRootMessenger());