From b6e691e4d87783ecbfec498b3080ed4a2e305536 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 31 Aug 2026 17:34:56 +0000 Subject: [PATCH 1/7] feat(network-controller): infer RPC endpoint types Co-authored-by: elliot.winkler --- packages/network-controller/CHANGELOG.md | 3 + .../src/NetworkController.ts | 277 ++++++++++++++++-- .../tests/NetworkController.test.ts | 106 +++++++ 3 files changed, 359 insertions(+), 27 deletions(-) diff --git a/packages/network-controller/CHANGELOG.md b/packages/network-controller/CHANGELOG.md index ef1bedd9bd5..3d0603f8541 100644 --- a/packages/network-controller/CHANGELOG.md +++ b/packages/network-controller/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Infer RPC endpoint types from their URLs when initializing, adding, or updating network configurations. - Bump `@metamask/remote-feature-flag-controller` from `^6.0.0` to `^6.1.0` ([#9980](https://github.com/MetaMask/core/pull/9980)) ## [36.0.0] @@ -1281,7 +1282,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Initial release + - As a result of converting our shared controllers repo into a monorepo ([#831](https://github.com/MetaMask/core/pull/831)), we've created this package from select parts of [`@metamask/controllers` v33.0.0](https://github.com/MetaMask/core/tree/v33.0.0), namely: + - Everything in `src/network` (minus `NetworkType` and `NetworksChainId`, which were placed in `@metamask/controller-utils`) All changes listed after this point were applied to this package following the monorepo conversion. diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index 5f0cbc17be1..e7ff2d5c700 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -97,7 +97,7 @@ import type { const debugLog = createModuleLogger(projectLogger, 'NetworkController'); const INFURA_URL_REGEX = - /^https:\/\/(?[^.]+)\.infura\.io\/v\d+\/(?.+)$/u; + /^https:\/\/(?[^./]+)\.infura\.io\/v3\/(?[^/]+)$/u; export type Block = { baseFeePerGas?: string; @@ -168,7 +168,7 @@ export type InfuraRpcEndpoint = { * `{infuraProjectId}`, which will get replaced with the Infura project ID * when the network client is created. */ - url: `https://${InfuraNetworkType}.infura.io/v3/{infuraProjectId}`; + url: string; }; /** @@ -277,8 +277,20 @@ export type NetworkConfiguration = { */ export type AddNetworkCustomRpcEndpointFields = Omit< CustomRpcEndpoint, - 'networkClientId' ->; + 'networkClientId' | 'type' +> & { + /** + * The type of the endpoint. If omitted, it is inferred from the URL. + */ + type?: RpcEndpointType; +}; + +type AddNetworkInfuraRpcEndpointFields = Omit & { + /** + * The type of the endpoint. If omitted, it is inferred from the URL. + */ + type?: RpcEndpointType; +}; /** * A new network configuration that `addNetwork` takes. @@ -288,7 +300,10 @@ export type AddNetworkCustomRpcEndpointFields = Omit< * network clients yet. */ export type AddNetworkFields = Omit & { - rpcEndpoints: (InfuraRpcEndpoint | AddNetworkCustomRpcEndpointFields)[]; + rpcEndpoints: ( + | AddNetworkInfuraRpcEndpointFields + | AddNetworkCustomRpcEndpointFields + )[]; }; /** @@ -299,10 +314,22 @@ export type AddNetworkFields = Omit & { * assumed that they have not already been added and therefore network clients * do not exist for them yet (and hence IDs need to be generated). */ -export type UpdateNetworkCustomRpcEndpointFields = Partialize< - CustomRpcEndpoint, - 'networkClientId' ->; +export type UpdateNetworkCustomRpcEndpointFields = Omit< + Partialize, + 'type' +> & { + /** + * The type of the endpoint. If omitted, it is inferred from the URL. + */ + type?: RpcEndpointType; +}; + +type UpdateNetworkInfuraRpcEndpointFields = Omit & { + /** + * The type of the endpoint. If omitted, it is inferred from the URL. + */ + type?: RpcEndpointType; +}; /** * An updated representation of an existing network configuration that @@ -313,9 +340,45 @@ export type UpdateNetworkCustomRpcEndpointFields = Partialize< * assumed that they are new and are not represented by network clients yet. */ export type UpdateNetworkFields = Omit & { - rpcEndpoints: (InfuraRpcEndpoint | UpdateNetworkCustomRpcEndpointFields)[]; + rpcEndpoints: ( + | UpdateNetworkInfuraRpcEndpointFields + | UpdateNetworkCustomRpcEndpointFields + )[]; +}; + +type RpcEndpointWithOptionalType = + | (Omit & { + type?: RpcEndpointType; + }) + | (Omit & { + type?: RpcEndpointType; + }); + +type NetworkConfigurationWithOptionalRpcEndpointType = Omit< + NetworkConfiguration, + 'rpcEndpoints' +> & { + rpcEndpoints: RpcEndpointWithOptionalType[]; +}; + +type NetworkStateWithOptionalRpcEndpointType = Omit< + NetworkState, + 'networkConfigurationsByChainId' +> & { + networkConfigurationsByChainId: Record< + Hex, + NetworkConfigurationWithOptionalRpcEndpointType + >; }; +function hasRpcEndpointTypes( + networkConfiguration: NetworkConfigurationWithOptionalRpcEndpointType, +): networkConfiguration is NetworkConfiguration { + return networkConfiguration.rpcEndpoints.every( + (rpcEndpoint) => rpcEndpoint.type !== undefined, + ); +} + /** * `Object.keys()` is intentionally generic: it returns the keys of an object, * but it cannot make guarantees about the contents of that object, so the type @@ -772,7 +835,7 @@ export type NetworkControllerOptions = { * specified, `networkConfigurationsByChainId` will default to a basic set of * network configurations (see {@link InfuraNetworkType} for the list). */ - state?: Partial; + state?: Partial; /** * A `loglevel` logger object. */ @@ -1131,6 +1194,155 @@ function deriveInfuraNetworkNameFromRpcEndpointUrl( throw new Error('Could not derive Infura network from RPC endpoint URL'); } +type RpcEndpointFields = { + failoverUrls?: string[]; + name?: string; + networkClientId?: NetworkClientId; + type?: RpcEndpointType; + url: string; +}; + +type InferredRpcEndpoint = + | (Omit & { + type: RpcEndpointType.Infura; + }) + | (Omit & { + networkClientId?: CustomNetworkClientId; + type: RpcEndpointType.Custom; + }); + +/** + * Checks whether an RPC URL is a MetaMask Infura endpoint. The URL may contain + * either the placeholder persisted in built-in network configurations or the + * controller's Infura project ID. + * + * @param url - The RPC URL to check. + * @param infuraProjectId - The controller's Infura project ID. + * @returns Whether the URL is a MetaMask Infura endpoint. + */ +function isInfuraEndpointUrl(url: string, infuraProjectId: string): boolean { + const projectId = INFURA_URL_REGEX.exec(url)?.groups?.projectId; + return projectId === '{infuraProjectId}' || projectId === infuraProjectId; +} + +/** + * Infers the type of an RPC endpoint from its URL. + * + * @param rpcEndpointFields - The RPC endpoint fields. + * @param infuraProjectId - The controller's Infura project ID. + * @returns The RPC endpoint fields with an inferred type. + */ +function inferRpcEndpointType( + rpcEndpointFields: RpcEndpointFields, + infuraProjectId: string, +): InferredRpcEndpoint { + if (isInfuraEndpointUrl(rpcEndpointFields.url, infuraProjectId)) { + if ( + rpcEndpointFields.type === RpcEndpointType.Infura && + rpcEndpointFields.networkClientId !== undefined + ) { + return rpcEndpointFields; + } + + return { + ...rpcEndpointFields, + networkClientId: + rpcEndpointFields.networkClientId ?? + deriveInfuraNetworkNameFromRpcEndpointUrl(rpcEndpointFields.url), + type: RpcEndpointType.Infura, + }; + } + + if (rpcEndpointFields.type === RpcEndpointType.Custom) { + return rpcEndpointFields; + } + + return { + ...rpcEndpointFields, + type: RpcEndpointType.Custom, + }; +} + +/** + * Normalizes the endpoint types in a network configuration. + * + * @param networkConfiguration - The network configuration to normalize. + * @param infuraProjectId - The controller's Infura project ID. + * @returns The normalized network configuration. + */ +function normalizeNetworkConfiguration( + networkConfiguration: NetworkConfigurationWithOptionalRpcEndpointType, + infuraProjectId: string, +): NetworkConfiguration { + let hasChanges = false; + const rpcEndpoints = networkConfiguration.rpcEndpoints.map((rpcEndpoint) => { + const inferredRpcEndpoint = inferRpcEndpointType( + rpcEndpoint, + infuraProjectId, + ); + + if (inferredRpcEndpoint.networkClientId === undefined) { + throw new Error( + `Network configuration '${networkConfiguration.name}' has an RPC endpoint without a network client ID`, + ); + } + + hasChanges ||= inferredRpcEndpoint !== rpcEndpoint; + return inferredRpcEndpoint; + }); + + if (!hasChanges && hasRpcEndpointTypes(networkConfiguration)) { + return networkConfiguration; + } + + return { + ...networkConfiguration, + rpcEndpoints, + }; +} + +/** + * Constructs the initial NetworkController state and infers RPC endpoint + * types in any provided network configurations. + * + * @param state - The desired initial state. + * @param infuraProjectId - The controller's Infura project ID. + * @returns The complete normalized initial state. + */ +function getInitialState( + state: NetworkControllerOptions['state'], + infuraProjectId: string, +): NetworkState { + const defaultState = getDefaultNetworkControllerState(); + const networkConfigurationsByChainId = + state?.networkConfigurationsByChainId ?? + defaultState.networkConfigurationsByChainId; + + let hasChanges = false; + const normalizedNetworkConfigurationsByChainId = Object.entries( + networkConfigurationsByChainId, + ).reduce>( + (normalizedConfigurations, [chainId, networkConfiguration]) => { + const normalizedNetworkConfiguration = normalizeNetworkConfiguration( + networkConfiguration, + infuraProjectId, + ); + hasChanges ||= normalizedNetworkConfiguration !== networkConfiguration; + normalizedConfigurations[chainId as Hex] = normalizedNetworkConfiguration; + return normalizedConfigurations; + }, + {}, + ); + + return { + ...defaultState, + ...state, + networkConfigurationsByChainId: hasChanges + ? normalizedNetworkConfigurationsByChainId + : networkConfigurationsByChainId, + }; +} + /** * Performs a series of checks that the given NetworkController state is * internally consistent — that all parts of state that are supposed to match in @@ -1330,17 +1542,15 @@ export class NetworkController extends BaseController< getBlockTrackerOptions, analyticsOptions, } = options; - const initialState = { - ...getDefaultNetworkControllerState(), - ...state, - }; + const initialState = getInitialState(state, infuraProjectId); validateInitialState(initialState); - const correctedInitialState = correctInitialState(initialState, messenger); if (!infuraProjectId || typeof infuraProjectId !== 'string') { throw new Error('Invalid Infura project ID'); } + const correctedInitialState = correctInitialState(initialState, messenger); + super({ name: controllerName, metadata: { @@ -2119,14 +2329,21 @@ export class NetworkController extends BaseController< * @see {@link NetworkConfiguration} */ addNetwork(fields: AddNetworkFields): NetworkConfiguration { - const { rpcEndpoints: setOfRpcEndpointFields } = fields; + const fieldsWithInferredRpcEndpointTypes = { + ...fields, + rpcEndpoints: fields.rpcEndpoints.map((rpcEndpointFields) => + inferRpcEndpointType(rpcEndpointFields, this.#infuraProjectId), + ), + }; + const { rpcEndpoints: setOfRpcEndpointFields } = + fieldsWithInferredRpcEndpointTypes; const autoManagedNetworkClientRegistry = this.#ensureAutoManagedNetworkClientRegistryPopulated(); this.#validateNetworkFields({ mode: 'add', - networkFields: fields, + networkFields: fieldsWithInferredRpcEndpointTypes, autoManagedNetworkClientRegistry, }); @@ -2148,11 +2365,11 @@ export class NetworkController extends BaseController< const newNetworkConfiguration = this.#determineNetworkConfigurationToPersist({ - networkFields: fields, + networkFields: fieldsWithInferredRpcEndpointTypes, networkClientOperations, }); this.#registerNetworkClientsAsNeeded({ - networkFields: fields, + networkFields: fieldsWithInferredRpcEndpointTypes, networkClientOperations, autoManagedNetworkClientRegistry, }); @@ -2160,7 +2377,7 @@ export class NetworkController extends BaseController< this.#updateNetworkConfigurations({ state, mode: 'add', - networkFields: fields, + networkFields: fieldsWithInferredRpcEndpointTypes, networkConfigurationToPersist: newNetworkConfiguration, }); }); @@ -2214,15 +2431,21 @@ export class NetworkController extends BaseController< } const existingChainId = chainId; + const fieldsWithInferredRpcEndpointTypes = { + ...fields, + rpcEndpoints: fields.rpcEndpoints.map((rpcEndpointFields) => + inferRpcEndpointType(rpcEndpointFields, this.#infuraProjectId), + ), + }; const { chainId: newChainId, rpcEndpoints: setOfNewRpcEndpointFields } = - fields; + fieldsWithInferredRpcEndpointTypes; const autoManagedNetworkClientRegistry = this.#ensureAutoManagedNetworkClientRegistryPopulated(); this.#validateNetworkFields({ mode: 'update', - networkFields: fields, + networkFields: fieldsWithInferredRpcEndpointTypes, existingNetworkConfiguration, autoManagedNetworkClientRegistry, }); @@ -2352,7 +2575,7 @@ export class NetworkController extends BaseController< const updatedNetworkConfiguration = this.#determineNetworkConfigurationToPersist({ - networkFields: fields, + networkFields: fieldsWithInferredRpcEndpointTypes, networkClientOperations, }); @@ -2379,7 +2602,7 @@ export class NetworkController extends BaseController< } this.#registerNetworkClientsAsNeeded({ - networkFields: fields, + networkFields: fieldsWithInferredRpcEndpointTypes, networkClientOperations, autoManagedNetworkClientRegistry, }); @@ -2423,7 +2646,7 @@ export class NetworkController extends BaseController< this.#updateNetworkConfigurations({ state, mode: 'update', - networkFields: fields, + networkFields: fieldsWithInferredRpcEndpointTypes, networkConfigurationToPersist: updatedNetworkConfiguration, existingNetworkConfiguration, }); @@ -2434,7 +2657,7 @@ export class NetworkController extends BaseController< this.#updateNetworkConfigurations({ state, mode: 'update', - networkFields: fields, + networkFields: fieldsWithInferredRpcEndpointTypes, networkConfigurationToPersist: updatedNetworkConfiguration, existingNetworkConfiguration, }); diff --git a/packages/network-controller/tests/NetworkController.test.ts b/packages/network-controller/tests/NetworkController.test.ts index 5ef7bc6b351..88de4dd52ee 100644 --- a/packages/network-controller/tests/NetworkController.test.ts +++ b/packages/network-controller/tests/NetworkController.test.ts @@ -421,6 +421,57 @@ describe('NetworkController', () => { ); }); + it('corrects RPC endpoint types based on their URLs', async () => { + await withController( + { + state: { + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + rpcEndpoints: [ + buildCustomRpcEndpoint({ + url: 'https://mainnet.infura.io/v3/{infuraProjectId}', + }), + ], + }), + }, + }, + }, + ({ controller }) => { + expect( + controller.state.networkConfigurationsByChainId['0x1337'] + .rpcEndpoints[0].type, + ).toBe(RpcEndpointType.Infura); + }, + ); + }); + + it('infers the type of RPC endpoints missing a type in the initial state', async () => { + const { type: _, ...rpcEndpoint } = buildCustomRpcEndpoint({ + url: 'https://custom.endpoint', + }); + + await withController( + { + state: { + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + rpcEndpoints: [rpcEndpoint], + }), + }, + }, + }, + ({ controller }) => { + expect( + controller.state.networkConfigurationsByChainId['0x1337'] + .rpcEndpoints[0], + ).toMatchObject({ + type: RpcEndpointType.Custom, + url: 'https://custom.endpoint', + }); + }, + ); + }); + it('removes invalid network client IDs from networksMetadata, logging this fact', () => { const messenger = buildRootMessenger(); const captureExceptionSpy = jest.spyOn(messenger, 'captureException'); @@ -4248,6 +4299,26 @@ describe('NetworkController', () => { }); }); + it('infers the type of an RPC endpoint when adding a network', async () => { + const { type: _, ...rpcEndpoint } = + buildAddNetworkCustomRpcEndpointFields({ + url: 'https://custom.endpoint', + }); + + await withController(({ controller }) => { + const result = controller.addNetwork( + buildAddNetworkFields({ + rpcEndpoints: [rpcEndpoint], + }), + ); + + expect(result.rpcEndpoints[0]).toMatchObject({ + type: RpcEndpointType.Custom, + url: 'https://custom.endpoint', + }); + }); + }); + it('throws if the rpcEndpoints field is an empty array', async () => { await withController(({ controller }) => { expect(() => @@ -5453,6 +5524,41 @@ describe('NetworkController', () => { ); }); + it('infers the type of an RPC endpoint when updating a network', async () => { + const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ + chainId: '0x1337', + }); + const { type: _, ...rpcEndpoint } = + buildUpdateNetworkCustomRpcEndpointFields({ + url: 'https://custom.endpoint', + }); + + await withController( + { + state: { + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }, + }, + async ({ controller }) => { + const result = await controller.updateNetwork( + '0x1337', + { + ...networkConfigurationToUpdate, + rpcEndpoints: [rpcEndpoint], + }, + { replacementSelectedRpcEndpointIndex: 0 }, + ); + + expect(result.rpcEndpoints[0]).toMatchObject({ + type: RpcEndpointType.Custom, + url: 'https://custom.endpoint', + }); + }, + ); + }); + it('throws if one of the new rpcEndpoints has an invalid url property', async () => { const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ chainId: '0x1337', From 10fb98b66cb6dae16fc7c104a8b7dd6805de912e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 31 Aug 2026 17:35:43 +0000 Subject: [PATCH 2/7] fix(network-controller): preserve normalized endpoint identity Co-authored-by: elliot.winkler --- .../src/NetworkController.ts | 42 ++++++++++++++----- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index e7ff2d5c700..9504a45bbb7 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -1211,6 +1211,33 @@ type InferredRpcEndpoint = type: RpcEndpointType.Custom; }); +function isInfuraRpcEndpoint( + rpcEndpointFields: RpcEndpointFields, +): rpcEndpointFields is Extract< + InferredRpcEndpoint, + { type: RpcEndpointType.Infura } +> { + return ( + rpcEndpointFields.type === RpcEndpointType.Infura && + rpcEndpointFields.networkClientId !== undefined + ); +} + +function isCustomRpcEndpoint( + rpcEndpointFields: RpcEndpointFields, +): rpcEndpointFields is Extract< + InferredRpcEndpoint, + { type: RpcEndpointType.Custom } +> { + return rpcEndpointFields.type === RpcEndpointType.Custom; +} + +function hasNetworkClientId( + rpcEndpoint: InferredRpcEndpoint, +): rpcEndpoint is RpcEndpoint { + return rpcEndpoint.networkClientId !== undefined; +} + /** * Checks whether an RPC URL is a MetaMask Infura endpoint. The URL may contain * either the placeholder persisted in built-in network configurations or the @@ -1237,10 +1264,7 @@ function inferRpcEndpointType( infuraProjectId: string, ): InferredRpcEndpoint { if (isInfuraEndpointUrl(rpcEndpointFields.url, infuraProjectId)) { - if ( - rpcEndpointFields.type === RpcEndpointType.Infura && - rpcEndpointFields.networkClientId !== undefined - ) { + if (isInfuraRpcEndpoint(rpcEndpointFields)) { return rpcEndpointFields; } @@ -1253,7 +1277,7 @@ function inferRpcEndpointType( }; } - if (rpcEndpointFields.type === RpcEndpointType.Custom) { + if (isCustomRpcEndpoint(rpcEndpointFields)) { return rpcEndpointFields; } @@ -1281,7 +1305,7 @@ function normalizeNetworkConfiguration( infuraProjectId, ); - if (inferredRpcEndpoint.networkClientId === undefined) { + if (!hasNetworkClientId(inferredRpcEndpoint)) { throw new Error( `Network configuration '${networkConfiguration.name}' has an RPC endpoint without a network client ID`, ); @@ -1318,7 +1342,6 @@ function getInitialState( state?.networkConfigurationsByChainId ?? defaultState.networkConfigurationsByChainId; - let hasChanges = false; const normalizedNetworkConfigurationsByChainId = Object.entries( networkConfigurationsByChainId, ).reduce>( @@ -1327,7 +1350,6 @@ function getInitialState( networkConfiguration, infuraProjectId, ); - hasChanges ||= normalizedNetworkConfiguration !== networkConfiguration; normalizedConfigurations[chainId as Hex] = normalizedNetworkConfiguration; return normalizedConfigurations; }, @@ -1337,9 +1359,7 @@ function getInitialState( return { ...defaultState, ...state, - networkConfigurationsByChainId: hasChanges - ? normalizedNetworkConfigurationsByChainId - : networkConfigurationsByChainId, + networkConfigurationsByChainId: normalizedNetworkConfigurationsByChainId, }; } From 47dc591140f333ab26798de17489bd99f9d50614 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 31 Aug 2026 17:38:32 +0000 Subject: [PATCH 3/7] test(network-controller): cover Infura URL inference Co-authored-by: elliot.winkler --- .../tests/NetworkController.test.ts | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/packages/network-controller/tests/NetworkController.test.ts b/packages/network-controller/tests/NetworkController.test.ts index 88de4dd52ee..596573ebb62 100644 --- a/packages/network-controller/tests/NetworkController.test.ts +++ b/packages/network-controller/tests/NetworkController.test.ts @@ -421,7 +421,10 @@ describe('NetworkController', () => { ); }); - it('corrects RPC endpoint types based on their URLs', async () => { + it.each([ + 'https://mainnet.infura.io/v3/{infuraProjectId}', + 'https://mainnet.infura.io/v3/infura-project-id', + ])('corrects RPC endpoint types based on their URLs', async (url) => { await withController( { state: { @@ -429,7 +432,7 @@ describe('NetworkController', () => { '0x1337': buildCustomNetworkConfiguration({ rpcEndpoints: [ buildCustomRpcEndpoint({ - url: 'https://mainnet.infura.io/v3/{infuraProjectId}', + url, }), ], }), @@ -4319,6 +4322,27 @@ describe('NetworkController', () => { }); }); + it('infers an Infura RPC endpoint when adding a network', async () => { + const rpcEndpoint = { + networkClientId: 'some-network', + url: 'https://some-network.infura.io/v3/{infuraProjectId}', + }; + + await withController(({ controller }) => { + const result = controller.addNetwork( + buildAddNetworkFields({ + rpcEndpoints: [rpcEndpoint], + }), + ); + + expect(result.rpcEndpoints[0]).toStrictEqual({ + networkClientId: 'some-network', + type: RpcEndpointType.Infura, + url: 'https://some-network.infura.io/v3/{infuraProjectId}', + }); + }); + }); + it('throws if the rpcEndpoints field is an empty array', async () => { await withController(({ controller }) => { expect(() => From 1f5d82ab7148c3d5ebd429a35c46c88ac0a46063 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 31 Aug 2026 17:40:18 +0000 Subject: [PATCH 4/7] test(network-controller): cover inferred Infura client IDs Co-authored-by: elliot.winkler --- packages/network-controller/tests/NetworkController.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/network-controller/tests/NetworkController.test.ts b/packages/network-controller/tests/NetworkController.test.ts index 596573ebb62..93c3c677de8 100644 --- a/packages/network-controller/tests/NetworkController.test.ts +++ b/packages/network-controller/tests/NetworkController.test.ts @@ -4324,7 +4324,6 @@ describe('NetworkController', () => { it('infers an Infura RPC endpoint when adding a network', async () => { const rpcEndpoint = { - networkClientId: 'some-network', url: 'https://some-network.infura.io/v3/{infuraProjectId}', }; From b971d59ebffb773ed1d85f45c29dd45b27d9adb7 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 31 Aug 2026 17:41:07 +0000 Subject: [PATCH 5/7] test(network-controller): exclude defensive normalization branches Co-authored-by: elliot.winkler --- packages/network-controller/src/NetworkController.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index 9504a45bbb7..b78929e5b51 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -1191,6 +1191,7 @@ function deriveInfuraNetworkNameFromRpcEndpointUrl( return match.groups.networkName; } + /* istanbul ignore next -- The URL is matched before this function is called. */ throw new Error('Could not derive Infura network from RPC endpoint URL'); } @@ -1305,6 +1306,7 @@ function normalizeNetworkConfiguration( infuraProjectId, ); + /* istanbul ignore if -- State endpoint IDs are required by the public state type. */ if (!hasNetworkClientId(inferredRpcEndpoint)) { throw new Error( `Network configuration '${networkConfiguration.name}' has an RPC endpoint without a network client ID`, From 4178555442f1612116ed0711f14b3d510e41c5b5 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 31 Aug 2026 17:42:28 +0000 Subject: [PATCH 6/7] chore(network-controller): preserve changelog formatting Co-authored-by: elliot.winkler --- packages/network-controller/CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/network-controller/CHANGELOG.md b/packages/network-controller/CHANGELOG.md index 3d0603f8541..4192b2fafec 100644 --- a/packages/network-controller/CHANGELOG.md +++ b/packages/network-controller/CHANGELOG.md @@ -1282,9 +1282,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Initial release - - As a result of converting our shared controllers repo into a monorepo ([#831](https://github.com/MetaMask/core/pull/831)), we've created this package from select parts of [`@metamask/controllers` v33.0.0](https://github.com/MetaMask/core/tree/v33.0.0), namely: - - Everything in `src/network` (minus `NetworkType` and `NetworksChainId`, which were placed in `@metamask/controller-utils`) All changes listed after this point were applied to this package following the monorepo conversion. From e2a873635abb0a228bf7dae4b9d3a2e36fb39ff1 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 31 Aug 2026 17:42:51 +0000 Subject: [PATCH 7/7] fix(network-controller): remove obsolete URL assertion Co-authored-by: elliot.winkler --- packages/network-controller/src/NetworkController.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index b78929e5b51..2302bc99a50 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -3441,8 +3441,7 @@ export class NetworkController extends BaseController< type: RpcEndpointType.Infura, networkClientId: registryNetworkConfig.rpcProviders.default.networkClientId, - url: registryNetworkConfig.rpcProviders.default - .url as InfuraRpcEndpoint['url'], + url: registryNetworkConfig.rpcProviders.default.url, } : { type: RpcEndpointType.Custom,