From 29e2f369bef8dd4f49f1c5b5aa0aad6845f706ae Mon Sep 17 00:00:00 2001 From: mikesposito Date: Fri, 14 Aug 2026 13:11:21 +0200 Subject: [PATCH 01/23] feat: auto-enable networks from `config-registry` --- package.json | 4 +- .../config-registry-controller/CHANGELOG.md | 4 + .../src/config-registry-api-service/types.ts | 1 + .../config-registry-controller/src/index.ts | 1 + packages/network-controller/CHANGELOG.md | 2 + packages/network-controller/package.json | 1 + .../src/NetworkController.ts | 76 +++++++++++++++++-- packages/network-controller/src/selectors.ts | 20 +++++ .../network-controller/tsconfig.build.json | 1 + packages/network-controller/tsconfig.json | 1 + yarn.lock | 1 + 11 files changed, 106 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index fcba376a8c3..a4cc9bd0ea5 100644 --- a/package.json +++ b/package.json @@ -137,7 +137,9 @@ "eslint-plugin-import-x>unrs-resolver": false, "simple-git-hooks": false, "tsx>esbuild": false, - "jest>@jest/core>jest-resolve>unrs-resolver": false + "jest>@jest/core>jest-resolve>unrs-resolver": false, + "@metamask/network-controller>@metamask/config-registry-controller>@metamask/keyring-controller>ethereumjs-wallet>ethereum-cryptography>keccak": false, + "@metamask/network-controller>@metamask/config-registry-controller>@metamask/keyring-controller>ethereumjs-wallet>ethereum-cryptography>secp256k1": false } } } diff --git a/packages/config-registry-controller/CHANGELOG.md b/packages/config-registry-controller/CHANGELOG.md index f3d6629e2e5..ff1fd02cba9 100644 --- a/packages/config-registry-controller/CHANGELOG.md +++ b/packages/config-registry-controller/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add optional `isAutoEnabled?: boolean` property to `RegistryNetworkConfig.config` ([#0000](https://github.com/MetaMask/core/pull/0000)) + ### Changed - Bump `@metamask/superstruct` from `^3.1.0` to `^3.4.1` ([#9754](https://github.com/MetaMask/core/pull/9754)) diff --git a/packages/config-registry-controller/src/config-registry-api-service/types.ts b/packages/config-registry-controller/src/config-registry-api-service/types.ts index d1604ff8554..4019cbd15a4 100644 --- a/packages/config-registry-controller/src/config-registry-api-service/types.ts +++ b/packages/config-registry-controller/src/config-registry-api-service/types.ts @@ -48,6 +48,7 @@ const ChainConfigSchema = type({ isFeatured: boolean(), isDeprecated: boolean(), isDeletable: boolean(), + isAutoEnabled: boolean(), priority: number(), }); diff --git a/packages/config-registry-controller/src/index.ts b/packages/config-registry-controller/src/index.ts index f107ae4abc9..4642b1a87b6 100644 --- a/packages/config-registry-controller/src/index.ts +++ b/packages/config-registry-controller/src/index.ts @@ -3,6 +3,7 @@ export type { ConfigRegistryControllerOptions, ConfigRegistryControllerActions, ConfigRegistryControllerGetStateAction, + ConfigRegistryControllerStateChangedEvent, ConfigRegistryControllerEvents, ConfigRegistryControllerMessenger, } from './ConfigRegistryController.js'; diff --git a/packages/network-controller/CHANGELOG.md b/packages/network-controller/CHANGELOG.md index 6e97a2dc8d6..3aef1153cce 100644 --- a/packages/network-controller/CHANGELOG.md +++ b/packages/network-controller/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- **BREAKING:** `NetworkControllerMessenger` now requires the `ConfigRegistryController:stateChanged` event and `ConfigRegistryController:getNetworkConfigByCaip2ChainId` action to be delegated from the root messenger ([#0000](https://github.com/MetaMask/core/pull/0000)) + - `NetworkController` now depends on the `ConfigRegistryController` to auto-enable default networks from the registry. - Bump `@metamask/eth-json-rpc-middleware` from `^23.1.3` to `^24.0.0` ([#9758](https://github.com/MetaMask/core/pull/9758)) ## [35.0.0] diff --git a/packages/network-controller/package.json b/packages/network-controller/package.json index 06e583d125a..19eb309031b 100644 --- a/packages/network-controller/package.json +++ b/packages/network-controller/package.json @@ -57,6 +57,7 @@ "dependencies": { "@metamask/analytics-controller": "^2.0.0", "@metamask/base-controller": "^9.1.0", + "@metamask/config-registry-controller": "^2.0.1", "@metamask/connectivity-controller": "^0.3.0", "@metamask/controller-utils": "^12.3.0", "@metamask/eth-block-tracker": "^15.0.1", diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index 13d91c8b3f0..2927b5cc9bc 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -8,6 +8,10 @@ import type { } from '@metamask/base-controller'; import { BaseController } from '@metamask/base-controller'; import type { ConnectivityControllerGetStateAction } from '@metamask/connectivity-controller'; +import type { + ConfigRegistryControllerStateChangedEvent, + ConfigRegistryControllerGetNetworkConfigByCaip2ChainIdAction, +} from '@metamask/config-registry-controller'; import type { Partialize } from '@metamask/controller-utils'; import { InfuraNetworkType, @@ -35,8 +39,14 @@ import { createSwappableProxy, } from '@metamask/swappable-obj-proxy'; import type { SwappableProxy } from '@metamask/swappable-obj-proxy'; -import type { Hex } from '@metamask/utils'; -import { hasProperty, isPlainObject, isStrictHexString } from '@metamask/utils'; +import type { CaipChainId, Hex } from '@metamask/utils'; +import { + add0x, + hasProperty, + isPlainObject, + isStrictHexString, + parseCaipChainId, +} from '@metamask/utils'; import deepEqual from 'fast-deep-equal'; import type { Draft } from 'immer'; import { produce } from 'immer'; @@ -71,7 +81,10 @@ import type { ResolvedNetworkControllerAnalyticsOptions, } from './rpc-service-analytics.js'; import type { RpcServiceOptionsWithDefaults } from './rpc-service/rpc-service.js'; -import { getRpcFailoverMode } from './selectors.js'; +import { + getConfigRegistryEvmAutoEnabledChains, + getRpcFailoverMode, +} from './selectors.js'; import type { RpcFailoverMode } from './selectors.js'; import { NetworkClientType } from './types.js'; import type { @@ -680,7 +693,9 @@ export type NetworkControllerEvents = /** * All events that {@link NetworkController} calls internally. */ -type AllowedEvents = RemoteFeatureFlagControllerStateChangeEvent; +type AllowedEvents = + | RemoteFeatureFlagControllerStateChangeEvent + | ConfigRegistryControllerStateChangedEvent; const MESSENGER_EXPOSED_METHODS = [ 'addNetwork', @@ -726,7 +741,8 @@ type AllowedActions = | ConnectivityControllerGetStateAction | RemoteFeatureFlagControllerGetStateAction | AnalyticsControllerGetStateAction - | AnalyticsControllerTrackEventAction; + | AnalyticsControllerTrackEventAction + | ConfigRegistryControllerGetNetworkConfigByCaip2ChainIdAction; export type NetworkControllerMessenger = Messenger< typeof controllerName, @@ -1418,6 +1434,12 @@ export class NetworkController extends BaseController< }, getRpcFailoverMode, ); + + this.messenger.subscribe( + 'ConfigRegistryController:stateChanged', + (autoEnabledChains) => this.#autoEnableChains(autoEnabledChains), + getConfigRegistryEvmAutoEnabledChains, + ); } /** @@ -3133,4 +3155,48 @@ export class NetworkController extends BaseController< this.#ethQuery = new EthQuery(this.#providerProxy); } + + /** + * Enables networks for the given CAIP-2 chain IDs by adding + * them to state if they are not already present. Configurations for + * these networks are retrieved from ConfigRegistryController. + * + * @param chainIds - The CAIP-2 chain IDs of the networks to enable. + */ + #autoEnableChains(chainIds: CaipChainId[]): void { + for (const chainId of chainIds) { + const networkConfiguration = this.messenger.call( + 'ConfigRegistryController:getNetworkConfigByCaip2ChainId', + chainId, + ); + const hexChainId = add0x(parseCaipChainId(chainId).reference); + + if (!networkConfiguration || this.getNetworkClientById(hexChainId)) { + return; + } + + const rpcEndpoint: InfuraRpcEndpoint | AddNetworkCustomRpcEndpointFields = + networkConfiguration.rpcProviders.default.type === 'infura' + ? { + type: RpcEndpointType.Infura, + networkClientId: hexChainId, + url: networkConfiguration.rpcProviders.default + .url as InfuraRpcEndpoint['url'], + } + : { + type: RpcEndpointType.Custom, + url: networkConfiguration.rpcProviders.default.url, + }; + + this.addNetwork({ + chainId: hexChainId, + name: networkConfiguration.name, + nativeCurrency: networkConfiguration.assets.native.symbol, + rpcEndpoints: [rpcEndpoint], + defaultRpcEndpointIndex: 0, + blockExplorerUrls: [networkConfiguration.blockExplorerUrls.default], + defaultBlockExplorerUrlIndex: 0, + }); + } + } } diff --git a/packages/network-controller/src/selectors.ts b/packages/network-controller/src/selectors.ts index 60406079d8a..7cc9451cb1e 100644 --- a/packages/network-controller/src/selectors.ts +++ b/packages/network-controller/src/selectors.ts @@ -1,4 +1,6 @@ +import { ConfigRegistryControllerState } from '@metamask/config-registry-controller'; import { RemoteFeatureFlagControllerState } from '@metamask/remote-feature-flag-controller'; +import { CaipChainId, KnownCaipNamespace } from '@metamask/utils'; /** * The RPC failover behavior for Infura networks, controlled by the @@ -26,3 +28,21 @@ export function getRpcFailoverMode( const mode = state.remoteFeatureFlags.corePlatformRpcFailoverMode; return mode === 'enabled' || mode === 'forced' ? mode : 'disabled'; } + +/** + * Returns the list of CAIP-2 chain IDs for networks that are auto-enabled in the + * config registry. + * + * @param state - The config registry controller state. + * @returns The list of CAIP-2 chain IDs for auto-enabled networks. + */ +export function getConfigRegistryEvmAutoEnabledChains( + state: ConfigRegistryControllerState, +): CaipChainId[] { + return Object.values(state.configs.networks) + .filter( + ({ chainId, config }) => + chainId.startsWith(KnownCaipNamespace.Eip155) && config.isAutoEnabled, + ) + .map((config) => config.chainId); +} diff --git a/packages/network-controller/tsconfig.build.json b/packages/network-controller/tsconfig.build.json index eaab8d986d3..0ce20a4381b 100644 --- a/packages/network-controller/tsconfig.build.json +++ b/packages/network-controller/tsconfig.build.json @@ -8,6 +8,7 @@ "references": [ { "path": "../analytics-controller/tsconfig.build.json" }, { "path": "../base-controller/tsconfig.build.json" }, + { "path": "../config-registry-controller/tsconfig.build.json" }, { "path": "../controller-utils/tsconfig.build.json" }, { "path": "../connectivity-controller/tsconfig.build.json" }, { "path": "../eth-block-tracker/tsconfig.build.json" }, diff --git a/packages/network-controller/tsconfig.json b/packages/network-controller/tsconfig.json index 292dec5bbc8..543c1616011 100644 --- a/packages/network-controller/tsconfig.json +++ b/packages/network-controller/tsconfig.json @@ -7,6 +7,7 @@ "references": [ { "path": "../analytics-controller" }, { "path": "../base-controller" }, + { "path": "../config-registry-controller" }, { "path": "../controller-utils" }, { "path": "../connectivity-controller" }, { "path": "../eth-block-tracker" }, diff --git a/yarn.lock b/yarn.lock index 236ea87072e..e4baedbe6f0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8166,6 +8166,7 @@ __metadata: "@metamask/analytics-controller": "npm:^2.0.0" "@metamask/auto-changelog": "npm:^6.1.0" "@metamask/base-controller": "npm:^9.1.0" + "@metamask/config-registry-controller": "npm:^2.0.1" "@metamask/connectivity-controller": "npm:^0.3.0" "@metamask/controller-utils": "npm:^12.3.0" "@metamask/eth-block-tracker": "npm:^15.0.1" From 58a5796c1c1b30b082a2298f6a879325ec2301af Mon Sep 17 00:00:00 2001 From: Michele Esposito <34438276+mikesposito@users.noreply.github.com> Date: Fri, 14 Aug 2026 13:15:46 +0200 Subject: [PATCH 02/23] update changelogs Co-authored-by: Michele Esposito <34438276+mikesposito@users.noreply.github.com> --- packages/config-registry-controller/CHANGELOG.md | 2 +- packages/network-controller/CHANGELOG.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/config-registry-controller/CHANGELOG.md b/packages/config-registry-controller/CHANGELOG.md index ff1fd02cba9..97f2705e9ed 100644 --- a/packages/config-registry-controller/CHANGELOG.md +++ b/packages/config-registry-controller/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Add optional `isAutoEnabled?: boolean` property to `RegistryNetworkConfig.config` ([#0000](https://github.com/MetaMask/core/pull/0000)) +- Add optional `isAutoEnabled?: boolean` property to `RegistryNetworkConfig.config` ([#9879](https://github.com/MetaMask/core/pull/9879)) ### Changed diff --git a/packages/network-controller/CHANGELOG.md b/packages/network-controller/CHANGELOG.md index 3aef1153cce..6df63f91ef7 100644 --- a/packages/network-controller/CHANGELOG.md +++ b/packages/network-controller/CHANGELOG.md @@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- **BREAKING:** `NetworkControllerMessenger` now requires the `ConfigRegistryController:stateChanged` event and `ConfigRegistryController:getNetworkConfigByCaip2ChainId` action to be delegated from the root messenger ([#0000](https://github.com/MetaMask/core/pull/0000)) +- **BREAKING:** `NetworkControllerMessenger` now requires the `ConfigRegistryController:stateChanged` event and `ConfigRegistryController:getNetworkConfigByCaip2ChainId` action to be delegated from the root messenger ([#9879](https://github.com/MetaMask/core/pull/9879)) - `NetworkController` now depends on the `ConfigRegistryController` to auto-enable default networks from the registry. - Bump `@metamask/eth-json-rpc-middleware` from `^23.1.3` to `^24.0.0` ([#9758](https://github.com/MetaMask/core/pull/9758)) From 6bd9b0018a67e0daf0cacf40d42463055cb87f03 Mon Sep 17 00:00:00 2001 From: mikesposito Date: Tue, 18 Aug 2026 12:51:31 +0200 Subject: [PATCH 03/23] set property as optional --- .../src/config-registry-api-service/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/config-registry-controller/src/config-registry-api-service/types.ts b/packages/config-registry-controller/src/config-registry-api-service/types.ts index 4019cbd15a4..18343ffba07 100644 --- a/packages/config-registry-controller/src/config-registry-api-service/types.ts +++ b/packages/config-registry-controller/src/config-registry-api-service/types.ts @@ -48,7 +48,7 @@ const ChainConfigSchema = type({ isFeatured: boolean(), isDeprecated: boolean(), isDeletable: boolean(), - isAutoEnabled: boolean(), + isAutoEnabled: optional(boolean()), priority: number(), }); From c9d1a5aaeba76f55e252ea7777da459870ebbd11 Mon Sep 17 00:00:00 2001 From: mikesposito Date: Tue, 18 Aug 2026 12:51:35 +0200 Subject: [PATCH 04/23] update readme content --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 21a625cf759..1cee67adae7 100644 --- a/README.md +++ b/README.md @@ -488,6 +488,7 @@ linkStyle default opacity:0.5 network_connection_banner_controller --> network_enablement_controller; network_controller --> analytics_controller; network_controller --> base_controller; + network_controller --> config_registry_controller; network_controller --> connectivity_controller; network_controller --> controller_utils; network_controller --> eth_block_tracker; From 6431e51913b7ee7191ea38bf7ed0f9ed8ceab94c Mon Sep 17 00:00:00 2001 From: mikesposito Date: Tue, 18 Aug 2026 12:55:12 +0200 Subject: [PATCH 05/23] fix `NetworkController.ts` formatting --- packages/network-controller/src/NetworkController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index 2927b5cc9bc..7c7c25baea9 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -7,11 +7,11 @@ import type { ControllerStateChangeEvent, } from '@metamask/base-controller'; import { BaseController } from '@metamask/base-controller'; -import type { ConnectivityControllerGetStateAction } from '@metamask/connectivity-controller'; import type { ConfigRegistryControllerStateChangedEvent, ConfigRegistryControllerGetNetworkConfigByCaip2ChainIdAction, } from '@metamask/config-registry-controller'; +import type { ConnectivityControllerGetStateAction } from '@metamask/connectivity-controller'; import type { Partialize } from '@metamask/controller-utils'; import { InfuraNetworkType, From 63f6dfe1ee065e6c3e345bc813dd7fcddb6f6b19 Mon Sep 17 00:00:00 2001 From: mikesposito Date: Tue, 18 Aug 2026 16:07:36 +0200 Subject: [PATCH 06/23] update tests --- packages/network-controller/jest.config.js | 8 +- .../src/NetworkController.ts | 32 +++- .../network-controller/src/selectors.test.ts | 63 +++++++- packages/network-controller/src/selectors.ts | 5 +- .../tests/NetworkController.test.ts | 151 ++++++++++++++++++ packages/network-controller/tests/helpers.ts | 86 +++++++++- 6 files changed, 329 insertions(+), 16 deletions(-) diff --git a/packages/network-controller/jest.config.js b/packages/network-controller/jest.config.js index 245fc38807a..bd532ddb09e 100644 --- a/packages/network-controller/jest.config.js +++ b/packages/network-controller/jest.config.js @@ -17,10 +17,10 @@ module.exports = merge(baseConfig, { // An object that configures minimum threshold enforcement for coverage results coverageThreshold: { global: { - branches: 93.42, - functions: 98, - lines: 97.67, - statements: 97.56, + branches: 94.04, + functions: 98.17, + lines: 97.94, + statements: 97.83, }, }, diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index 7c7c25baea9..b02f025a7d6 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -10,6 +10,7 @@ import { BaseController } from '@metamask/base-controller'; import type { ConfigRegistryControllerStateChangedEvent, ConfigRegistryControllerGetNetworkConfigByCaip2ChainIdAction, + ConfigRegistryControllerGetStateAction, } from '@metamask/config-registry-controller'; import type { ConnectivityControllerGetStateAction } from '@metamask/connectivity-controller'; import type { Partialize } from '@metamask/controller-utils'; @@ -45,6 +46,7 @@ import { hasProperty, isPlainObject, isStrictHexString, + numberToHex, parseCaipChainId, } from '@metamask/utils'; import deepEqual from 'fast-deep-equal'; @@ -738,11 +740,12 @@ export type NetworkControllerActions = * All actions that {@link NetworkController} calls internally. */ type AllowedActions = + | ConfigRegistryControllerGetNetworkConfigByCaip2ChainIdAction + | ConfigRegistryControllerGetStateAction | ConnectivityControllerGetStateAction | RemoteFeatureFlagControllerGetStateAction | AnalyticsControllerGetStateAction - | AnalyticsControllerTrackEventAction - | ConfigRegistryControllerGetNetworkConfigByCaip2ChainIdAction; + | AnalyticsControllerTrackEventAction; export type NetworkControllerMessenger = Messenger< typeof controllerName, @@ -1639,15 +1642,22 @@ export class NetworkController extends BaseController< } /** - * Initialize the NetworkController, applying the RPC failover mode from the - * `corePlatformRpcFailoverMode` remote feature flag and applying the network - * selection. + * Initialize the NetworkController: + * - Apply the RPC failover mode from the `corePlatformRpcFailoverMode` remote feature flag; + * - Apply the network selection. + * - Auto-enable any chains that are configured to be auto-enabled in ConfigRegistryController. */ init(): void { const state = this.messenger.call('RemoteFeatureFlagController:getState'); this.#updateRpcFailover(getRpcFailoverMode(state)); this.#applyNetworkSelection(this.state.selectedNetworkClientId); + + this.#autoEnableChains( + getConfigRegistryEvmAutoEnabledChains( + this.messenger.call('ConfigRegistryController:getState'), + ), + ); } /** @@ -3169,9 +3179,14 @@ export class NetworkController extends BaseController< 'ConfigRegistryController:getNetworkConfigByCaip2ChainId', chainId, ); - const hexChainId = add0x(parseCaipChainId(chainId).reference); + const hexChainId = numberToHex( + Number(parseCaipChainId(chainId).reference), + ); - if (!networkConfiguration || this.getNetworkClientById(hexChainId)) { + if ( + !networkConfiguration || + this.state.networkConfigurationsByChainId[hexChainId] + ) { return; } @@ -3179,7 +3194,8 @@ export class NetworkController extends BaseController< networkConfiguration.rpcProviders.default.type === 'infura' ? { type: RpcEndpointType.Infura, - networkClientId: hexChainId, + networkClientId: + networkConfiguration.rpcProviders.default.networkClientId, url: networkConfiguration.rpcProviders.default .url as InfuraRpcEndpoint['url'], } diff --git a/packages/network-controller/src/selectors.test.ts b/packages/network-controller/src/selectors.test.ts index 0fed844f57e..b36806b8f6c 100644 --- a/packages/network-controller/src/selectors.test.ts +++ b/packages/network-controller/src/selectors.test.ts @@ -1,4 +1,7 @@ -import { getRpcFailoverMode } from './selectors.js'; +import { + getConfigRegistryEvmAutoEnabledChains, + getRpcFailoverMode, +} from './selectors.js'; /** * Builds a remote feature flag controller state with the given failover mode. @@ -40,3 +43,61 @@ describe('getRpcFailoverMode', () => { expect(getRpcFailoverMode(buildState('yes') as never)).toBe('disabled'); }); }); + +describe('getConfigRegistryEvmAutoEnabledChains', () => { + it('returns the list of CAIP-2 chain IDs for auto-enabled EVM networks', () => { + const state = { + configs: { + networks: { + 'eip155:1': { + chainId: 'eip155:1', + config: { + isAutoEnabled: true, + isActive: true, + isDeprecated: false, + }, + }, + 'eip155:3': { + chainId: 'eip155:3', + config: { + isAutoEnabled: false, + isActive: true, + isDeprecated: false, + }, + }, + 'eip155:4': { + chainId: 'eip155:4', + config: { + isAutoEnabled: true, + isActive: false, + isDeprecated: false, + }, + }, + 'eip155:5': { + chainId: 'eip155:5', + config: { isAutoEnabled: true, isActive: true, isDeprecated: true }, + }, + 'eip155:6': { + chainId: 'eip155:6', + config: { + isAutoEnabled: true, + isActive: true, + isDeprecated: false, + }, + }, + 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp': { + chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', + config: { + isAutoEnabled: true, + isActive: true, + isDeprecated: false, + }, + }, + }, + }, + }; + + const result = getConfigRegistryEvmAutoEnabledChains(state); + expect(result).toStrictEqual(['eip155:1', 'eip155:6']); + }); +}); diff --git a/packages/network-controller/src/selectors.ts b/packages/network-controller/src/selectors.ts index 7cc9451cb1e..799bcce3c28 100644 --- a/packages/network-controller/src/selectors.ts +++ b/packages/network-controller/src/selectors.ts @@ -42,7 +42,10 @@ export function getConfigRegistryEvmAutoEnabledChains( return Object.values(state.configs.networks) .filter( ({ chainId, config }) => - chainId.startsWith(KnownCaipNamespace.Eip155) && config.isAutoEnabled, + chainId.startsWith(KnownCaipNamespace.Eip155) && + config.isAutoEnabled && + config.isActive && + !config.isDeprecated, ) .map((config) => config.chainId); } diff --git a/packages/network-controller/tests/NetworkController.test.ts b/packages/network-controller/tests/NetworkController.test.ts index 34b0ae744dd..68f33a2760a 100644 --- a/packages/network-controller/tests/NetworkController.test.ts +++ b/packages/network-controller/tests/NetworkController.test.ts @@ -57,6 +57,7 @@ import { buildInfuraNetworkClientConfiguration, buildInfuraNetworkConfiguration, buildInfuraRpcEndpoint, + buildMockConfigRegistryControllerNetwork, buildNetworkConfiguration, buildNetworkControllerMessenger, buildRootMessenger, @@ -996,6 +997,156 @@ describe('NetworkController', () => { }); }); + describe('init', () => { + it('auto-enables networks that are set as auto-enabled in the config registry', async () => { + const networkConfig = buildMockConfigRegistryControllerNetwork({ + chainId: 'eip155:9999', + config: { + ...buildMockConfigRegistryControllerNetwork().config, + isAutoEnabled: true, + }, + }); + await withController( + { + initializeController: false, + configRegistryNetworkConfig: networkConfig, + }, + ({ controller }) => { + expect( + controller.state.networkConfigurationsByChainId, + ).not.toHaveProperty('0x270f'); + + controller.init(); + + expect( + controller.state.networkConfigurationsByChainId, + ).toHaveProperty( + '0x270f', + expect.objectContaining({ + chainId: '0x270f', + name: networkConfig.name, + nativeCurrency: networkConfig.assets.native.symbol, + blockExplorerUrls: [networkConfig.blockExplorerUrls.default], + defaultBlockExplorerUrlIndex: 0, + rpcEndpoints: [ + expect.objectContaining({ + networkClientId: + networkConfig.rpcProviders.default.networkClientId, + url: networkConfig.rpcProviders.default.url, + type: networkConfig.rpcProviders.default.type, + }), + ], + defaultRpcEndpointIndex: 0, + }), + ); + }, + ); + }); + }); + + describe('ConfigRegistryController:stateChanged', () => { + it('enables Infura chains that are set as auto-enabled in the config registry', async () => { + const networkConfig = buildMockConfigRegistryControllerNetwork({ + chainId: 'eip155:9999', + config: { + ...buildMockConfigRegistryControllerNetwork().config, + isAutoEnabled: true, + }, + }); + await withController( + { configRegistryNetworkConfig: networkConfig }, + async ({ controller, messenger }) => { + messenger.publish( + 'ConfigRegistryController:stateChanged', + { + configs: { networks: { 'eip155:9999': networkConfig } }, + lastFetched: 0, + etag: 'etag', + version: '1', + }, + [], + ); + + expect( + controller.state.networkConfigurationsByChainId, + ).toHaveProperty( + '0x270f', + expect.objectContaining({ + chainId: '0x270f', + name: networkConfig.name, + nativeCurrency: networkConfig.assets.native.symbol, + blockExplorerUrls: [networkConfig.blockExplorerUrls.default], + defaultBlockExplorerUrlIndex: 0, + rpcEndpoints: [ + expect.objectContaining({ + networkClientId: + networkConfig.rpcProviders.default.networkClientId, + url: networkConfig.rpcProviders.default.url, + type: networkConfig.rpcProviders.default.type, + }), + ], + defaultRpcEndpointIndex: 0, + }), + ); + }, + ); + }); + + it('enables custom networks that are set as auto-enabled in the config registry', async () => { + const networkConfig = buildMockConfigRegistryControllerNetwork({ + chainId: 'eip155:9999', + rpcProviders: { + default: buildCustomRpcEndpoint({ + url: 'https://test.network/1', + type: RpcEndpointType.Custom, + }), + fallbacks: [], + }, + config: { + ...buildMockConfigRegistryControllerNetwork().config, + isAutoEnabled: true, + }, + }); + await withController( + { configRegistryNetworkConfig: networkConfig }, + async ({ controller, messenger }) => { + messenger.publish( + 'ConfigRegistryController:stateChanged', + { + configs: { networks: { 'eip155:9999': networkConfig } }, + lastFetched: 0, + etag: 'etag', + version: '1', + }, + [], + ); + + expect( + controller.state.networkConfigurationsByChainId, + ).toHaveProperty( + '0x270f', + expect.objectContaining({ + chainId: '0x270f', + name: networkConfig.name, + nativeCurrency: networkConfig.assets.native.symbol, + blockExplorerUrls: [networkConfig.blockExplorerUrls.default], + defaultBlockExplorerUrlIndex: 0, + rpcEndpoints: [ + expect.objectContaining({ + networkClientId: + networkConfig.rpcProviders.default.networkClientId, + url: networkConfig.rpcProviders.default.url, + type: networkConfig.rpcProviders.default.type, + }), + ], + defaultRpcEndpointIndex: 0, + }), + ); + }, + ); + }); + }); + describe('destroy', () => { it('does not throw if called before the provider is initialized', async () => { await withController(async ({ controller }) => { diff --git a/packages/network-controller/tests/helpers.ts b/packages/network-controller/tests/helpers.ts index c522757f098..27578a304d2 100644 --- a/packages/network-controller/tests/helpers.ts +++ b/packages/network-controller/tests/helpers.ts @@ -49,6 +49,7 @@ import type { InfuraNetworkClientConfiguration, } from '../src/types.js'; import { NetworkClientType } from '../src/types.js'; +import { RegistryNetworkConfig } from '@metamask/config-registry-controller'; export type AllNetworkControllerActions = MessengerActions; @@ -97,6 +98,9 @@ export const TESTNET = { * @param options.trackEvent - The handler registered for * `AnalyticsController:trackEvent`. Defaults to a Jest mock so tests can assert * on it. + * @param options.configRegistryNetworkConfig - The network config that + * `ConfigRegistryController:getNetworkConfigByCaip2ChainId` returns by default. Defaults to + * a mock network config for the chain ID `eip155:9999`. * @returns The messenger. */ export function buildRootMessenger({ @@ -104,11 +108,13 @@ export function buildRootMessenger({ rpcFailoverMode = 'disabled', analyticsId = '11111111-1111-4111-8111-111111111111', trackEvent = jest.fn(), + configRegistryNetworkConfig = buildMockConfigRegistryControllerNetwork(), }: { connectivityStatus?: ConnectivityStatus; rpcFailoverMode?: RpcFailoverMode; analyticsId?: string; trackEvent?: jest.Mock; + configRegistryNetworkConfig?: RegistryNetworkConfig; } = {}): RootMessenger { const rootMessenger = new Messenger< MockAnyNamespace, @@ -143,6 +149,25 @@ export function buildRootMessenger({ trackEvent, ); + rootMessenger.registerActionHandler( + 'ConfigRegistryController:getNetworkConfigByCaip2ChainId', + () => configRegistryNetworkConfig, + ); + + rootMessenger.registerActionHandler( + 'ConfigRegistryController:getState', + () => ({ + configs: { + networks: { + [configRegistryNetworkConfig.chainId]: configRegistryNetworkConfig, + }, + }, + version: '0', + lastFetched: 0, + etag: '', + }), + ); + return rootMessenger; } @@ -168,13 +193,18 @@ export function buildNetworkControllerMessenger( rootMessenger.delegate({ messenger: networkControllerMessenger, actions: [ + 'ConfigRegistryController:getNetworkConfigByCaip2ChainId', + 'ConfigRegistryController:getState', 'ConnectivityController:getState', 'RemoteFeatureFlagController:getState', 'AnalyticsController:getState', 'AnalyticsController:trackEvent', ], - // eslint-disable-next-line no-restricted-syntax - events: ['RemoteFeatureFlagController:stateChange'], + events: [ + // eslint-disable-next-line no-restricted-syntax + 'RemoteFeatureFlagController:stateChange', + 'ConfigRegistryController:stateChanged', + ], }); return networkControllerMessenger; @@ -645,6 +675,55 @@ function generateCustomRpcEndpointUrl(): string { return url; } +/** + * Builds a mock RegistryNetworkConfig object for use in testing, providing defaults + * and allowing properties to be overridden at will. + * + * @param override - The properties to override the new RegistryNetworkConfig with. + * @returns The complete RegistryNetworkConfig object. + */ +export function buildMockConfigRegistryControllerNetwork( + override: Partial = {}, +): RegistryNetworkConfig { + return { + chainId: 'eip155:9999', + imageUrl: 'https://example.com/network-logo.png', + coingeckoPlatformId: 'ethereum', + name: 'Ethereum Mainnet', + assets: { + native: { + assetId: 'eip155:1/slip44:60', + imageUrl: 'https://example.com/eth-logo.png', + name: 'Ether', + symbol: 'ETH', + decimals: 18, + }, + }, + rpcProviders: { + default: { + type: RpcEndpointType.Infura, + url: 'https://my-network.infura.io/v3/{infuraProjectId}', + networkClientId: 'my-network', + }, + fallbacks: [], + }, + blockExplorerUrls: { + default: 'https://etherscan.io', + fallbacks: [], + }, + config: { + isActive: true, + isTestnet: false, + isDefault: false, + isDeprecated: false, + isDeletable: true, + isFeatured: false, + priority: 0, + }, + ...override, + }; +} + type WithControllerCallback = ({ controller, }: { @@ -655,6 +734,7 @@ type WithControllerCallback = ({ type WithControllerOptions = Partial & { rpcFailoverMode?: RpcFailoverMode; + configRegistryNetworkConfig?: RegistryNetworkConfig; initializeController?: boolean; }; @@ -679,10 +759,12 @@ export async function withController( const { rpcFailoverMode, initializeController = true, + configRegistryNetworkConfig = buildMockConfigRegistryControllerNetwork(), ...controllerOptions } = rest; const messenger = buildRootMessenger({ rpcFailoverMode, + configRegistryNetworkConfig, }); const networkControllerMessenger = buildNetworkControllerMessenger(messenger); const controller = new NetworkController({ From 2483e597536ec0934ae900335d06f3ad8d174a30 Mon Sep 17 00:00:00 2001 From: mikesposito Date: Tue, 18 Aug 2026 16:09:46 +0200 Subject: [PATCH 07/23] fix formatting --- packages/network-controller/tests/helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/network-controller/tests/helpers.ts b/packages/network-controller/tests/helpers.ts index 27578a304d2..b619dd330ee 100644 --- a/packages/network-controller/tests/helpers.ts +++ b/packages/network-controller/tests/helpers.ts @@ -1,4 +1,5 @@ import { getDefaultAnalyticsControllerState } from '@metamask/analytics-controller'; +import { RegistryNetworkConfig } from '@metamask/config-registry-controller'; import { CONNECTIVITY_STATUSES } from '@metamask/connectivity-controller'; import type { ConnectivityStatus } from '@metamask/connectivity-controller'; import { @@ -49,7 +50,6 @@ import type { InfuraNetworkClientConfiguration, } from '../src/types.js'; import { NetworkClientType } from '../src/types.js'; -import { RegistryNetworkConfig } from '@metamask/config-registry-controller'; export type AllNetworkControllerActions = MessengerActions; From b3ef925833a8ad08d13215ee6ffad1f26ef949f8 Mon Sep 17 00:00:00 2001 From: mikesposito Date: Wed, 19 Aug 2026 09:58:36 +0200 Subject: [PATCH 08/23] remove unused import --- packages/network-controller/src/NetworkController.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index b02f025a7d6..43327656628 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -42,7 +42,6 @@ import { import type { SwappableProxy } from '@metamask/swappable-obj-proxy'; import type { CaipChainId, Hex } from '@metamask/utils'; import { - add0x, hasProperty, isPlainObject, isStrictHexString, From a5297e8b3b59ed9ae3c4a5025e755eadfc1972f7 Mon Sep 17 00:00:00 2001 From: mikesposito Date: Wed, 19 Aug 2026 10:01:15 +0200 Subject: [PATCH 09/23] update changelog --- packages/network-controller/CHANGELOG.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/network-controller/CHANGELOG.md b/packages/network-controller/CHANGELOG.md index 6df63f91ef7..993f793475a 100644 --- a/packages/network-controller/CHANGELOG.md +++ b/packages/network-controller/CHANGELOG.md @@ -7,12 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [35.0.1] - ### Changed - **BREAKING:** `NetworkControllerMessenger` now requires the `ConfigRegistryController:stateChanged` event and `ConfigRegistryController:getNetworkConfigByCaip2ChainId` action to be delegated from the root messenger ([#9879](https://github.com/MetaMask/core/pull/9879)) - `NetworkController` now depends on the `ConfigRegistryController` to auto-enable default networks from the registry. + +## [35.0.1] + +### Changed + - Bump `@metamask/eth-json-rpc-middleware` from `^23.1.3` to `^24.0.0` ([#9758](https://github.com/MetaMask/core/pull/9758)) ## [35.0.0] From 76c8e8b27e84688700d3015258c0ad84cc984d78 Mon Sep 17 00:00:00 2001 From: mikesposito Date: Thu, 20 Aug 2026 11:49:59 +0200 Subject: [PATCH 10/23] fix `{gas-fee, transaction}-controller` tests --- .../src/GasFeeController.test.ts | 11 ++++++++++ .../transaction-controller/jest.config.js | 8 +++---- .../TransactionControllerIntegration.test.ts | 21 +++++++++++++------ 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/packages/gas-fee-controller/src/GasFeeController.test.ts b/packages/gas-fee-controller/src/GasFeeController.test.ts index 17fb08fac35..00c67aa6265 100644 --- a/packages/gas-fee-controller/src/GasFeeController.test.ts +++ b/packages/gas-fee-controller/src/GasFeeController.test.ts @@ -79,6 +79,16 @@ const getRootMessenger = (): RootMessenger => { }), ); + rootMessenger.registerActionHandler( + 'ConfigRegistryController:getState', + () => ({ + configs: { networks: {} }, + lastFetched: 0, + etag: '', + version: '1', + }), + ); + return rootMessenger; }; @@ -105,6 +115,7 @@ const setupNetworkController = async ({ rootMessenger.delegate({ messenger: networkControllerMessenger, actions: [ + 'ConfigRegistryController:getState', 'ConnectivityController:getState', 'RemoteFeatureFlagController:getState', ], diff --git a/packages/transaction-controller/jest.config.js b/packages/transaction-controller/jest.config.js index 0a7110162b2..636e85e9785 100644 --- a/packages/transaction-controller/jest.config.js +++ b/packages/transaction-controller/jest.config.js @@ -17,10 +17,10 @@ module.exports = merge(baseConfig, { // An object that configures minimum threshold enforcement for coverage results coverageThreshold: { global: { - branches: 93.49, - functions: 93.77, - lines: 97.15, - statements: 97.12, + branches: 94.04, + functions: 93.9, + lines: 97.29, + statements: 97.26, }, }, diff --git a/packages/transaction-controller/src/TransactionControllerIntegration.test.ts b/packages/transaction-controller/src/TransactionControllerIntegration.test.ts index c815c116a6e..d1e4da1d39c 100644 --- a/packages/transaction-controller/src/TransactionControllerIntegration.test.ts +++ b/packages/transaction-controller/src/TransactionControllerIntegration.test.ts @@ -29,6 +29,7 @@ import type { NetworkClientConfiguration, NetworkControllerActions, NetworkControllerEvents, + NetworkControllerMessenger, NetworkControllerOptions, } from '@metamask/network-controller'; import type { RemoteFeatureFlagControllerGetStateAction } from '@metamask/remote-feature-flag-controller'; @@ -79,7 +80,7 @@ type AllTransactionControllerEvents = type AllActions = | AllTransactionControllerActions - | NetworkControllerActions + | MessengerActions | ConnectivityControllerGetStateAction | ApprovalControllerActions | AccountsControllerActions @@ -89,7 +90,7 @@ type AllActions = type AllEvents = | AllTransactionControllerEvents - | NetworkControllerEvents + | MessengerEvents | ApprovalControllerEvents; type RootMessenger = Messenger; @@ -210,10 +211,8 @@ const setupController = async ( const networkControllerMessenger = new Messenger< 'NetworkController', - | NetworkControllerActions - | ConnectivityControllerGetStateAction - | RemoteFeatureFlagControllerGetStateAction, - NetworkControllerEvents, + MessengerActions, + MessengerEvents, typeof rootMessenger >({ namespace: 'NetworkController', @@ -222,10 +221,20 @@ const setupController = async ( rootMessenger.delegate({ messenger: networkControllerMessenger, actions: [ + 'ConfigRegistryController:getState', 'ConnectivityController:getState', 'RemoteFeatureFlagController:getState', ], }); + rootMessenger.registerActionHandler( + 'ConfigRegistryController:getState', + () => ({ + configs: { networks: {} }, + version: '1', + etag: '', + lastFetched: 0, + }), + ); const networkController = new NetworkController({ messenger: networkControllerMessenger, infuraProjectId, From 8c3208c2023b1f12f29c5eb185d7b26381e1bb60 Mon Sep 17 00:00:00 2001 From: mikesposito Date: Sun, 23 Aug 2026 11:12:15 +0200 Subject: [PATCH 11/23] use config-registry-controller@^3.0.0 --- packages/network-controller/package.json | 2 +- yarn.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/network-controller/package.json b/packages/network-controller/package.json index 19eb309031b..9471ef06d7f 100644 --- a/packages/network-controller/package.json +++ b/packages/network-controller/package.json @@ -57,7 +57,7 @@ "dependencies": { "@metamask/analytics-controller": "^2.0.0", "@metamask/base-controller": "^9.1.0", - "@metamask/config-registry-controller": "^2.0.1", + "@metamask/config-registry-controller": "^3.0.0", "@metamask/connectivity-controller": "^0.3.0", "@metamask/controller-utils": "^12.3.0", "@metamask/eth-block-tracker": "^15.0.1", diff --git a/yarn.lock b/yarn.lock index 01303d048c6..3785747b949 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8159,7 +8159,7 @@ __metadata: "@metamask/analytics-controller": "npm:^2.0.0" "@metamask/auto-changelog": "npm:^6.1.0" "@metamask/base-controller": "npm:^9.1.0" - "@metamask/config-registry-controller": "npm:^2.0.1" + "@metamask/config-registry-controller": "npm:^3.0.0" "@metamask/connectivity-controller": "npm:^0.3.0" "@metamask/controller-utils": "npm:^12.3.0" "@metamask/eth-block-tracker": "npm:^15.0.1" From 1c1ecfbc17a0b3f37cac118e9f5f923c6e046a6d Mon Sep 17 00:00:00 2001 From: mikesposito Date: Sun, 23 Aug 2026 11:27:47 +0200 Subject: [PATCH 12/23] fix more tests --- .../src/AssetsContractController.test.ts | 11 +++++++++++ .../tests/NetworkController.test.ts | 2 -- .../network-controller/network-controller.ts | 3 +++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/assets-controllers/src/AssetsContractController.test.ts b/packages/assets-controllers/src/AssetsContractController.test.ts index 8033c412094..e218c1157ac 100644 --- a/packages/assets-controllers/src/AssetsContractController.test.ts +++ b/packages/assets-controllers/src/AssetsContractController.test.ts @@ -111,6 +111,15 @@ async function setupAssetContractControllers({ }), ); + messenger.registerActionHandler('ConfigRegistryController:getState', () => ({ + configs: { + networks: {}, + }, + lastFetched: 0, + etag: '', + version: '1', + })); + const networkControllerMessenger: NetworkControllerMessenger = new Messenger({ namespace: 'NetworkController', parent: messenger, @@ -119,6 +128,8 @@ async function setupAssetContractControllers({ messenger.delegate({ messenger: networkControllerMessenger, actions: [ + 'ConfigRegistryController:getNetworkConfigByCaip2ChainId', + 'ConfigRegistryController:getState', 'ConnectivityController:getState', 'RemoteFeatureFlagController:getState', ], diff --git a/packages/network-controller/tests/NetworkController.test.ts b/packages/network-controller/tests/NetworkController.test.ts index 68f33a2760a..b3d01757d58 100644 --- a/packages/network-controller/tests/NetworkController.test.ts +++ b/packages/network-controller/tests/NetworkController.test.ts @@ -1133,8 +1133,6 @@ describe('NetworkController', () => { defaultBlockExplorerUrlIndex: 0, rpcEndpoints: [ expect.objectContaining({ - networkClientId: - networkConfig.rpcProviders.default.networkClientId, url: networkConfig.rpcProviders.default.url, type: networkConfig.rpcProviders.default.type, }), diff --git a/packages/wallet/src/initialization/instances/network-controller/network-controller.ts b/packages/wallet/src/initialization/instances/network-controller/network-controller.ts index db61fabb905..5557dace48f 100644 --- a/packages/wallet/src/initialization/instances/network-controller/network-controller.ts +++ b/packages/wallet/src/initialization/instances/network-controller/network-controller.ts @@ -31,11 +31,14 @@ export const networkController: InitializationConfiguration< actions: [ 'AnalyticsController:getState', 'AnalyticsController:trackEvent', + 'ConfigRegistryController:getState', + 'ConfigRegistryController:getNetworkConfigByCaip2ChainId', 'ConnectivityController:getState', 'RemoteFeatureFlagController:getState', ], events: [ + 'ConfigRegistryController:stateChanged', // eslint-disable-next-line no-restricted-syntax 'RemoteFeatureFlagController:stateChange', ], From 2c4135ed6e3e3c79bf13569019bec061ee8eb800 Mon Sep 17 00:00:00 2001 From: mikesposito Date: Sun, 23 Aug 2026 11:28:17 +0200 Subject: [PATCH 13/23] fix lint --- .../src/TransactionControllerIntegration.test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/transaction-controller/src/TransactionControllerIntegration.test.ts b/packages/transaction-controller/src/TransactionControllerIntegration.test.ts index d1e4da1d39c..d4bb64265c2 100644 --- a/packages/transaction-controller/src/TransactionControllerIntegration.test.ts +++ b/packages/transaction-controller/src/TransactionControllerIntegration.test.ts @@ -27,8 +27,6 @@ import { } from '@metamask/network-controller'; import type { NetworkClientConfiguration, - NetworkControllerActions, - NetworkControllerEvents, NetworkControllerMessenger, NetworkControllerOptions, } from '@metamask/network-controller'; From 06346349c5ed2e3545c0b3a9342d750c5e8ac7b6 Mon Sep 17 00:00:00 2001 From: mikesposito Date: Sun, 23 Aug 2026 11:41:27 +0200 Subject: [PATCH 14/23] fix: use continue instead of return --- packages/network-controller/src/NetworkController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index 43327656628..afcc0900912 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -3186,7 +3186,7 @@ export class NetworkController extends BaseController< !networkConfiguration || this.state.networkConfigurationsByChainId[hexChainId] ) { - return; + continue; } const rpcEndpoint: InfuraRpcEndpoint | AddNetworkCustomRpcEndpointFields = From 89f9a1f6cc582c1059cdde77244bc171c726b03d Mon Sep 17 00:00:00 2001 From: mikesposito Date: Mon, 24 Aug 2026 14:48:59 +0200 Subject: [PATCH 15/23] fix: gracefully handle auto-enable errors --- packages/network-controller/jest.config.js | 2 +- .../src/NetworkController.ts | 83 +++++++++------- .../tests/NetworkController.test.ts | 94 ++++++++++++++++++- packages/network-controller/tests/helpers.ts | 28 +++--- 4 files changed, 156 insertions(+), 51 deletions(-) diff --git a/packages/network-controller/jest.config.js b/packages/network-controller/jest.config.js index bd532ddb09e..a9f85df8e93 100644 --- a/packages/network-controller/jest.config.js +++ b/packages/network-controller/jest.config.js @@ -17,7 +17,7 @@ module.exports = merge(baseConfig, { // An object that configures minimum threshold enforcement for coverage results coverageThreshold: { global: { - branches: 94.04, + branches: 93.85, functions: 98.17, lines: 97.94, statements: 97.83, diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index afcc0900912..c092488a4a6 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -3174,44 +3174,55 @@ export class NetworkController extends BaseController< */ #autoEnableChains(chainIds: CaipChainId[]): void { for (const chainId of chainIds) { - const networkConfiguration = this.messenger.call( - 'ConfigRegistryController:getNetworkConfigByCaip2ChainId', - chainId, - ); - const hexChainId = numberToHex( - Number(parseCaipChainId(chainId).reference), - ); + try { + const networkConfiguration = this.messenger.call( + 'ConfigRegistryController:getNetworkConfigByCaip2ChainId', + chainId, + ); + const hexChainId = numberToHex( + Number(parseCaipChainId(chainId).reference), + ); - if ( - !networkConfiguration || - this.state.networkConfigurationsByChainId[hexChainId] - ) { - continue; - } + if ( + !networkConfiguration || + this.state.networkConfigurationsByChainId[hexChainId] + ) { + continue; + } - const rpcEndpoint: InfuraRpcEndpoint | AddNetworkCustomRpcEndpointFields = - networkConfiguration.rpcProviders.default.type === 'infura' - ? { - type: RpcEndpointType.Infura, - networkClientId: - networkConfiguration.rpcProviders.default.networkClientId, - url: networkConfiguration.rpcProviders.default - .url as InfuraRpcEndpoint['url'], - } - : { - type: RpcEndpointType.Custom, - url: networkConfiguration.rpcProviders.default.url, - }; - - this.addNetwork({ - chainId: hexChainId, - name: networkConfiguration.name, - nativeCurrency: networkConfiguration.assets.native.symbol, - rpcEndpoints: [rpcEndpoint], - defaultRpcEndpointIndex: 0, - blockExplorerUrls: [networkConfiguration.blockExplorerUrls.default], - defaultBlockExplorerUrlIndex: 0, - }); + const rpcEndpoint: + | InfuraRpcEndpoint + | AddNetworkCustomRpcEndpointFields = + networkConfiguration.rpcProviders.default.type === 'infura' + ? { + type: RpcEndpointType.Infura, + networkClientId: + networkConfiguration.rpcProviders.default.networkClientId, + url: networkConfiguration.rpcProviders.default + .url as InfuraRpcEndpoint['url'], + } + : { + type: RpcEndpointType.Custom, + url: networkConfiguration.rpcProviders.default.url, + }; + + this.addNetwork({ + chainId: hexChainId, + name: networkConfiguration.name, + nativeCurrency: networkConfiguration.assets.native.symbol, + rpcEndpoints: [rpcEndpoint], + defaultRpcEndpointIndex: 0, + blockExplorerUrls: [networkConfiguration.blockExplorerUrls.default], + defaultBlockExplorerUrlIndex: 0, + }); + } catch (error) { + const capturedError = + error instanceof Error ? error : new Error(String(error)); + this.#log?.error( + `Failed to auto-enable network for chain ID ${chainId}: ${capturedError}`, + ); + this.messenger.captureException?.(capturedError); + } } } } diff --git a/packages/network-controller/tests/NetworkController.test.ts b/packages/network-controller/tests/NetworkController.test.ts index b3d01757d58..5ef7bc6b351 100644 --- a/packages/network-controller/tests/NetworkController.test.ts +++ b/packages/network-controller/tests/NetworkController.test.ts @@ -1009,7 +1009,7 @@ describe('NetworkController', () => { await withController( { initializeController: false, - configRegistryNetworkConfig: networkConfig, + configRegistryNetworkConfigs: [networkConfig], }, ({ controller }) => { expect( @@ -1054,7 +1054,7 @@ describe('NetworkController', () => { }, }); await withController( - { configRegistryNetworkConfig: networkConfig }, + { configRegistryNetworkConfigs: [networkConfig] }, async ({ controller, messenger }) => { messenger.publish( 'ConfigRegistryController:stateChanged', @@ -1108,7 +1108,7 @@ describe('NetworkController', () => { }, }); await withController( - { configRegistryNetworkConfig: networkConfig }, + { configRegistryNetworkConfigs: [networkConfig] }, async ({ controller, messenger }) => { messenger.publish( 'ConfigRegistryController:stateChanged', @@ -1143,6 +1143,94 @@ describe('NetworkController', () => { }, ); }); + + it('gracefully handles errors thrown when enabling networks from the config registry', async () => { + const networkConfigs = [ + buildMockConfigRegistryControllerNetwork({ + chainId: 'eip155:9997', + rpcProviders: { + default: buildCustomRpcEndpoint({ + url: 'https://test.network/2', + type: RpcEndpointType.Custom, + }), + fallbacks: [], + }, + config: { + ...buildMockConfigRegistryControllerNetwork().config, + isAutoEnabled: true, + }, + }), + buildMockConfigRegistryControllerNetwork({ + chainId: 'eip155:9998', + rpcProviders: { + default: buildCustomRpcEndpoint({ + url: 'https://test.network/2', + type: RpcEndpointType.Custom, + }), + fallbacks: [], + }, + config: { + ...buildMockConfigRegistryControllerNetwork().config, + isAutoEnabled: true, + }, + }), + buildMockConfigRegistryControllerNetwork({ + chainId: 'eip155:9999', + rpcProviders: { + default: buildCustomRpcEndpoint({ + url: 'https://test.network/1', + type: RpcEndpointType.Custom, + }), + fallbacks: [], + }, + config: { + ...buildMockConfigRegistryControllerNetwork().config, + isAutoEnabled: true, + }, + }), + ]; + await withController( + { configRegistryNetworkConfigs: networkConfigs }, + async ({ controller, messenger, networkControllerMessenger }) => { + messenger.publish( + 'ConfigRegistryController:stateChanged', + { + configs: { networks: { 'eip155:9999': networkConfigs[1] } }, + lastFetched: 0, + etag: 'etag', + version: '1', + }, + [], + ); + + expect( + networkControllerMessenger.captureException, + ).toHaveBeenCalled(); + expect( + controller.state.networkConfigurationsByChainId, + ).not.toHaveProperty('0x270e'); + expect( + controller.state.networkConfigurationsByChainId, + ).toHaveProperty( + '0x270f', + expect.objectContaining({ + chainId: '0x270f', + name: networkConfigs[2].name, + nativeCurrency: networkConfigs[2].assets.native.symbol, + blockExplorerUrls: [networkConfigs[2].blockExplorerUrls.default], + defaultBlockExplorerUrlIndex: 0, + rpcEndpoints: [ + expect.objectContaining({ + url: networkConfigs[2].rpcProviders.default.url, + type: networkConfigs[2].rpcProviders.default.type, + }), + ], + defaultRpcEndpointIndex: 0, + }), + ); + }, + ); + }); }); describe('destroy', () => { diff --git a/packages/network-controller/tests/helpers.ts b/packages/network-controller/tests/helpers.ts index b619dd330ee..5717a8f9d2e 100644 --- a/packages/network-controller/tests/helpers.ts +++ b/packages/network-controller/tests/helpers.ts @@ -16,7 +16,7 @@ import type { MessengerActions, MessengerEvents, } from '@metamask/messenger'; -import type { Hex } from '@metamask/utils'; +import type { CaipChainId, Hex } from '@metamask/utils'; import { v4 as uuidV4 } from 'uuid'; import { FakeBlockTracker } from '../../../tests/fake-block-tracker.js'; @@ -98,7 +98,7 @@ export const TESTNET = { * @param options.trackEvent - The handler registered for * `AnalyticsController:trackEvent`. Defaults to a Jest mock so tests can assert * on it. - * @param options.configRegistryNetworkConfig - The network config that + * @param options.configRegistryNetworkConfigs - The network config that * `ConfigRegistryController:getNetworkConfigByCaip2ChainId` returns by default. Defaults to * a mock network config for the chain ID `eip155:9999`. * @returns The messenger. @@ -108,13 +108,13 @@ export function buildRootMessenger({ rpcFailoverMode = 'disabled', analyticsId = '11111111-1111-4111-8111-111111111111', trackEvent = jest.fn(), - configRegistryNetworkConfig = buildMockConfigRegistryControllerNetwork(), + configRegistryNetworkConfigs = [buildMockConfigRegistryControllerNetwork()], }: { connectivityStatus?: ConnectivityStatus; rpcFailoverMode?: RpcFailoverMode; analyticsId?: string; trackEvent?: jest.Mock; - configRegistryNetworkConfig?: RegistryNetworkConfig; + configRegistryNetworkConfigs?: RegistryNetworkConfig[]; } = {}): RootMessenger { const rootMessenger = new Messenger< MockAnyNamespace, @@ -151,16 +151,22 @@ export function buildRootMessenger({ rootMessenger.registerActionHandler( 'ConfigRegistryController:getNetworkConfigByCaip2ChainId', - () => configRegistryNetworkConfig, + (caipChainId) => + configRegistryNetworkConfigs.find( + (config) => config.chainId === caipChainId, + ), ); rootMessenger.registerActionHandler( 'ConfigRegistryController:getState', () => ({ configs: { - networks: { - [configRegistryNetworkConfig.chainId]: configRegistryNetworkConfig, - }, + networks: configRegistryNetworkConfigs.reduce< + Record + >((acc, config) => { + acc[config.chainId] = config; + return acc; + }, {}), }, version: '0', lastFetched: 0, @@ -734,7 +740,7 @@ type WithControllerCallback = ({ type WithControllerOptions = Partial & { rpcFailoverMode?: RpcFailoverMode; - configRegistryNetworkConfig?: RegistryNetworkConfig; + configRegistryNetworkConfigs?: RegistryNetworkConfig[]; initializeController?: boolean; }; @@ -759,12 +765,12 @@ export async function withController( const { rpcFailoverMode, initializeController = true, - configRegistryNetworkConfig = buildMockConfigRegistryControllerNetwork(), + configRegistryNetworkConfigs = [buildMockConfigRegistryControllerNetwork()], ...controllerOptions } = rest; const messenger = buildRootMessenger({ rpcFailoverMode, - configRegistryNetworkConfig, + configRegistryNetworkConfigs, }); const networkControllerMessenger = buildNetworkControllerMessenger(messenger); const controller = new NetworkController({ From 3764c26b307ed94a9970139b20020ca50687a8c6 Mon Sep 17 00:00:00 2001 From: Michele Esposito <34438276+mikesposito@users.noreply.github.com> Date: Mon, 24 Aug 2026 17:56:52 +0200 Subject: [PATCH 16/23] Apply suggestions from code review Co-authored-by: Elliot Winkler --- .../src/NetworkController.ts | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index c092488a4a6..5a6e1b0efa8 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -1644,15 +1644,15 @@ export class NetworkController extends BaseController< * Initialize the NetworkController: * - Apply the RPC failover mode from the `corePlatformRpcFailoverMode` remote feature flag; * - Apply the network selection. - * - Auto-enable any chains that are configured to be auto-enabled in ConfigRegistryController. - */ - init(): void { - const state = this.messenger.call('RemoteFeatureFlagController:getState'); - this.#updateRpcFailover(getRpcFailoverMode(state)); - - this.#applyNetworkSelection(this.state.selectedNetworkClientId); - - this.#autoEnableChains( + * - Auto-add networks for any chains that are configured to be auto-enabled in ConfigRegistryController. + */ + init(): void { + const state = this.messenger.call('RemoteFeatureFlagController:getState'); + this.#updateRpcFailover(getRpcFailoverMode(state)); + + this.#applyNetworkSelection(this.state.selectedNetworkClientId); + + this.#autoAddNetworksFromConfigRegistry( getConfigRegistryEvmAutoEnabledChains( this.messenger.call('ConfigRegistryController:getState'), ), @@ -3164,15 +3164,14 @@ export class NetworkController extends BaseController< this.#ethQuery = new EthQuery(this.#providerProxy); } - - /** - * Enables networks for the given CAIP-2 chain IDs by adding - * them to state if they are not already present. Configurations for - * these networks are retrieved from ConfigRegistryController. - * - * @param chainIds - The CAIP-2 chain IDs of the networks to enable. - */ - #autoEnableChains(chainIds: CaipChainId[]): void { + /** + * Adds networks to state and registers network clients for the given CAIP-2 + * chain IDs if they are not already present. Configurations for these + * networks are retrieved from ConfigRegistryController. + * + * @param caipChainIds - The CAIP-2 chain IDs of the networks to enable. + */ + #autoAddNetworksFromConfigRegistry(caipChainIds: CaipChainId[]): void { for (const chainId of chainIds) { try { const networkConfiguration = this.messenger.call( From 1d8087a423c684f34f2b00796b56c80dd8993c1b Mon Sep 17 00:00:00 2001 From: mikesposito Date: Mon, 24 Aug 2026 18:01:11 +0200 Subject: [PATCH 17/23] rename variables --- .../src/NetworkController.ts | 61 ++++++++++--------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index 5a6e1b0efa8..cbbba2607a8 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -1644,15 +1644,15 @@ export class NetworkController extends BaseController< * Initialize the NetworkController: * - Apply the RPC failover mode from the `corePlatformRpcFailoverMode` remote feature flag; * - Apply the network selection. - * - Auto-add networks for any chains that are configured to be auto-enabled in ConfigRegistryController. - */ - init(): void { - const state = this.messenger.call('RemoteFeatureFlagController:getState'); - this.#updateRpcFailover(getRpcFailoverMode(state)); - - this.#applyNetworkSelection(this.state.selectedNetworkClientId); - - this.#autoAddNetworksFromConfigRegistry( + * - Auto-add networks for any chains that are configured to be auto-enabled in ConfigRegistryController. + */ + init(): void { + const state = this.messenger.call('RemoteFeatureFlagController:getState'); + this.#updateRpcFailover(getRpcFailoverMode(state)); + + this.#applyNetworkSelection(this.state.selectedNetworkClientId); + + this.#autoAddNetworksFromConfigRegistry( getConfigRegistryEvmAutoEnabledChains( this.messenger.call('ConfigRegistryController:getState'), ), @@ -3164,26 +3164,27 @@ export class NetworkController extends BaseController< this.#ethQuery = new EthQuery(this.#providerProxy); } - /** - * Adds networks to state and registers network clients for the given CAIP-2 - * chain IDs if they are not already present. Configurations for these - * networks are retrieved from ConfigRegistryController. - * - * @param caipChainIds - The CAIP-2 chain IDs of the networks to enable. - */ - #autoAddNetworksFromConfigRegistry(caipChainIds: CaipChainId[]): void { - for (const chainId of chainIds) { + + /** + * Adds networks to state and registers network clients for the given CAIP-2 + * chain IDs if they are not already present. Configurations for these + * networks are retrieved from ConfigRegistryController. + * + * @param caipChainIds - The CAIP-2 chain IDs of the networks to enable. + */ + #autoAddNetworksFromConfigRegistry(caipChainIds: CaipChainId[]): void { + for (const caipChainId of caipChainIds) { try { - const networkConfiguration = this.messenger.call( + const registryNetworkConfig = this.messenger.call( 'ConfigRegistryController:getNetworkConfigByCaip2ChainId', - chainId, + caipChainId, ); const hexChainId = numberToHex( - Number(parseCaipChainId(chainId).reference), + Number(parseCaipChainId(caipChainId).reference), ); if ( - !networkConfiguration || + !registryNetworkConfig || this.state.networkConfigurationsByChainId[hexChainId] ) { continue; @@ -3192,33 +3193,33 @@ export class NetworkController extends BaseController< const rpcEndpoint: | InfuraRpcEndpoint | AddNetworkCustomRpcEndpointFields = - networkConfiguration.rpcProviders.default.type === 'infura' + registryNetworkConfig.rpcProviders.default.type === 'infura' ? { type: RpcEndpointType.Infura, networkClientId: - networkConfiguration.rpcProviders.default.networkClientId, - url: networkConfiguration.rpcProviders.default + registryNetworkConfig.rpcProviders.default.networkClientId, + url: registryNetworkConfig.rpcProviders.default .url as InfuraRpcEndpoint['url'], } : { type: RpcEndpointType.Custom, - url: networkConfiguration.rpcProviders.default.url, + url: registryNetworkConfig.rpcProviders.default.url, }; this.addNetwork({ chainId: hexChainId, - name: networkConfiguration.name, - nativeCurrency: networkConfiguration.assets.native.symbol, + name: registryNetworkConfig.name, + nativeCurrency: registryNetworkConfig.assets.native.symbol, rpcEndpoints: [rpcEndpoint], defaultRpcEndpointIndex: 0, - blockExplorerUrls: [networkConfiguration.blockExplorerUrls.default], + blockExplorerUrls: [registryNetworkConfig.blockExplorerUrls.default], defaultBlockExplorerUrlIndex: 0, }); } catch (error) { const capturedError = error instanceof Error ? error : new Error(String(error)); this.#log?.error( - `Failed to auto-enable network for chain ID ${chainId}: ${capturedError}`, + `Failed to auto-enable network for chain ID ${caipChainId}: ${capturedError}`, ); this.messenger.captureException?.(capturedError); } From 5602a3b569ee883a9da9a79b09c8f8ceb8f93140 Mon Sep 17 00:00:00 2001 From: mikesposito Date: Mon, 24 Aug 2026 18:06:42 +0200 Subject: [PATCH 18/23] move auto-added networks selector to `config-registry-controller` --- .../config-registry-controller/CHANGELOG.md | 1 + .../config-registry-controller/src/index.ts | 6 ++++- .../src/selectors.ts | 22 ++++++++++++++++++ .../src/NetworkController.ts | 20 ++++++++-------- packages/network-controller/src/selectors.ts | 23 ------------------- 5 files changed, 37 insertions(+), 35 deletions(-) diff --git a/packages/config-registry-controller/CHANGELOG.md b/packages/config-registry-controller/CHANGELOG.md index e299f10b153..fb4df0e5c42 100644 --- a/packages/config-registry-controller/CHANGELOG.md +++ b/packages/config-registry-controller/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Add optional `isAutoEnabled?: boolean` property to `RegistryNetworkConfig.config` ([#9879](https://github.com/MetaMask/core/pull/9879)) +- Add `selectEvmAutoEnabledNetworksChainIds` selector to retrieve the CAIP-2 chain IDs of all EVM networks that are auto-enabled ([#9879](https://github.com/MetaMask/core/pull/9879)) ## [3.0.0] diff --git a/packages/config-registry-controller/src/index.ts b/packages/config-registry-controller/src/index.ts index f409b76530d..4e9cd1096fa 100644 --- a/packages/config-registry-controller/src/index.ts +++ b/packages/config-registry-controller/src/index.ts @@ -16,7 +16,11 @@ export { ConfigRegistryController, DEFAULT_POLLING_INTERVAL, } from './ConfigRegistryController.js'; -export { selectFeaturedNetworks, selectNetworks } from './selectors.js'; +export { + selectFeaturedNetworks, + selectNetworks, + selectEvmAutoEnabledNetworksChainIds, +} from './selectors.js'; export type { FetchConfigOptions, FetchConfigResult, diff --git a/packages/config-registry-controller/src/selectors.ts b/packages/config-registry-controller/src/selectors.ts index 5c8c608dd33..3988ef9b2a3 100644 --- a/packages/config-registry-controller/src/selectors.ts +++ b/packages/config-registry-controller/src/selectors.ts @@ -3,6 +3,7 @@ import { createSelector } from 'reselect'; import { filterNetworks } from './config-registry-api-service/filters.js'; import type { RegistryNetworkConfig } from './config-registry-api-service/types.js'; import type { ConfigRegistryControllerState } from './ConfigRegistryController.js'; +import { CaipChainId, KnownCaipNamespace } from '@metamask/utils'; /** * Base selector to get all networks from the controller state. @@ -37,3 +38,24 @@ export const selectFeaturedNetworks = createSelector( return result; }, ); + +/** + * Returns the list of CAIP-2 chain IDs for networks that are auto-enabled in the + * config registry. + * + * @param state - The config registry controller state. + * @returns The list of CAIP-2 chain IDs for auto-enabled networks. + */ +export function selectEvmAutoEnabledNetworksChainIds( + state: ConfigRegistryControllerState, +): CaipChainId[] { + return Object.values(state.configs.networks) + .filter( + ({ chainId, config }) => + chainId.startsWith(KnownCaipNamespace.Eip155) && + config.isAutoEnabled && + config.isActive && + !config.isDeprecated, + ) + .map((config) => config.chainId); +} diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index cbbba2607a8..7d21e169efe 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -7,10 +7,11 @@ import type { ControllerStateChangeEvent, } from '@metamask/base-controller'; import { BaseController } from '@metamask/base-controller'; -import type { - ConfigRegistryControllerStateChangedEvent, - ConfigRegistryControllerGetNetworkConfigByCaip2ChainIdAction, - ConfigRegistryControllerGetStateAction, +import { + type ConfigRegistryControllerStateChangedEvent, + type ConfigRegistryControllerGetNetworkConfigByCaip2ChainIdAction, + type ConfigRegistryControllerGetStateAction, + selectEvmAutoEnabledNetworksChainIds, } from '@metamask/config-registry-controller'; import type { ConnectivityControllerGetStateAction } from '@metamask/connectivity-controller'; import type { Partialize } from '@metamask/controller-utils'; @@ -82,10 +83,7 @@ import type { ResolvedNetworkControllerAnalyticsOptions, } from './rpc-service-analytics.js'; import type { RpcServiceOptionsWithDefaults } from './rpc-service/rpc-service.js'; -import { - getConfigRegistryEvmAutoEnabledChains, - getRpcFailoverMode, -} from './selectors.js'; +import { getRpcFailoverMode } from './selectors.js'; import type { RpcFailoverMode } from './selectors.js'; import { NetworkClientType } from './types.js'; import type { @@ -1439,8 +1437,8 @@ export class NetworkController extends BaseController< this.messenger.subscribe( 'ConfigRegistryController:stateChanged', - (autoEnabledChains) => this.#autoEnableChains(autoEnabledChains), - getConfigRegistryEvmAutoEnabledChains, + (caipChainIds) => this.#autoAddNetworksFromConfigRegistry(caipChainIds), + selectEvmAutoEnabledNetworksChainIds, ); } @@ -1653,7 +1651,7 @@ export class NetworkController extends BaseController< this.#applyNetworkSelection(this.state.selectedNetworkClientId); this.#autoAddNetworksFromConfigRegistry( - getConfigRegistryEvmAutoEnabledChains( + selectEvmAutoEnabledNetworksChainIds( this.messenger.call('ConfigRegistryController:getState'), ), ); diff --git a/packages/network-controller/src/selectors.ts b/packages/network-controller/src/selectors.ts index 799bcce3c28..60406079d8a 100644 --- a/packages/network-controller/src/selectors.ts +++ b/packages/network-controller/src/selectors.ts @@ -1,6 +1,4 @@ -import { ConfigRegistryControllerState } from '@metamask/config-registry-controller'; import { RemoteFeatureFlagControllerState } from '@metamask/remote-feature-flag-controller'; -import { CaipChainId, KnownCaipNamespace } from '@metamask/utils'; /** * The RPC failover behavior for Infura networks, controlled by the @@ -28,24 +26,3 @@ export function getRpcFailoverMode( const mode = state.remoteFeatureFlags.corePlatformRpcFailoverMode; return mode === 'enabled' || mode === 'forced' ? mode : 'disabled'; } - -/** - * Returns the list of CAIP-2 chain IDs for networks that are auto-enabled in the - * config registry. - * - * @param state - The config registry controller state. - * @returns The list of CAIP-2 chain IDs for auto-enabled networks. - */ -export function getConfigRegistryEvmAutoEnabledChains( - state: ConfigRegistryControllerState, -): CaipChainId[] { - return Object.values(state.configs.networks) - .filter( - ({ chainId, config }) => - chainId.startsWith(KnownCaipNamespace.Eip155) && - config.isAutoEnabled && - config.isActive && - !config.isDeprecated, - ) - .map((config) => config.chainId); -} From a7a534fee3b90f2dc059a63b614133bd133e10a6 Mon Sep 17 00:00:00 2001 From: mikesposito Date: Mon, 24 Aug 2026 18:09:06 +0200 Subject: [PATCH 19/23] run oxfmt --- packages/config-registry-controller/src/selectors.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/config-registry-controller/src/selectors.ts b/packages/config-registry-controller/src/selectors.ts index 3988ef9b2a3..3844aeab2d6 100644 --- a/packages/config-registry-controller/src/selectors.ts +++ b/packages/config-registry-controller/src/selectors.ts @@ -1,9 +1,9 @@ +import { CaipChainId, KnownCaipNamespace } from '@metamask/utils'; import { createSelector } from 'reselect'; import { filterNetworks } from './config-registry-api-service/filters.js'; import type { RegistryNetworkConfig } from './config-registry-api-service/types.js'; import type { ConfigRegistryControllerState } from './ConfigRegistryController.js'; -import { CaipChainId, KnownCaipNamespace } from '@metamask/utils'; /** * Base selector to get all networks from the controller state. From 3c0b3a29067f62396e504868f90c5caad5eca700 Mon Sep 17 00:00:00 2001 From: mikesposito Date: Mon, 24 Aug 2026 18:25:17 +0200 Subject: [PATCH 20/23] update tests --- .../src/selectors.test.ts | 72 ++++++++++++++++++- packages/network-controller/jest.config.js | 4 +- .../network-controller/src/selectors.test.ts | 63 +--------------- 3 files changed, 74 insertions(+), 65 deletions(-) diff --git a/packages/config-registry-controller/src/selectors.test.ts b/packages/config-registry-controller/src/selectors.test.ts index 1120c15eb55..e61c51004be 100644 --- a/packages/config-registry-controller/src/selectors.test.ts +++ b/packages/config-registry-controller/src/selectors.test.ts @@ -1,5 +1,10 @@ import { createMockNetworkConfig } from '../tests/helpers.js'; -import { selectFeaturedNetworks, selectNetworks } from './selectors.js'; +import { ConfigRegistryControllerState } from './ConfigRegistryController.js'; +import { + selectEvmAutoEnabledNetworksChainIds, + selectFeaturedNetworks, + selectNetworks, +} from './selectors.js'; describe('selectors', () => { describe('selectNetworks', () => { @@ -79,4 +84,69 @@ describe('selectors', () => { expect(Object.keys(featured)).toHaveLength(0); }); }); + + describe('selectEvmAutoEnabledNetworksChainIds', () => { + it('returns the list of CAIP-2 chain IDs for auto-enabled EVM networks', () => { + const state: ConfigRegistryControllerState = { + configs: { + networks: { + 'eip155:1': createMockNetworkConfig({ + chainId: 'eip155:1', + config: { + isAutoEnabled: true, + isActive: true, + isDeprecated: false, + }, + }), + 'eip155:3': createMockNetworkConfig({ + chainId: 'eip155:3', + config: { + isAutoEnabled: false, + isActive: true, + isDeprecated: false, + }, + }), + 'eip155:4': createMockNetworkConfig({ + chainId: 'eip155:4', + config: { + isAutoEnabled: true, + isActive: false, + isDeprecated: false, + }, + }), + 'eip155:5': createMockNetworkConfig({ + chainId: 'eip155:5', + config: { + isAutoEnabled: true, + isActive: true, + isDeprecated: true, + }, + }), + 'eip155:6': createMockNetworkConfig({ + chainId: 'eip155:6', + config: { + isAutoEnabled: true, + isActive: true, + isDeprecated: false, + }, + }), + 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp': createMockNetworkConfig({ + chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', + config: { + isAutoEnabled: true, + isActive: true, + isDeprecated: false, + }, + }), + }, + }, + version: '1.0.0', + lastFetched: Date.now(), + etag: null, + }; + + const result = selectEvmAutoEnabledNetworksChainIds(state); + expect(result).toStrictEqual(['eip155:1', 'eip155:6']); + }); + }); }); diff --git a/packages/network-controller/jest.config.js b/packages/network-controller/jest.config.js index a9f85df8e93..e20eff6e4a0 100644 --- a/packages/network-controller/jest.config.js +++ b/packages/network-controller/jest.config.js @@ -17,8 +17,8 @@ module.exports = merge(baseConfig, { // An object that configures minimum threshold enforcement for coverage results coverageThreshold: { global: { - branches: 93.85, - functions: 98.17, + branches: 93.8, + functions: 98.14, lines: 97.94, statements: 97.83, }, diff --git a/packages/network-controller/src/selectors.test.ts b/packages/network-controller/src/selectors.test.ts index b36806b8f6c..0fed844f57e 100644 --- a/packages/network-controller/src/selectors.test.ts +++ b/packages/network-controller/src/selectors.test.ts @@ -1,7 +1,4 @@ -import { - getConfigRegistryEvmAutoEnabledChains, - getRpcFailoverMode, -} from './selectors.js'; +import { getRpcFailoverMode } from './selectors.js'; /** * Builds a remote feature flag controller state with the given failover mode. @@ -43,61 +40,3 @@ describe('getRpcFailoverMode', () => { expect(getRpcFailoverMode(buildState('yes') as never)).toBe('disabled'); }); }); - -describe('getConfigRegistryEvmAutoEnabledChains', () => { - it('returns the list of CAIP-2 chain IDs for auto-enabled EVM networks', () => { - const state = { - configs: { - networks: { - 'eip155:1': { - chainId: 'eip155:1', - config: { - isAutoEnabled: true, - isActive: true, - isDeprecated: false, - }, - }, - 'eip155:3': { - chainId: 'eip155:3', - config: { - isAutoEnabled: false, - isActive: true, - isDeprecated: false, - }, - }, - 'eip155:4': { - chainId: 'eip155:4', - config: { - isAutoEnabled: true, - isActive: false, - isDeprecated: false, - }, - }, - 'eip155:5': { - chainId: 'eip155:5', - config: { isAutoEnabled: true, isActive: true, isDeprecated: true }, - }, - 'eip155:6': { - chainId: 'eip155:6', - config: { - isAutoEnabled: true, - isActive: true, - isDeprecated: false, - }, - }, - 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp': { - chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', - config: { - isAutoEnabled: true, - isActive: true, - isDeprecated: false, - }, - }, - }, - }, - }; - - const result = getConfigRegistryEvmAutoEnabledChains(state); - expect(result).toStrictEqual(['eip155:1', 'eip155:6']); - }); -}); From 55ec7295db26da3cede659a5fd48c8e14ed8deae Mon Sep 17 00:00:00 2001 From: mikesposito Date: Mon, 24 Aug 2026 18:30:11 +0200 Subject: [PATCH 21/23] fix selector memoization --- .../src/selectors.test.ts | 30 ++++++++++++++++ .../src/selectors.ts | 34 ++++++++++++------- 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/packages/config-registry-controller/src/selectors.test.ts b/packages/config-registry-controller/src/selectors.test.ts index e61c51004be..536d6412411 100644 --- a/packages/config-registry-controller/src/selectors.test.ts +++ b/packages/config-registry-controller/src/selectors.test.ts @@ -148,5 +148,35 @@ describe('selectors', () => { const result = selectEvmAutoEnabledNetworksChainIds(state); expect(result).toStrictEqual(['eip155:1', 'eip155:6']); }); + + it('returns the same array reference when the chain IDs have not changed', () => { + const networks = { + 'eip155:1': createMockNetworkConfig({ + chainId: 'eip155:1', + config: { isAutoEnabled: true, isActive: true, isDeprecated: false }, + }), + }; + const state: ConfigRegistryControllerState = { + configs: { networks }, + version: '1.0.0', + lastFetched: 1, + etag: null, + }; + + const first = selectEvmAutoEnabledNetworksChainIds(state); + // Unrelated state change, same `networks` object. + const second = selectEvmAutoEnabledNetworksChainIds({ + ...state, + lastFetched: 2, + }); + // New `networks` object with the same auto-enabled chain IDs. + const third = selectEvmAutoEnabledNetworksChainIds({ + ...state, + configs: { networks: { ...networks } }, + }); + + expect(second).toBe(first); + expect(third).toBe(first); + }); }); }); diff --git a/packages/config-registry-controller/src/selectors.ts b/packages/config-registry-controller/src/selectors.ts index 3844aeab2d6..030fc10b875 100644 --- a/packages/config-registry-controller/src/selectors.ts +++ b/packages/config-registry-controller/src/selectors.ts @@ -46,16 +46,24 @@ export const selectFeaturedNetworks = createSelector( * @param state - The config registry controller state. * @returns The list of CAIP-2 chain IDs for auto-enabled networks. */ -export function selectEvmAutoEnabledNetworksChainIds( - state: ConfigRegistryControllerState, -): CaipChainId[] { - return Object.values(state.configs.networks) - .filter( - ({ chainId, config }) => - chainId.startsWith(KnownCaipNamespace.Eip155) && - config.isAutoEnabled && - config.isActive && - !config.isDeprecated, - ) - .map((config) => config.chainId); -} +export const selectEvmAutoEnabledNetworksChainIds = createSelector( + selectNetworks, + (networks): CaipChainId[] => + Object.values(networks) + .filter( + ({ chainId, config }) => + chainId.startsWith(KnownCaipNamespace.Eip155) && + config.isAutoEnabled && + config.isActive && + !config.isDeprecated, + ) + .map((config) => config.chainId), + { + // Messenger selector subscriptions only skip work when the result is + // referentially equal, so keep the previous array when the IDs are the same. + memoizeOptions: { + resultEqualityCheck: (a: CaipChainId[], b: CaipChainId[]) => + a.length === b.length && a.every((chainId, i) => chainId === b[i]), + }, + }, +); From 68feb4971f516134922d2c07af9cb970afdebf3e Mon Sep 17 00:00:00 2001 From: mikesposito Date: Mon, 24 Aug 2026 19:48:28 +0200 Subject: [PATCH 22/23] fix eslint --- packages/network-controller/src/NetworkController.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index 7d21e169efe..5f0cbc17be1 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -7,12 +7,12 @@ import type { ControllerStateChangeEvent, } from '@metamask/base-controller'; import { BaseController } from '@metamask/base-controller'; -import { - type ConfigRegistryControllerStateChangedEvent, - type ConfigRegistryControllerGetNetworkConfigByCaip2ChainIdAction, - type ConfigRegistryControllerGetStateAction, - selectEvmAutoEnabledNetworksChainIds, +import type { + ConfigRegistryControllerStateChangedEvent, + ConfigRegistryControllerGetNetworkConfigByCaip2ChainIdAction, + ConfigRegistryControllerGetStateAction, } from '@metamask/config-registry-controller'; +import { selectEvmAutoEnabledNetworksChainIds } from '@metamask/config-registry-controller'; import type { ConnectivityControllerGetStateAction } from '@metamask/connectivity-controller'; import type { Partialize } from '@metamask/controller-utils'; import { From b0efd2320f7b661eac4a0141e2100766d3556962 Mon Sep 17 00:00:00 2001 From: Michele Esposito <34438276+mikesposito@users.noreply.github.com> Date: Mon, 24 Aug 2026 19:48:58 +0200 Subject: [PATCH 23/23] Update packages/network-controller/CHANGELOG.md Co-authored-by: Elliot Winkler --- packages/network-controller/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/network-controller/CHANGELOG.md b/packages/network-controller/CHANGELOG.md index 993f793475a..5824737c576 100644 --- a/packages/network-controller/CHANGELOG.md +++ b/packages/network-controller/CHANGELOG.md @@ -10,7 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - **BREAKING:** `NetworkControllerMessenger` now requires the `ConfigRegistryController:stateChanged` event and `ConfigRegistryController:getNetworkConfigByCaip2ChainId` action to be delegated from the root messenger ([#9879](https://github.com/MetaMask/core/pull/9879)) - - `NetworkController` now depends on the `ConfigRegistryController` to auto-enable default networks from the registry. + - `NetworkController` now depends on the `ConfigRegistryController` to auto-register default networks from the registry. +- Update `init` to auto-register default networks from `ConfigRegistryController` ([#9879](https://github.com/MetaMask/core/pull/9879)) ## [35.0.1]