diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b957029a8c5..a0f63e8c536 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -897,6 +897,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 1e174a68620..e3e9a39af66 100644 --- a/codeowners.ts +++ b/codeowners.ts @@ -258,6 +258,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 ddda33e6cfd..b4bcb5140eb 100644 --- a/packages/network-enablement-controller/CHANGELOG.md +++ b/packages/network-enablement-controller/CHANGELOG.md @@ -22,6 +22,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/CHANGELOG.md b/packages/wallet/CHANGELOG.md index d2efe17ef06..fab293d3b65 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)) + - 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. + ### Changed +- Add `@metamask/network-enablement-controller` as a dependency ([#10997](https://github.com/MetaMask/core/pull/10997)) - Bump `@metamask/claims-controller` from `^1.0.4` to `^1.1.0` ([#11092](https://github.com/MetaMask/core/pull/11092)) - Bump `@metamask/shield-controller` from `^7.0.5` to `^7.1.0` ([#11092](https://github.com/MetaMask/core/pull/11092)) - Bump `@metamask/subscription-controller` from `^15.0.0` to `^15.1.0` ([#11092](https://github.com/MetaMask/core/pull/11092)) diff --git a/packages/wallet/package.json b/packages/wallet/package.json index b4bcb42bbc2..655a98ed46f 100644 --- a/packages/wallet/package.json +++ b/packages/wallet/package.json @@ -67,6 +67,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 8714053046b..6a3033710f3 100644 --- a/packages/wallet/src/Wallet.test.ts +++ b/packages/wallet/src/Wallet.test.ts @@ -199,7 +199,7 @@ describe('Wallet', () => { const results = await wallet.init(); - expect(results).toHaveLength(7); + expect(results).toHaveLength(8); }); it('disallows modifying the messenger', async () => { diff --git a/packages/wallet/src/initialization/instances/index.ts b/packages/wallet/src/initialization/instances/index.ts index ea1694abb1d..4055cad6ada 100644 --- a/packages/wallet/src/initialization/instances/index.ts +++ b/packages/wallet/src/initialization/instances/index.ts @@ -14,6 +14,7 @@ export { geolocationController } from './geolocation-controller/geolocation-cont 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..eb14befe35a --- /dev/null +++ b/packages/wallet/src/initialization/instances/network-enablement-controller/network-enablement-controller.test.ts @@ -0,0 +1,163 @@ +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 { 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('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 = vi.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 = vi.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 00bdbbf398d..dfdc85cf60f 100644 --- a/packages/wallet/tsconfig.build.json +++ b/packages/wallet/tsconfig.build.json @@ -50,6 +50,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 70f5feb27be..d16557d7cd7 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 3d3d9d2b590..ae0727ede27 100644 --- a/packages/wallet/tsconfig.lint.json +++ b/packages/wallet/tsconfig.lint.json @@ -53,6 +53,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 48a291b9e37..921ad40bbfa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9163,6 +9163,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"