diff --git a/packages/assets-controllers/src/TokenRatesController.test.ts b/packages/assets-controllers/src/TokenRatesController.test.ts index 10d6122f6c..df8833ff4e 100644 --- a/packages/assets-controllers/src/TokenRatesController.test.ts +++ b/packages/assets-controllers/src/TokenRatesController.test.ts @@ -641,11 +641,6 @@ describe('TokenRatesController', () => { .mockResolvedValue(); triggerNetworkStateChange({ ...defaultNetworkState, - providerConfig: { - ...defaultNetworkState.providerConfig, - chainId: ChainId.mainnet, - ticker: 'NEW', - }, selectedNetworkClientId: defaultSelectedNetworkClientId, }); @@ -1421,13 +1416,6 @@ describe('TokenRatesController', () => { }, }, }, - mockNetworkState: { - providerConfig: { - ...defaultNetworkState.providerConfig, - chainId: toHex(2), - ticker: 'ticker', - }, - }, }, async ({ controller }) => { controller.startPollingByNetworkClientId('mainnet'); diff --git a/packages/assets-controllers/src/TokenRatesController.ts b/packages/assets-controllers/src/TokenRatesController.ts index 1888fbc1a9..c7380171fb 100644 --- a/packages/assets-controllers/src/TokenRatesController.ts +++ b/packages/assets-controllers/src/TokenRatesController.ts @@ -435,12 +435,16 @@ export class TokenRatesController extends StaticIntervalPollingController< chainId: Hex; ticker: string; } { - const { providerConfig } = this.messagingSystem.call( + const { selectedNetworkClientId } = this.messagingSystem.call( 'NetworkController:getState', ); + const networkClient = this.messagingSystem.call( + 'NetworkController:getNetworkClientById', + selectedNetworkClientId, + ); return { - chainId: providerConfig.chainId, - ticker: providerConfig.ticker, + chainId: networkClient.configuration.chainId, + ticker: networkClient.configuration.ticker, }; } diff --git a/packages/network-controller/CHANGELOG.md b/packages/network-controller/CHANGELOG.md index 8e76f49010..de3beeb534 100644 --- a/packages/network-controller/CHANGELOG.md +++ b/packages/network-controller/CHANGELOG.md @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- **BREAKING:** Update `networksMetadata` state property so that the keys in the object will only ever be network client IDs and not RPC URLs ([#4254](https://github.com/MetaMask/core/pull/4254)) + - Some keys could have been RPC URLs if the initial network controller state had a `providerConfig` with an empty `id`, but since `providerConfig` is being removed, that won't happen anymore. + +### Removed + +- **BREAKING:** Remove `providerConfig` property from state along with `ProviderConfig` type and `NetworkController:getProviderConfig` messenger action ([#4254](https://github.com/MetaMask/core/pull/4254)) + - The best way to obtain the equivalent configuration object, e.g. to access the chain ID of the currently selected network, is to get `selectedNetworkClientId` from state, pass this to the `NetworkController:getNetworkClientId` messenger action, and then use the `configuration` property on the network client. + ## [19.0.0] ### Changed diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index 126ee67b29..d9fe7cc474 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -6,8 +6,6 @@ import type { import { BaseController } from '@metamask/base-controller'; import { BUILT_IN_NETWORKS, - NetworksTicker, - ChainId, InfuraNetworkType, NetworkType, isSafeChainId, @@ -44,27 +42,6 @@ import type { const log = createModuleLogger(projectLogger, 'NetworkController'); -/** - * @type ProviderConfig - * - * Configuration passed to web3-provider-engine - * @property rpcUrl - RPC target URL. - * @property type - Human-readable network name. - * @property chainId - Network ID as per EIP-155. - * @property ticker - Currency ticker. - * @property nickname - Personalized network name. - * @property id - Network Configuration Id. - */ -export type ProviderConfig = { - rpcUrl?: string; - type: NetworkType; - chainId: Hex; - ticker: string; - nickname?: string; - rpcPrefs?: { blockExplorerUrl?: string }; - id?: NetworkConfigurationId; -}; - export type Block = { baseFeePerGas?: string; }; @@ -197,109 +174,6 @@ function isErrorWithCode(error: unknown): error is { code: string | number } { return typeof error === 'object' && error !== null && 'code' in error; } -/** - * Builds an identifier for an Infura network client for lookup purposes. - * - * @param infuraNetworkOrProviderConfig - The name of an Infura network or a - * provider config. - * @returns The built identifier. - */ -function buildInfuraNetworkClientId( - infuraNetworkOrProviderConfig: - | InfuraNetworkType - | (ProviderConfig & { type: InfuraNetworkType }), -): BuiltInNetworkClientId { - if (typeof infuraNetworkOrProviderConfig === 'string') { - return infuraNetworkOrProviderConfig; - } - return infuraNetworkOrProviderConfig.type; -} - -/** - * Builds an identifier for a custom network client for lookup purposes. - * - * @param args - This function can be called two ways: - * 1. The ID of a network configuration. - * 2. A provider config and a set of network configurations. - * @returns The built identifier. - */ -function buildCustomNetworkClientId( - ...args: - | [NetworkConfigurationId] - | [ - ProviderConfig & { type: typeof NetworkType.rpc; rpcUrl: string }, - NetworkConfigurations, - ] -): CustomNetworkClientId { - if (args.length === 1) { - return args[0]; - } - const [{ id, rpcUrl }, networkConfigurations] = args; - if (id === undefined) { - const matchingNetworkConfiguration = Object.values( - networkConfigurations, - ).find((networkConfiguration) => { - return networkConfiguration.rpcUrl === rpcUrl.toLowerCase(); - }); - if (matchingNetworkConfiguration) { - return matchingNetworkConfiguration.id; - } - return rpcUrl.toLowerCase(); - } - return id; -} - -/** - * Returns whether the given provider config refers to an Infura network. - * - * @param providerConfig - The provider config. - * @returns True if the provider config refers to an Infura network, false - * otherwise. - */ -function isInfuraProviderConfig( - providerConfig: ProviderConfig, -): providerConfig is ProviderConfig & { type: InfuraNetworkType } { - return isInfuraNetworkType(providerConfig.type); -} - -/** - * Returns whether the given provider config refers to an Infura network. - * - * @param providerConfig - The provider config. - * @returns True if the provider config refers to an Infura network, false - * otherwise. - */ -function isCustomProviderConfig( - providerConfig: ProviderConfig, -): providerConfig is ProviderConfig & { type: typeof NetworkType.rpc } { - return providerConfig.type === NetworkType.rpc; -} - -/** - * As a provider config represents the settings that are used to interface with - * an RPC endpoint, it must have both a chain ID and an RPC URL if it represents - * a custom network. These properties _should_ be set as they are validated in - * the UI when a user adds a custom network, but just to be safe we validate - * them here. - * - * In addition, historically the `rpcUrl` property on the ProviderConfig type - * has been optional, even though it should not be. Making this non-optional - * would be a breaking change, so this function types the provider config - * correctly so that we don't have to check `rpcUrl` in other places. - * - * @param providerConfig - A provider config. - * @throws if the provider config does not have a chain ID or an RPC URL. - */ -function validateCustomProviderConfig( - providerConfig: ProviderConfig & { type: typeof NetworkType.rpc }, -): asserts providerConfig is typeof providerConfig & { rpcUrl: string } { - if (providerConfig.chainId === undefined) { - throw new Error('chainId must be provided for custom RPC endpoints'); - } - if (providerConfig.rpcUrl === undefined) { - throw new Error('rpcUrl must be provided for custom RPC endpoints'); - } -} /** * The string that uniquely identifies an Infura network client. */ @@ -326,13 +200,11 @@ export type NetworksMetadata = { * @type NetworkState * * Network controller state - * @property providerConfig - RPC URL and network name provider settings of the currently connected network * @property properties - an additional set of network properties for the currently connected network * @property networkConfigurations - the full list of configured networks either preloaded or added by the user. */ export type NetworkState = { selectedNetworkClientId: NetworkClientId; - providerConfig: ProviderConfig; networkConfigurations: NetworkConfigurations; networksMetadata: NetworksMetadata; }; @@ -415,11 +287,6 @@ export type NetworkControllerGetStateAction = ControllerGetStateAction< NetworkState >; -export type NetworkControllerGetProviderConfigAction = { - type: `NetworkController:getProviderConfig`; - handler: () => ProviderConfig; -}; - export type NetworkControllerGetEthQueryAction = { type: `NetworkController:getEthQuery`; handler: () => EthQuery | undefined; @@ -468,7 +335,6 @@ export type NetworkControllerGetNetworkConfigurationByNetworkClientId = { export type NetworkControllerActions = | NetworkControllerGetStateAction - | NetworkControllerGetProviderConfigAction | NetworkControllerGetEthQueryAction | NetworkControllerGetNetworkClientByIdAction | NetworkControllerGetSelectedNetworkClientAction @@ -495,11 +361,6 @@ export type NetworkControllerOptions = { export const defaultState: NetworkState = { selectedNetworkClientId: NetworkType.mainnet, - providerConfig: { - type: NetworkType.mainnet, - chainId: ChainId.mainnet, - ticker: NetworksTicker.mainnet, - }, networksMetadata: {}, networkConfigurations: {}, }; @@ -558,7 +419,7 @@ export class NetworkController extends BaseController< #trackMetaMetricsEvent: (event: MetaMetricsEventPayload) => void; - #previousProviderConfig: ProviderConfig; + #previouslySelectedNetworkClientId: string; #providerProxy: ProviderProxy | undefined; @@ -566,6 +427,10 @@ export class NetworkController extends BaseController< #autoManagedNetworkClientRegistry?: AutoManagedNetworkClientRegistry; + #autoManagedNetworkClient?: + | AutoManagedNetworkClient + | AutoManagedNetworkClient; + constructor({ messenger, state, @@ -583,10 +448,6 @@ export class NetworkController extends BaseController< persist: true, anonymous: false, }, - providerConfig: { - persist: true, - anonymous: false, - }, networkConfigurations: { persist: true, anonymous: false, @@ -600,14 +461,6 @@ export class NetworkController extends BaseController< } this.#infuraProjectId = infuraProjectId; this.#trackMetaMetricsEvent = trackMetaMetricsEvent; - this.messagingSystem.registerActionHandler( - // TODO: Either fix this lint violation or explain why it's necessary to ignore. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - `${this.name}:getProviderConfig`, - () => { - return this.state.providerConfig; - }, - ); this.messagingSystem.registerActionHandler( // TODO: Either fix this lint violation or explain why it's necessary to ignore. @@ -667,7 +520,8 @@ export class NetworkController extends BaseController< this.getSelectedNetworkClient.bind(this), ); - this.#previousProviderConfig = this.state.providerConfig; + this.#previouslySelectedNetworkClientId = + this.state.selectedNetworkClientId; } /** @@ -764,6 +618,8 @@ export class NetworkController extends BaseController< autoManagedNetworkClientRegistry[NetworkClientType.Infura][ networkClientId ]; + // This is impossible to reach + /* istanbul ignore if */ if (!infuraNetworkClient) { throw new Error( // TODO: Either fix this lint violation or explain why it's necessary to ignore. @@ -789,19 +645,27 @@ export class NetworkController extends BaseController< } /** - * Executes a series of steps to apply the changes to the provider config: + * Executes a series of steps to switch the network: * - * 1. Notifies subscribers that the network is about to change. - * 2. Looks up a known and preinitialized network client matching the provider - * config and re-points the provider and block tracker proxy to it. - * 3. Notifies subscribers that the network has changed. + * 1. Notifies subscribers via the messenger that the network is about to be + * switched (and, really, that the global provider and block tracker proxies + * will be re-pointed to a new network). + * 2. Looks up a known and preinitialized network client matching the given + * ID and uses it to re-point the aforementioned provider and block tracker + * proxies. + * 3. Notifies subscribers via the messenger that the network has switched. + * 4. Captures metadata for the newly switched network in state. + * + * @param networkClientId - The ID of a network client that requests will be + * routed through (either the name of an Infura network or the ID of a custom + * network configuration). */ - async #refreshNetwork() { + async #refreshNetwork(networkClientId: string) { this.messagingSystem.publish( 'NetworkController:networkWillChange', this.state, ); - this.#applyNetworkSelection(); + this.#applyNetworkSelection(networkClientId); this.messagingSystem.publish( 'NetworkController:networkDidChange', this.state, @@ -810,13 +674,11 @@ export class NetworkController extends BaseController< } /** - * Populates the network clients and establishes the initial network based on - * the provider configuration in state. + * Creates network clients for built-in and custom networks, then establishes + * the currently selected network client based on state. */ async initializeProvider() { - this.#ensureAutoManagedNetworkClientRegistryPopulated(); - - this.#applyNetworkSelection(); + this.#applyNetworkSelection(this.state.selectedNetworkClientId); await this.lookupNetwork(); } @@ -893,17 +755,19 @@ export class NetworkController extends BaseController< } /** - * Performs side effects after switching to a network. If the network is - * available, updates the network state with the network ID of the network and - * stores whether the network supports EIP-1559; otherwise clears said - * information about the network that may have been previously stored. + * Persists the following metadata about the given or selected network to + * state: + * + * - The status of the network, namely, whether it is available, geo-blocked + * (Infura only), or unavailable, or whether the status is unknown + * - Whether the network supports EIP-1559, or whether it is unknown + * + * Note that it is possible for the network to be switched while this data is + * being collected. If that is the case, no metadata for the (now previously) + * selected network will be updated. * - * @param networkClientId - (Optional) The ID of the network client to update. + * @param networkClientId - The ID of the network client to update. * If no ID is provided, uses the currently selected network. - * @fires infuraIsBlocked if the network is Infura-supported and is blocking - * requests. - * @fires infuraIsUnblocked if the network is Infura-supported and is not - * blocking requests, or if the network is not Infura-supported. */ async lookupNetwork(networkClientId?: NetworkClientId) { if (networkClientId) { @@ -915,7 +779,9 @@ export class NetworkController extends BaseController< return; } - const isInfura = isInfuraProviderConfig(this.state.providerConfig); + const isInfura = + this.#autoManagedNetworkClient?.configuration.type === + NetworkClientType.Infura; let networkChanged = false; const listener = () => { @@ -1030,50 +896,19 @@ export class NetworkController extends BaseController< } /** - * Convenience method to update provider RPC settings. + * Changes the selected network. * - * @param networkConfigurationIdOrType - The unique id for the network configuration to set as the active provider, - * or the type of a built-in network. + * @param networkClientId - The ID of a network client that requests will be + * routed through (either the name of an Infura network or the ID of a custom + * network configuration). + * @throws if no network client is associated with the given + * `networkClientId`. */ - async setActiveNetwork(networkConfigurationIdOrType: string) { - this.#previousProviderConfig = this.state.providerConfig; - - let targetNetwork: ProviderConfig; - if (isInfuraNetworkType(networkConfigurationIdOrType)) { - const ticker = NetworksTicker[networkConfigurationIdOrType]; - - targetNetwork = { - chainId: ChainId[networkConfigurationIdOrType], - id: undefined, - rpcPrefs: BUILT_IN_NETWORKS[networkConfigurationIdOrType].rpcPrefs, - rpcUrl: undefined, - nickname: undefined, - ticker, - type: networkConfigurationIdOrType, - }; - } else { - if ( - !Object.keys(this.state.networkConfigurations).includes( - networkConfigurationIdOrType, - ) - ) { - throw new Error( - `networkConfigurationId ${networkConfigurationIdOrType} does not match a configured networkConfiguration or built-in network type`, - ); - } - targetNetwork = { - ...this.state.networkConfigurations[networkConfigurationIdOrType], - type: NetworkType.rpc, - }; - } - - this.#ensureAutoManagedNetworkClientRegistryPopulated(); + async setActiveNetwork(networkClientId: string) { + this.#previouslySelectedNetworkClientId = + this.state.selectedNetworkClientId; - this.update((state) => { - state.providerConfig = targetNetwork; - }); - - await this.#refreshNetwork(); + await this.#refreshNetwork(networkClientId); } /** @@ -1178,11 +1013,11 @@ export class NetworkController extends BaseController< } /** - * Re-initializes the provider and block tracker for the current network. + * Ensures that the provider and block tracker proxies are pointed to the + * currently selected network and refreshes the metadata for the */ async resetConnection() { - this.#ensureAutoManagedNetworkClientRegistryPopulated(); - await this.#refreshNetwork(); + await this.#refreshNetwork(this.state.selectedNetworkClientId); } /** @@ -1298,9 +1133,7 @@ export class NetworkController extends BaseController< const upsertedNetworkConfigurationId = existingNetworkConfiguration ? existingNetworkConfiguration.id : random(); - const networkClientId = buildCustomNetworkClientId( - upsertedNetworkConfigurationId, - ); + const networkClientId = upsertedNetworkConfigurationId; const customNetworkClientRegistry = autoManagedNetworkClientRegistry[NetworkClientType.Custom]; @@ -1375,7 +1208,7 @@ export class NetworkController extends BaseController< const autoManagedNetworkClientRegistry = this.#ensureAutoManagedNetworkClientRegistryPopulated(); - const networkClientId = buildCustomNetworkClientId(networkConfigurationId); + const networkClientId = networkConfigurationId; this.update((state) => { delete state.networkConfigurations[networkConfigurationId]; @@ -1390,18 +1223,14 @@ export class NetworkController extends BaseController< } /** - * Switches to the previously selected network, assuming that there is one - * (if not and `initializeProvider` has not been previously called, then this - * method is equivalent to calling `resetConnection`). + * Assuming that the network has been previously switched, switches to this + * new network. + * + * If the network has not been previously switched, this method is equivalent + * to {@link resetConnection}. */ async rollbackToPreviousProvider() { - this.#ensureAutoManagedNetworkClientRegistryPopulated(); - - this.update((state) => { - state.providerConfig = this.#previousProviderConfig; - }); - - await this.#refreshNetwork(); + await this.#refreshNetwork(this.#previouslySelectedNetworkClientId); } /** @@ -1475,7 +1304,6 @@ export class NetworkController extends BaseController< return [ ...this.#buildIdentifiedInfuraNetworkClientConfigurations(), ...this.#buildIdentifiedCustomNetworkClientConfigurations(), - ...this.#buildIdentifiedNetworkClientConfigurationsFromProviderConfig(), ].reduce( ( registry, @@ -1484,9 +1312,6 @@ export class NetworkController extends BaseController< const autoManagedNetworkClient = createAutoManagedNetworkClient( networkClientConfiguration, ); - if (networkClientId in registry[networkClientType]) { - return registry; - } return { ...registry, [networkClientType]: { @@ -1515,7 +1340,6 @@ export class NetworkController extends BaseController< InfuraNetworkClientConfiguration, ][] { return knownKeysOf(InfuraNetworkType).map((network) => { - const networkClientId = buildInfuraNetworkClientId(network); const networkClientConfiguration: InfuraNetworkClientConfiguration = { type: NetworkClientType.Infura, network, @@ -1523,11 +1347,7 @@ export class NetworkController extends BaseController< chainId: BUILT_IN_NETWORKS[network].chainId, ticker: BUILT_IN_NETWORKS[network].ticker, }; - return [ - NetworkClientType.Infura, - networkClientId, - networkClientConfiguration, - ]; + return [NetworkClientType.Infura, network, networkClientConfiguration]; }); } @@ -1544,15 +1364,7 @@ export class NetworkController extends BaseController< ][] { return Object.entries(this.state.networkConfigurations).map( ([networkConfigurationId, networkConfiguration]) => { - if (networkConfiguration.chainId === undefined) { - throw new Error('chainId must be provided for custom RPC endpoints'); - } - if (networkConfiguration.rpcUrl === undefined) { - throw new Error('rpcUrl must be provided for custom RPC endpoints'); - } - const networkClientId = buildCustomNetworkClientId( - networkConfigurationId, - ); + const networkClientId = networkConfigurationId; const networkClientConfiguration: CustomNetworkClientConfiguration = { type: NetworkClientType.Custom, chainId: networkConfiguration.chainId, @@ -1569,106 +1381,61 @@ export class NetworkController extends BaseController< } /** - * Converts the provider config object in state to a network client - * configuration object. + * Updates the global provider and block tracker proxies (accessible via + * {@link getSelectedNetworkClient}) to point to the same ones within the + * given network client, thereby magically switching any consumers using these + * proxies to use the new network. * - * @returns The network client config. - * @throws If the provider config is of type "rpc" and lacks either a - * `chainId` or an `rpcUrl`. - */ - #buildIdentifiedNetworkClientConfigurationsFromProviderConfig(): - | [ - [ - NetworkClientType.Custom, - CustomNetworkClientId, - CustomNetworkClientConfiguration, - ], - ] - | [] { - const { providerConfig } = this.state; - - if (isCustomProviderConfig(providerConfig)) { - validateCustomProviderConfig(providerConfig); - const networkClientId = buildCustomNetworkClientId( - providerConfig, - this.state.networkConfigurations, - ); - const networkClientConfiguration: CustomNetworkClientConfiguration = { - chainId: providerConfig.chainId, - rpcUrl: providerConfig.rpcUrl, - type: NetworkClientType.Custom, - ticker: providerConfig.ticker, - }; - return [ - [NetworkClientType.Custom, networkClientId, networkClientConfiguration], - ]; - } - - if (isInfuraProviderConfig(providerConfig)) { - return []; - } - - // TODO: Either fix this lint violation or explain why it's necessary to ignore. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - throw new Error(`Unrecognized network type: '${providerConfig.type}'`); - } - - /** - * Uses the information in the provider config object to look up a known and - * preinitialized network client. Once a network client is found, updates the - * provider and block tracker proxy to point to those from the network client, - * then finally creates an EthQuery that points to the provider proxy. + * Also refreshes the EthQuery instance accessible via the `getEthQuery` + * action to wrap the provider from the new network client. Note that this is + * not a proxy, so consumers will need to call `getEthQuery` again after the + * network switch. * - * @throws If no network client could be found matching the current provider - * config. + * @param networkClientId - The ID of a network client that requests will be + * routed through (either the name of an Infura network or the ID of a custom + * network configuration). + * @throws if no network client could be found matching the given ID. */ - #applyNetworkSelection() { - if (!this.#autoManagedNetworkClientRegistry) { - throw new Error( - 'initializeProvider must be called first in order to switch the network', - ); - } + #applyNetworkSelection(networkClientId: string) { + const autoManagedNetworkClientRegistry = + this.#ensureAutoManagedNetworkClientRegistryPopulated(); - const { providerConfig } = this.state; + let autoManagedNetworkClient: + | AutoManagedNetworkClient + | AutoManagedNetworkClient; - let autoManagedNetworkClient: AutoManagedNetworkClient; + if (isInfuraNetworkType(networkClientId)) { + const possibleAutoManagedNetworkClient = + autoManagedNetworkClientRegistry[NetworkClientType.Infura][ + networkClientId + ]; - let networkClientId: NetworkClientId; - if (isInfuraProviderConfig(providerConfig)) { - const networkClientType = NetworkClientType.Infura; - networkClientId = buildInfuraNetworkClientId(providerConfig); - const builtInNetworkClientRegistry = - this.#autoManagedNetworkClientRegistry[networkClientType]; - autoManagedNetworkClient = - builtInNetworkClientRegistry[networkClientId as BuiltInNetworkClientId]; - if (!autoManagedNetworkClient) { + // This is impossible to reach + /* istanbul ignore if */ + if (!possibleAutoManagedNetworkClient) { throw new Error( - // TODO: Either fix this lint violation or explain why it's necessary to ignore. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - `Could not find custom network matching ${networkClientId}`, + `Infura network client not found with ID '${networkClientId}'`, ); } - } else if (isCustomProviderConfig(providerConfig)) { - validateCustomProviderConfig(providerConfig); - const networkClientType = NetworkClientType.Custom; - networkClientId = buildCustomNetworkClientId( - providerConfig, - this.state.networkConfigurations, - ); - const customNetworkClientRegistry = - this.#autoManagedNetworkClientRegistry[networkClientType]; - autoManagedNetworkClient = customNetworkClientRegistry[networkClientId]; - if (!autoManagedNetworkClient) { + + autoManagedNetworkClient = possibleAutoManagedNetworkClient; + } else { + const possibleAutoManagedNetworkClient = + autoManagedNetworkClientRegistry[NetworkClientType.Custom][ + networkClientId + ]; + + if (!possibleAutoManagedNetworkClient) { throw new Error( - // TODO: Either fix this lint violation or explain why it's necessary to ignore. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - `Could not find built-in network matching ${networkClientId}`, + `Custom network client not found with ID '${networkClientId}'`, ); } - } else { - throw new Error('Could not determine type of provider config'); + + autoManagedNetworkClient = possibleAutoManagedNetworkClient; } + this.#autoManagedNetworkClient = autoManagedNetworkClient; + this.update((state) => { state.selectedNetworkClientId = networkClientId; if (state.networksMetadata[networkClientId] === undefined) { @@ -1679,20 +1446,23 @@ export class NetworkController extends BaseController< } }); - const { provider, blockTracker } = autoManagedNetworkClient; - if (this.#providerProxy) { - this.#providerProxy.setTarget(provider); + this.#providerProxy.setTarget(this.#autoManagedNetworkClient.provider); } else { - this.#providerProxy = createEventEmitterProxy(provider); + this.#providerProxy = createEventEmitterProxy( + this.#autoManagedNetworkClient.provider, + ); } if (this.#blockTrackerProxy) { - this.#blockTrackerProxy.setTarget(blockTracker); + this.#blockTrackerProxy.setTarget( + this.#autoManagedNetworkClient.blockTracker, + ); } else { - this.#blockTrackerProxy = createEventEmitterProxy(blockTracker, { - eventFilter: 'skipInternal', - }); + this.#blockTrackerProxy = createEventEmitterProxy( + this.#autoManagedNetworkClient.blockTracker, + { eventFilter: 'skipInternal' }, + ); } this.#ethQuery = new EthQuery(this.#providerProxy); diff --git a/packages/network-controller/tests/NetworkController.test.ts b/packages/network-controller/tests/NetworkController.test.ts index 2ce9f58a1d..2b3d924f34 100644 --- a/packages/network-controller/tests/NetworkController.test.ts +++ b/packages/network-controller/tests/NetworkController.test.ts @@ -1,8 +1,8 @@ import { ControllerMessenger } from '@metamask/base-controller'; import { BUILT_IN_NETWORKS, - ChainId, InfuraNetworkType, + isInfuraNetworkType, MAX_SAFE_CHAIN_ID, NetworkType, toHex, @@ -29,11 +29,14 @@ import type { NetworkControllerOptions, NetworkControllerStateChangeEvent, NetworkState, - ProviderConfig, } from '../src/NetworkController'; import { NetworkController } from '../src/NetworkController'; -import type { Provider } from '../src/types'; +import type { NetworkClientConfiguration, Provider } from '../src/types'; import { NetworkClientType } from '../src/types'; +import { + buildCustomNetworkClientConfiguration, + buildInfuraNetworkClientConfiguration, +} from './helpers'; jest.mock('../src/create-network-client'); @@ -183,11 +186,6 @@ describe('NetworkController', () => { Object { "networkConfigurations": Object {}, "networksMetadata": Object {}, - "providerConfig": Object { - "chainId": "0x1", - "ticker": "ETH", - "type": "mainnet", - }, "selectedNetworkClientId": "mainnet", } `); @@ -198,13 +196,6 @@ describe('NetworkController', () => { await withController( { state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'http://example-custom-rpc.metamask.io', - chainId: '0x9999' as const, - nickname: 'Test initial state', - ticker: 'TEST', - }, networksMetadata: { mainnet: { EIPS: { 1559: true }, @@ -225,13 +216,6 @@ describe('NetworkController', () => { "status": "unknown", }, }, - "providerConfig": Object { - "chainId": "0x9999", - "nickname": "Test initial state", - "rpcUrl": "http://example-custom-rpc.metamask.io", - "ticker": "TEST", - "type": "rpc", - }, "selectedNetworkClientId": "mainnet", } `); @@ -269,38 +253,17 @@ describe('NetworkController', () => { }); describe('initializeProvider', () => { - describe('when the type in the provider config is invalid', () => { - it('throws', async () => { - const invalidProviderConfig = {}; - await withController( - /* @ts-expect-error We're intentionally passing bad input. */ - { - state: { - providerConfig: invalidProviderConfig, - }, - }, - async ({ controller }) => { - await expect(async () => { - await controller.initializeProvider(); - }).rejects.toThrow("Unrecognized network type: 'undefined'"); - }, - ); - }); - }); - for (const { networkType } of INFURA_NETWORKS) { // TODO: Either fix this lint violation or explain why it's necessary to ignore. // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - describe(`when the type in the provider config is "${networkType}"`, () => { + describe(`when selectedNetworkClientId in state is the Infura network "${networkType}"`, () => { // TODO: Either fix this lint violation or explain why it's necessary to ignore. // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - it(`does not create another network client for the ${networkType} Infura network, since it is built in`, async () => { + it(`does not create another network client for the "${networkType}" network, since it is built in`, async () => { await withController( { state: { - providerConfig: buildProviderConfig({ - type: networkType, - }), + selectedNetworkClientId: networkType, }, infuraProjectId: 'some-infura-project-id', }, @@ -326,9 +289,7 @@ describe('NetworkController', () => { await withController( { state: { - providerConfig: buildProviderConfig({ - type: networkType, - }), + selectedNetworkClientId: networkType, }, infuraProjectId: 'some-infura-project-id', }, @@ -366,9 +327,10 @@ describe('NetworkController', () => { }); lookupNetworkTests({ - expectedProviderConfig: buildProviderConfig({ type: networkType }), + expectedNetworkClientConfiguration: + buildInfuraNetworkClientConfiguration(networkType), initialState: { - providerConfig: buildProviderConfig({ type: networkType }), + selectedNetworkClientId: networkType, }, operation: async (controller: NetworkController) => { await controller.initializeProvider(); @@ -377,935 +339,628 @@ describe('NetworkController', () => { }); } - describe('when the type in the provider config is "rpc"', () => { - describe('if the provider config points to a network configuration', () => { - it('creates a network client for the custom RPC endpoint described by the network configuration, not the provider config', async () => { - await withController( - { - state: { - providerConfig: { - type: NetworkType.rpc, - chainId: toHex(2), - rpcUrl: 'https://test.network.2', + describe('when selectedNetworkClientId in state is the ID of a network configuration', () => { + it('creates a network client using the network configuration', async () => { + await withController( + { + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurations: { + 'AAAA-AAAA-AAAA-AAAA': { id: 'AAAA-AAAA-AAAA-AAAA', + rpcUrl: 'https://test.network.1', + chainId: toHex(1337), ticker: 'TEST', }, - networkConfigurations: { - 'AAAA-AAAA-AAAA-AAAA': { - id: 'AAAA-AAAA-AAAA-AAAA', - rpcUrl: 'https://test.network.1', - chainId: toHex(1), - ticker: 'TEST', - }, - }, }, }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'test_method', - params: [], - }, - response: { - result: 'test response', - }, + }, + async ({ controller }) => { + const fakeProvider = buildFakeProvider([ + { + request: { + method: 'test_method', + params: [], }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); + response: { + result: 'test response', + }, + }, + ]); + const fakeNetworkClient = buildFakeClient(fakeProvider); + createNetworkClientMock.mockReturnValue(fakeNetworkClient); - await controller.initializeProvider(); + await controller.initializeProvider(); - expect(createNetworkClientMock).toHaveBeenCalledWith({ - chainId: toHex(1), - rpcUrl: 'https://test.network.1', - type: NetworkClientType.Custom, - ticker: 'TEST', - }); - expect(createNetworkClientMock).toHaveBeenCalledTimes(1); - }, - ); - }); + expect(createNetworkClientMock).toHaveBeenCalledWith({ + chainId: toHex(1337), + rpcUrl: 'https://test.network.1', + type: NetworkClientType.Custom, + ticker: 'TEST', + }); + expect(createNetworkClientMock).toHaveBeenCalledTimes(1); + }, + ); + }); - it('captures the resulting provider of the new network client', async () => { - await withController( - { - state: { - providerConfig: { - type: NetworkType.rpc, - chainId: toHex(2), - rpcUrl: 'https://test.network.2', + it('captures the resulting provider of the new network client', async () => { + await withController( + { + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurations: { + 'AAAA-AAAA-AAAA-AAAA': { id: 'AAAA-AAAA-AAAA-AAAA', + rpcUrl: 'https://test.network.1', + chainId: toHex(1337), ticker: 'TEST', }, - networkConfigurations: { - 'AAAA-AAAA-AAAA-AAAA': { - id: 'AAAA-AAAA-AAAA-AAAA', - rpcUrl: 'https://test.network.1', - chainId: toHex(1), - ticker: 'TEST', - }, - }, }, }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'test_method', - params: [], - }, - response: { - result: 'test response', - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - - await controller.initializeProvider(); - - const { provider } = controller.getProviderAndBlockTracker(); - assert(provider, 'Provider is not set'); - const { result } = await promisify(provider.sendAsync).call( - provider, - { - id: 1, - jsonrpc: '2.0', + }, + async ({ controller }) => { + const fakeProvider = buildFakeProvider([ + { + request: { method: 'test_method', params: [], }, - ); - expect(result).toBe('test response'); - }, - ); - }); - }); - - describe('if the provider config does not point to a network configuration, but matches one based on RPC URL (exactly)', () => { - it('creates a network client for the custom RPC endpoint described by the network configuration, not the provider config', async () => { - await withController( - { - state: { - providerConfig: { - type: NetworkType.rpc, - chainId: toHex(2), - rpcUrl: 'https://test.network', - ticker: 'TEST', - }, - networkConfigurations: { - 'AAAA-AAAA-AAAA-AAAA': { - id: 'AAAA-AAAA-AAAA-AAAA', - rpcUrl: 'https://test.network', - chainId: toHex(1), - ticker: 'TEST', - }, + response: { + result: 'test response', }, }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'test_method', - params: [], - }, - response: { - result: 'test response', - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); + ]); + const fakeNetworkClient = buildFakeClient(fakeProvider); + createNetworkClientMock.mockReturnValue(fakeNetworkClient); - await controller.initializeProvider(); + await controller.initializeProvider(); - expect(createNetworkClientMock).toHaveBeenCalledWith({ - chainId: toHex(1), - rpcUrl: 'https://test.network', - type: NetworkClientType.Custom, - ticker: 'TEST', - }); - expect(createNetworkClientMock).toHaveBeenCalledTimes(1); - }, - ); - }); + const { provider } = controller.getProviderAndBlockTracker(); + assert(provider, 'Provider is not set'); + const { result } = await promisify(provider.sendAsync).call( + provider, + { + id: 1, + jsonrpc: '2.0', + method: 'test_method', + params: [], + }, + ); + expect(result).toBe('test response'); + }, + ); + }); + }); + }); + + describe('getProviderAndBlockTracker', () => { + it('returns objects that proxy to the provider and block tracker as long as the provider has been initialized', async () => { + await withController(async ({ controller }) => { + const fakeProvider = buildFakeProvider(); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + await controller.initializeProvider(); + + const { provider, blockTracker } = + controller.getProviderAndBlockTracker(); + + expect(provider).toHaveProperty('sendAsync'); + expect(blockTracker).toHaveProperty('checkForLatestBlock'); + }); + }); + + it("returns undefined for both the provider and block tracker if the provider hasn't been initialized yet", async () => { + await withController(async ({ controller }) => { + const { provider, blockTracker } = + controller.getProviderAndBlockTracker(); + + expect(provider).toBeUndefined(); + expect(blockTracker).toBeUndefined(); + }); + }); - it('captures the resulting provider of the new network client', async () => { + for (const { networkType } of INFURA_NETWORKS) { + // TODO: Either fix this lint violation or explain why it's necessary to ignore. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + describe(`when the selectedNetworkClientId is changed to "${networkType}"`, () => { + // TODO: Either fix this lint violation or explain why it's necessary to ignore. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + it(`returns a provider object that was pointed to another network before the switch and is pointed to "${networkType}" afterward`, async () => { await withController( { state: { - providerConfig: { - type: NetworkType.rpc, - chainId: toHex(2), - rpcUrl: 'https://test.network', - ticker: 'TEST', - }, + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', networkConfigurations: { 'AAAA-AAAA-AAAA-AAAA': { id: 'AAAA-AAAA-AAAA-AAAA', - rpcUrl: 'https://test.network', - chainId: toHex(1), + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), ticker: 'TEST', }, }, }, + infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'test_method', - params: [], + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response 1', + }, }, - response: { - result: 'test response', + ]), + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response 2', + }, }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + chainId: toHex(1337), + rpcUrl: 'https://mock-rpc-url', + type: NetworkClientType.Custom, + ticker: 'TEST', + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + network: networkType, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + chainId: BUILT_IN_NETWORKS[networkType].chainId, + ticker: BUILT_IN_NETWORKS[networkType].ticker, + }) + .mockReturnValue(fakeNetworkClients[1]); await controller.initializeProvider(); - const { provider } = controller.getProviderAndBlockTracker(); - assert(provider, 'Provider is not set'); - const { result } = await promisify(provider.sendAsync).call( + assert(provider, 'Provider is somehow unset'); + + const promisifiedSendAsync1 = promisify(provider.sendAsync).bind( provider, - { - id: 1, - jsonrpc: '2.0', - method: 'test_method', - params: [], - }, ); - expect(result).toBe('test response'); + const response1 = await promisifiedSendAsync1({ + id: '1', + jsonrpc: '2.0', + method: 'test', + }); + expect(response1.result).toBe('test response 1'); + + await controller.setProviderType(networkType); + const promisifiedSendAsync2 = promisify(provider.sendAsync).bind( + provider, + ); + const response2 = await promisifiedSendAsync2({ + id: '2', + jsonrpc: '2.0', + method: 'test', + }); + expect(response2.result).toBe('test response 2'); }, ); }); }); + } - describe('if the provider config does not point to a network configuration, but matches one based on RPC URL (case-insensitively)', () => { - it('creates a network client for the custom RPC endpoint described by the network configuration, not the provider config', async () => { - await withController( - { - state: { - providerConfig: { - type: NetworkType.rpc, - chainId: toHex(2), - rpcUrl: 'HTTPS://TEST.NETWORK', - ticker: 'TEST', - }, - networkConfigurations: { - 'AAAA-AAAA-AAAA-AAAA': { - id: 'AAAA-AAAA-AAAA-AAAA', - rpcUrl: 'https://test.network', - chainId: toHex(1), - ticker: 'TEST', - }, + describe(`when the selectedNetworkClientId is changed to a network configuration ID`, () => { + it('returns a provider object that was pointed to another network before the switch and is pointed to the new network', async () => { + await withController( + { + state: { + selectedNetworkClientId: 'goerli', + networkConfigurations: { + testNetworkConfigurationId: { + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + ticker: 'ABC', + id: 'testNetworkConfigurationId', }, }, }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ + infuraProjectId: 'some-infura-project-id', + }, + async ({ controller }) => { + const fakeProviders = [ + buildFakeProvider([ { request: { - method: 'test_method', - params: [], + method: 'test', }, response: { - result: 'test response', - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - - await controller.initializeProvider(); - - expect(createNetworkClientMock).toHaveBeenCalledWith({ - chainId: toHex(1), - rpcUrl: 'https://test.network', - type: NetworkClientType.Custom, - ticker: 'TEST', - }); - expect(createNetworkClientMock).toHaveBeenCalledTimes(1); - }, - ); - }); - - it('captures the resulting provider of the new network client', async () => { - await withController( - { - state: { - providerConfig: { - type: NetworkType.rpc, - chainId: toHex(2), - rpcUrl: 'HTTPS://TEST.NETWORK', - ticker: 'TEST', - }, - networkConfigurations: { - 'AAAA-AAAA-AAAA-AAAA': { - id: 'AAAA-AAAA-AAAA-AAAA', - rpcUrl: 'https://test.network', - chainId: toHex(1), - ticker: 'TEST', + result: 'test response 1', }, }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ + ]), + buildFakeProvider([ { request: { - method: 'test_method', - params: [], + method: 'test', }, response: { - result: 'test response', + result: 'test response 2', }, }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + network: NetworkType.goerli, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + chainId: BUILT_IN_NETWORKS[NetworkType.goerli].chainId, + ticker: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].ticker, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + chainId: toHex(1337), + rpcUrl: 'https://mock-rpc-url', + type: NetworkClientType.Custom, + ticker: 'ABC', + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); + const { provider } = controller.getProviderAndBlockTracker(); + assert(provider, 'Provider is somehow unset'); - await controller.initializeProvider(); + const promisifiedSendAsync1 = promisify(provider.sendAsync).bind( + provider, + ); + const response1 = await promisifiedSendAsync1({ + id: '1', + jsonrpc: '2.0', + method: 'test', + }); + expect(response1.result).toBe('test response 1'); - const { provider } = controller.getProviderAndBlockTracker(); - assert(provider, 'Provider is not set'); - const { result } = await promisify(provider.sendAsync).call( - provider, - { - id: 1, - jsonrpc: '2.0', - method: 'test_method', - params: [], - }, - ); - expect(result).toBe('test response'); - }, - ); - }); + await controller.setActiveNetwork('testNetworkConfigurationId'); + const promisifiedSendAsync2 = promisify(provider.sendAsync).bind( + provider, + ); + const response2 = await promisifiedSendAsync2({ + id: '2', + jsonrpc: '2.0', + method: 'test', + }); + expect(response2.result).toBe('test response 2'); + }, + ); }); + }); + }); - describe('if the provider config does not point to or match a network configuration', () => { - describe('if the provider config has a chain ID and RPC URL', () => { - it('creates a network client for a custom RPC endpoint using the provider config', async () => { - await withController( - { - state: { - providerConfig: { - type: NetworkType.rpc, - chainId: toHex(1337), - rpcUrl: 'http://example.com', - ticker: 'TEST', - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'test_method', - params: [], - }, - response: { - result: 'test response', - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); + describe('findNetworkConfigurationByChainId', () => { + it('returns the network configuration for the given chainId', async () => { + await withController( + { infuraProjectId: 'some-infura-project-id' }, + async ({ controller }) => { + const fakeNetworkClient = buildFakeClient(); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.initializeProvider(); + const networkClientId = + controller.findNetworkClientIdByChainId('0x1'); + expect(networkClientId).toBe('mainnet'); + }, + ); + }); - expect(createNetworkClientMock).toHaveBeenCalledWith({ - chainId: toHex(1337), - rpcUrl: 'http://example.com', - type: NetworkClientType.Custom, - ticker: 'TEST', - }); - expect(createNetworkClientMock).toHaveBeenCalledTimes(1); - }, - ); - }); + it('throws if the chainId doesnt exist in the configuration', async () => { + await withController( + { infuraProjectId: 'some-infura-project-id' }, + async ({ controller }) => { + const fakeNetworkClient = buildFakeClient(); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + expect(() => + controller.findNetworkClientIdByChainId('0xdeadbeef'), + ).toThrow("Couldn't find networkClientId for chainId"); + }, + ); + }); - it('captures the resulting provider of the new network client', async () => { - await withController( - { - state: { - providerConfig: { - type: NetworkType.rpc, - chainId: toHex(1337), - rpcUrl: 'http://example.com', - ticker: 'TEST', - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'test_method', - params: [], - }, - response: { - result: 'test response', - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - - await controller.initializeProvider(); + it('is callable from the controller messenger', async () => { + await withController( + { infuraProjectId: 'some-infura-project-id' }, + async ({ messenger }) => { + const fakeNetworkClient = buildFakeClient(); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - const { provider } = controller.getProviderAndBlockTracker(); - assert(provider, 'Provider is not set'); - const { result } = await promisify(provider.sendAsync).call( - provider, - { - id: 1, - jsonrpc: '2.0', - method: 'test_method', - params: [], - }, - ); - expect(result).toBe('test response'); - }, - ); - }); + const networkClientId = messenger.call( + 'NetworkController:findNetworkClientIdByChainId', + '0x1', + ); + expect(networkClientId).toBe('mainnet'); + }, + ); + }); + }); - lookupNetworkTests({ - expectedProviderConfig: buildProviderConfig({ - type: NetworkType.rpc, - }), - initialState: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - }), - }, - operation: async (controller: NetworkController) => { - await controller.initializeProvider(); - }, - }); - }); + describe('getNetworkClientById', () => { + describe('If passed an existing networkClientId', () => { + it('returns a valid built-in Infura NetworkClient', async () => { + await withController( + { infuraProjectId: 'some-infura-project-id' }, + async ({ controller }) => { + const fakeNetworkClient = buildFakeClient(); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - describe('if the chain ID is missing from the provider config', () => { - it('throws', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - chainId: undefined, - }), - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - - await expect(() => - controller.initializeProvider(), - ).rejects.toThrow( - 'chainId must be provided for custom RPC endpoints', - ); - }, + const networkClientRegistry = controller.getNetworkClientRegistry(); + const networkClient = controller.getNetworkClientById( + NetworkType.mainnet, ); - }); - it('does not create a network client or capture a provider', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - chainId: undefined, - }), - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); + expect(networkClient).toBe( + networkClientRegistry[NetworkType.mainnet], + ); + }, + ); + }); - try { - await controller.initializeProvider(); - } catch { - // ignore the error - } + it('returns a valid built-in Infura NetworkClient with a chainId in configuration', async () => { + await withController( + { infuraProjectId: 'some-infura-project-id' }, + async ({ controller }) => { + const fakeNetworkClient = buildFakeClient(); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - expect(createNetworkClientMock).not.toHaveBeenCalled(); - const { provider, blockTracker } = - controller.getProviderAndBlockTracker(); - expect(provider).toBeUndefined(); - expect(blockTracker).toBeUndefined(); - }, + const networkClientRegistry = controller.getNetworkClientRegistry(); + const networkClient = controller.getNetworkClientById( + NetworkType.mainnet, ); - }); - }); - describe('if the RPC URL is missing from the provider config', () => { - it('throws', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - rpcUrl: undefined, - }), - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - - await expect(() => - controller.initializeProvider(), - ).rejects.toThrow( - 'rpcUrl must be provided for custom RPC endpoints', - ); - }, + expect(networkClient.configuration.chainId).toBe('0x1'); + expect(networkClientRegistry.mainnet.configuration.chainId).toBe( + '0x1', ); - }); + }, + ); + }); - it('does not create a network client or capture a provider', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - rpcUrl: undefined, - }), + it('returns a valid custom NetworkClient', async () => { + await withController( + { + state: { + networkConfigurations: { + testNetworkConfigurationId: { + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', + ticker: 'ABC', + id: 'testNetworkConfigurationId', }, }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); + }, + infuraProjectId: 'some-infura-project-id', + }, + async ({ controller }) => { + const fakeNetworkClient = buildFakeClient(); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - try { - await controller.initializeProvider(); - } catch { - // ignore the error - } + const networkClientRegistry = controller.getNetworkClientRegistry(); + const networkClient = controller.getNetworkClientById( + 'testNetworkConfigurationId', + ); - expect(createNetworkClientMock).not.toHaveBeenCalled(); - const { provider, blockTracker } = - controller.getProviderAndBlockTracker(); - expect(provider).toBeUndefined(); - expect(blockTracker).toBeUndefined(); - }, + expect(networkClient).toBe( + networkClientRegistry.testNetworkConfigurationId, ); - }); - }); + }, + ); }); }); - }); - - describe('getProviderAndBlockTracker', () => { - it('returns objects that proxy to the provider and block tracker as long as the provider has been initialized', async () => { - await withController(async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.initializeProvider(); - const { provider, blockTracker } = - controller.getProviderAndBlockTracker(); + describe('If passed a networkClientId that does not match a NetworkClient in the registry', () => { + it('throws an error', async () => { + await withController( + { infuraProjectId: 'some-infura-project-id' }, + async ({ controller }) => { + const fakeNetworkClient = buildFakeClient(); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - expect(provider).toHaveProperty('sendAsync'); - expect(blockTracker).toHaveProperty('checkForLatestBlock'); + expect(() => + controller.getNetworkClientById('non-existent-network-id'), + ).toThrow( + 'No custom network client was found with the ID "non-existent-network-id', + ); + }, + ); }); }); - it("returns undefined for both the provider and block tracker if the provider hasn't been initialized yet", async () => { - await withController(async ({ controller }) => { - const { provider, blockTracker } = - controller.getProviderAndBlockTracker(); + describe('If not passed a networkClientId', () => { + it('throws an error', async () => { + await withController( + { infuraProjectId: 'some-infura-project-id' }, + async ({ controller }) => { + const fakeNetworkClient = buildFakeClient(); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - expect(provider).toBeUndefined(); - expect(blockTracker).toBeUndefined(); + expect(() => + // @ts-expect-error Intentionally passing invalid type + controller.getNetworkClientById(), + ).toThrow('No network client ID was provided.'); + }, + ); }); }); + }); - for (const { networkType } of INFURA_NETWORKS) { - // TODO: Either fix this lint violation or explain why it's necessary to ignore. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - describe(`when the type in the provider configuration is changed to "${networkType}"`, () => { - // TODO: Either fix this lint violation or explain why it's necessary to ignore. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - it(`returns a provider object that was pointed to another network before the switch and is pointed to "${networkType}" afterward`, async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - ticker: 'TEST', - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'test', - }, - response: { - result: 'test response 1', - }, - }, - ]), - buildFakeProvider([ - { - request: { - method: 'test', - }, - response: { - result: 'test response 2', - }, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - chainId: '0x1337', - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - ticker: 'TEST', - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - chainId: BUILT_IN_NETWORKS[networkType].chainId, - ticker: BUILT_IN_NETWORKS[networkType].ticker, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - const { provider } = controller.getProviderAndBlockTracker(); - assert(provider, 'Provider is somehow unset'); + describe('getNetworkClientRegistry', () => { + describe('if no network configurations are present in state', () => { + it('returns the built-in Infura networks by default', async () => { + await withController( + { infuraProjectId: 'some-infura-project-id' }, + async ({ controller }) => { + const fakeNetworkClient = buildFakeClient(); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - const promisifiedSendAsync1 = promisify(provider.sendAsync).bind( - provider, + const networkClients = controller.getNetworkClientRegistry(); + const simplifiedNetworkClients = Object.entries(networkClients) + .map( + ([networkClientId, networkClient]) => + [networkClientId, networkClient.configuration] as const, + ) + .sort( + ( + [networkClientId1, _networkClient1], + [networkClientId2, _networkClient2], + ) => { + return networkClientId1.localeCompare(networkClientId2); + }, ); - const response1 = await promisifiedSendAsync1({ - id: '1', - jsonrpc: '2.0', - method: 'test', - }); - expect(response1.result).toBe('test response 1'); - await controller.setProviderType(networkType); - const promisifiedSendAsync2 = promisify(provider.sendAsync).bind( - provider, - ); - const response2 = await promisifiedSendAsync2({ - id: '2', - jsonrpc: '2.0', - method: 'test', - }); - expect(response2.result).toBe('test response 2'); - }, - ); - }); + expect(simplifiedNetworkClients).toStrictEqual([ + [ + 'goerli', + { + type: NetworkClientType.Infura, + infuraProjectId: 'some-infura-project-id', + chainId: BUILT_IN_NETWORKS[NetworkType.goerli].chainId, + ticker: BUILT_IN_NETWORKS[NetworkType.goerli].ticker, + network: InfuraNetworkType.goerli, + }, + ], + [ + 'linea-goerli', + { + type: NetworkClientType.Infura, + infuraProjectId: 'some-infura-project-id', + chainId: + BUILT_IN_NETWORKS[NetworkType['linea-goerli']].chainId, + ticker: BUILT_IN_NETWORKS[NetworkType['linea-goerli']].ticker, + network: InfuraNetworkType['linea-goerli'], + }, + ], + [ + 'linea-mainnet', + { + type: NetworkClientType.Infura, + infuraProjectId: 'some-infura-project-id', + chainId: + BUILT_IN_NETWORKS[NetworkType['linea-mainnet']].chainId, + ticker: + BUILT_IN_NETWORKS[NetworkType['linea-mainnet']].ticker, + network: InfuraNetworkType['linea-mainnet'], + }, + ], + [ + 'linea-sepolia', + { + type: NetworkClientType.Infura, + infuraProjectId: 'some-infura-project-id', + chainId: + BUILT_IN_NETWORKS[NetworkType['linea-sepolia']].chainId, + ticker: + BUILT_IN_NETWORKS[NetworkType['linea-sepolia']].ticker, + network: InfuraNetworkType['linea-sepolia'], + }, + ], + [ + 'mainnet', + { + type: NetworkClientType.Infura, + infuraProjectId: 'some-infura-project-id', + chainId: BUILT_IN_NETWORKS[NetworkType.mainnet].chainId, + ticker: BUILT_IN_NETWORKS[NetworkType.mainnet].ticker, + network: InfuraNetworkType.mainnet, + }, + ], + [ + 'sepolia', + { + type: NetworkClientType.Infura, + infuraProjectId: 'some-infura-project-id', + chainId: BUILT_IN_NETWORKS[NetworkType.sepolia].chainId, + ticker: BUILT_IN_NETWORKS[NetworkType.sepolia].ticker, + network: InfuraNetworkType.sepolia, + }, + ], + ]); + }, + ); }); - } + }); - describe('when the type in the provider configuration is changed to "rpc"', () => { - it('returns a provider object that was pointed to another network before the switch and is pointed to the new network', async () => { + describe('if network configurations are present in state', () => { + it('incorporates them into the list of network clients, using the network configuration ID for identification', async () => { await withController( { state: { - providerConfig: { - type: 'goerli', - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x9999999', - ticker: 'TEST', - }, networkConfigurations: { - testNetworkConfigurationId: { - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - ticker: 'ABC', - id: 'testNetworkConfigurationId', + 'AAAA-AAAA-AAAA-AAAA': { + id: 'AAAA-AAAA-AAAA-AAAA', + rpcUrl: 'https://test.network.1', + chainId: toHex(1), + ticker: 'TEST1', + }, + 'BBBB-BBBB-BBBB-BBBB': { + id: 'BBBB-BBBB-BBBB-BBBB', + rpcUrl: 'https://test.network.2', + chainId: toHex(2), + ticker: 'TEST2', }, }, }, infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ + const fakeNetworkClient = buildFakeClient(); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + + const networkClients = controller.getNetworkClientRegistry(); + const simplifiedNetworkClients = Object.entries(networkClients) + .map( + ([networkClientId, networkClient]) => + [networkClientId, networkClient.configuration] as const, + ) + .sort( + ( + [networkClientId1, _networkClient1], + [networkClientId2, _networkClient2], + ) => { + return networkClientId1.localeCompare(networkClientId2); + }, + ); + + expect(simplifiedNetworkClients).toStrictEqual([ + [ + 'AAAA-AAAA-AAAA-AAAA', { - request: { - method: 'test', - }, - response: { - result: 'test response 1', - }, + type: NetworkClientType.Custom, + ticker: 'TEST1', + chainId: toHex(1), + rpcUrl: 'https://test.network.1', }, - ]), - buildFakeProvider([ + ], + [ + 'BBBB-BBBB-BBBB-BBBB', { - request: { - method: 'test', - }, - response: { - result: 'test response 2', - }, + type: NetworkClientType.Custom, + ticker: 'TEST2', + chainId: toHex(2), + rpcUrl: 'https://test.network.2', }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - chainId: BUILT_IN_NETWORKS[NetworkType.goerli].chainId, - ticker: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].ticker, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - chainId: '0x1337', - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - ticker: 'ABC', - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - const { provider } = controller.getProviderAndBlockTracker(); - assert(provider, 'Provider is somehow unset'); - - const promisifiedSendAsync1 = promisify(provider.sendAsync).bind( - provider, - ); - const response1 = await promisifiedSendAsync1({ - id: '1', - jsonrpc: '2.0', - method: 'test', - }); - expect(response1.result).toBe('test response 1'); - - await controller.setActiveNetwork('testNetworkConfigurationId'); - const promisifiedSendAsync2 = promisify(provider.sendAsync).bind( - provider, - ); - const response2 = await promisifiedSendAsync2({ - id: '2', - jsonrpc: '2.0', - method: 'test', - }); - expect(response2.result).toBe('test response 2'); - }, - ); - }); - }); - }); - - describe('findNetworkConfigurationByChainId', () => { - it('returns the network configuration for the given chainId', async () => { - await withController( - { infuraProjectId: 'some-infura-project-id' }, - async ({ controller }) => { - const fakeNetworkClient = buildFakeClient(); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - const networkClientId = - controller.findNetworkClientIdByChainId('0x1'); - expect(networkClientId).toBe('mainnet'); - }, - ); - }); - - it('throws if the chainId doesnt exist in the configuration', async () => { - await withController( - { infuraProjectId: 'some-infura-project-id' }, - async ({ controller }) => { - const fakeNetworkClient = buildFakeClient(); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - expect(() => - controller.findNetworkClientIdByChainId('0xdeadbeef'), - ).toThrow("Couldn't find networkClientId for chainId"); - }, - ); - }); - - it('is callable from the controller messenger', async () => { - await withController( - { infuraProjectId: 'some-infura-project-id' }, - async ({ messenger }) => { - const fakeNetworkClient = buildFakeClient(); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - const networkClientId = messenger.call( - 'NetworkController:findNetworkClientIdByChainId', - '0x1', - ); - expect(networkClientId).toBe('mainnet'); - }, - ); - }); - }); - - describe('getNetworkClientById', () => { - describe('If passed an existing networkClientId', () => { - it('returns a valid built-in Infura NetworkClient', async () => { - await withController( - { infuraProjectId: 'some-infura-project-id' }, - async ({ controller }) => { - const fakeNetworkClient = buildFakeClient(); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - const networkClientRegistry = controller.getNetworkClientRegistry(); - const networkClient = controller.getNetworkClientById( - NetworkType.mainnet, - ); - - expect(networkClient).toBe( - networkClientRegistry[NetworkType.mainnet], - ); - }, - ); - }); - - it('returns a valid built-in Infura NetworkClient with a chainId in configuration', async () => { - await withController( - { infuraProjectId: 'some-infura-project-id' }, - async ({ controller }) => { - const fakeNetworkClient = buildFakeClient(); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - const networkClientRegistry = controller.getNetworkClientRegistry(); - const networkClient = controller.getNetworkClientById( - NetworkType.mainnet, - ); - - expect(networkClient.configuration.chainId).toBe('0x1'); - expect(networkClientRegistry.mainnet.configuration.chainId).toBe( - '0x1', - ); - }, - ); - }); - - it('returns a valid custom NetworkClient', async () => { - await withController( - { - state: { - networkConfigurations: { - testNetworkConfigurationId: { - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - ticker: 'ABC', - id: 'testNetworkConfigurationId', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeNetworkClient = buildFakeClient(); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - const networkClientRegistry = controller.getNetworkClientRegistry(); - const networkClient = controller.getNetworkClientById( - 'testNetworkConfigurationId', - ); - - expect(networkClient).toBe( - networkClientRegistry.testNetworkConfigurationId, - ); - }, - ); - }); - }); - - describe('If passed a networkClientId that does not match a NetworkClient in the registry', () => { - it('throws an error', async () => { - await withController( - { infuraProjectId: 'some-infura-project-id' }, - async ({ controller }) => { - const fakeNetworkClient = buildFakeClient(); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - expect(() => - controller.getNetworkClientById('non-existent-network-id'), - ).toThrow( - 'No custom network client was found with the ID "non-existent-network-id', - ); - }, - ); - }); - }); - - describe('If not passed a networkClientId', () => { - it('throws an error', async () => { - await withController( - { infuraProjectId: 'some-infura-project-id' }, - async ({ controller }) => { - const fakeNetworkClient = buildFakeClient(); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - expect(() => - // @ts-expect-error Intentionally passing invalid type - controller.getNetworkClientById(), - ).toThrow('No network client ID was provided.'); - }, - ); - }); - }); - }); - - describe('getNetworkClientRegistry', () => { - describe('if neither a provider config nor network configurations are present in state', () => { - it('returns the built-in Infura networks by default', async () => { - await withController( - { infuraProjectId: 'some-infura-project-id' }, - async ({ controller }) => { - const fakeNetworkClient = buildFakeClient(); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - const networkClients = controller.getNetworkClientRegistry(); - const simplifiedNetworkClients = Object.entries(networkClients) - .map( - ([networkClientId, networkClient]) => - [networkClientId, networkClient.configuration] as const, - ) - .sort( - ( - [networkClientId1, _networkClient1], - [networkClientId2, _networkClient2], - ) => { - return networkClientId1.localeCompare(networkClientId2); - }, - ); - - expect(simplifiedNetworkClients).toStrictEqual([ + ], [ 'goerli', { type: NetworkClientType.Infura, infuraProjectId: 'some-infura-project-id', chainId: BUILT_IN_NETWORKS[NetworkType.goerli].chainId, - ticker: BUILT_IN_NETWORKS[NetworkType.goerli].ticker, + ticker: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].ticker, network: InfuraNetworkType.goerli, }, ], @@ -1365,861 +1020,166 @@ describe('NetworkController', () => { }, ], ]); + for (const networkClient of Object.values(networkClients)) { + expect(networkClient.provider).toHaveProperty('sendAsync'); + expect(networkClient.blockTracker).toHaveProperty( + 'checkForLatestBlock', + ); + } }, ); }); }); + }); - describe('if network configurations are present in state', () => { - it('incorporates them into the list of network clients, using the network configuration ID for identification', async () => { + describe('lookupNetwork', () => { + describe('if a networkClientId param is passed', () => { + it('updates the network status', async () => { await withController( - { - state: { - networkConfigurations: { - 'AAAA-AAAA-AAAA-AAAA': { - id: 'AAAA-AAAA-AAAA-AAAA', - rpcUrl: 'https://test.network.1', - chainId: toHex(1), - ticker: 'TEST1', - }, - 'BBBB-BBBB-BBBB-BBBB': { - id: 'BBBB-BBBB-BBBB-BBBB', - rpcUrl: 'https://test.network.2', - chainId: toHex(2), - ticker: 'TEST2', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, + { infuraProjectId: 'some-infura-project-id' }, async ({ controller }) => { const fakeNetworkClient = buildFakeClient(); mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + await controller.lookupNetwork('mainnet'); - const networkClients = controller.getNetworkClientRegistry(); - const simplifiedNetworkClients = Object.entries(networkClients) - .map( - ([networkClientId, networkClient]) => - [networkClientId, networkClient.configuration] as const, - ) - .sort( - ( - [networkClientId1, _networkClient1], - [networkClientId2, _networkClient2], - ) => { - return networkClientId1.localeCompare(networkClientId2); - }, - ); + expect(controller.state.networksMetadata.mainnet.status).toBe( + 'available', + ); + }, + ); + }); + it('throws an error if the network is not found', async () => { + await withController( + { infuraProjectId: 'some-infura-project-id' }, + async ({ controller }) => { + await expect(() => + controller.lookupNetwork('non-existent-network-id'), + ).rejects.toThrow( + 'No custom network client was found with the ID "non-existent-network-id".', + ); + }, + ); + }); + }); - expect(simplifiedNetworkClients).toStrictEqual([ - [ - 'AAAA-AAAA-AAAA-AAAA', - { - type: NetworkClientType.Custom, - ticker: 'TEST1', - chainId: toHex(1), - rpcUrl: 'https://test.network.1', - }, - ], - [ - 'BBBB-BBBB-BBBB-BBBB', - { - type: NetworkClientType.Custom, - ticker: 'TEST2', - chainId: toHex(2), - rpcUrl: 'https://test.network.2', - }, - ], - [ - 'goerli', + [NetworkType.mainnet, NetworkType.goerli, NetworkType.sepolia].forEach( + (networkType) => { + // TODO: Either fix this lint violation or explain why it's necessary to ignore. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + describe(`when selectedNetworkClientId in state is "${networkType}"`, () => { + describe('if the network was switched after the eth_getBlockByNumber request started but before it completed', () => { + it('stores the network status of the second network, not the first', async () => { + await withController( { - type: NetworkClientType.Infura, + state: { + selectedNetworkClientId: networkType, + networkConfigurations: { + testNetworkConfigurationId: { + id: 'testNetworkConfigurationId', + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + ticker: 'ABC', + }, + }, + }, infuraProjectId: 'some-infura-project-id', - chainId: BUILT_IN_NETWORKS[NetworkType.goerli].chainId, - ticker: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].ticker, - network: InfuraNetworkType.goerli, - }, - ], - [ - 'linea-goerli', - { - type: NetworkClientType.Infura, - infuraProjectId: 'some-infura-project-id', - chainId: - BUILT_IN_NETWORKS[NetworkType['linea-goerli']].chainId, - ticker: BUILT_IN_NETWORKS[NetworkType['linea-goerli']].ticker, - network: InfuraNetworkType['linea-goerli'], - }, - ], - [ - 'linea-mainnet', - { - type: NetworkClientType.Infura, - infuraProjectId: 'some-infura-project-id', - chainId: - BUILT_IN_NETWORKS[NetworkType['linea-mainnet']].chainId, - ticker: - BUILT_IN_NETWORKS[NetworkType['linea-mainnet']].ticker, - network: InfuraNetworkType['linea-mainnet'], - }, - ], - [ - 'linea-sepolia', - { - type: NetworkClientType.Infura, - infuraProjectId: 'some-infura-project-id', - chainId: - BUILT_IN_NETWORKS[NetworkType['linea-sepolia']].chainId, - ticker: - BUILT_IN_NETWORKS[NetworkType['linea-sepolia']].ticker, - network: InfuraNetworkType['linea-sepolia'], - }, - ], - [ - 'mainnet', - { - type: NetworkClientType.Infura, - infuraProjectId: 'some-infura-project-id', - chainId: BUILT_IN_NETWORKS[NetworkType.mainnet].chainId, - ticker: BUILT_IN_NETWORKS[NetworkType.mainnet].ticker, - network: InfuraNetworkType.mainnet, - }, - ], - [ - 'sepolia', - { - type: NetworkClientType.Infura, - infuraProjectId: 'some-infura-project-id', - chainId: BUILT_IN_NETWORKS[NetworkType.sepolia].chainId, - ticker: BUILT_IN_NETWORKS[NetworkType.sepolia].ticker, - network: InfuraNetworkType.sepolia, }, - ], - ]); - for (const networkClient of Object.values(networkClients)) { - expect(networkClient.provider).toHaveProperty('sendAsync'); - expect(networkClient.blockTracker).toHaveProperty( - 'checkForLatestBlock', - ); - } - }, - ); - }); - }); + async ({ controller, messenger }) => { + const fakeProviders = [ + buildFakeProvider([ + // Called during provider initialization + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + }, + // Called via `lookupNetwork` directly + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + beforeCompleting: () => { + // TODO: Either fix this lint violation or explain why it's necessary to ignore. + // eslint-disable-next-line @typescript-eslint/no-floating-promises + controller.setActiveNetwork( + 'testNetworkConfigurationId', + ); + }, + }, + ]), + buildFakeProvider([ + // Called when switching networks + { + request: { + method: 'eth_getBlockByNumber', + }, + error: GENERIC_JSON_RPC_ERROR, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + network: networkType, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + chainId: BUILT_IN_NETWORKS[networkType].chainId, + ticker: BUILT_IN_NETWORKS[networkType].ticker, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + chainId: toHex(1337), + rpcUrl: 'https://mock-rpc-url', + type: NetworkClientType.Custom, + ticker: 'ABC', + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); + expect( + controller.state.networksMetadata[networkType].status, + ).toBe('available'); - describe('if a provider config representing a built-in network is present in state', () => { - it('does not incorporate the network into the list of network clients since it is already present', async () => { - await withController( - { - state: { - providerConfig: { - type: NetworkType.mainnet, - chainId: ChainId.mainnet, - ticker: 'TEST', - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeNetworkClient = buildFakeClient(); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + await waitForStateChanges({ + messenger, + propertyPath: [ + 'networksMetadata', + 'testNetworkConfigurationId', + 'status', + ], + operation: async () => { + await controller.lookupNetwork(); + }, + }); - const networkClients = controller.getNetworkClientRegistry(); - const simplifiedNetworkClients = Object.entries(networkClients) - .map( - ([networkClientId, networkClient]) => - [networkClientId, networkClient.configuration] as const, - ) - .sort( - ( - [networkClientId1, _networkClient1], - [networkClientId2, _networkClient2], - ) => { - return networkClientId1.localeCompare(networkClientId2); + expect( + controller.state.networksMetadata[ + controller.state.selectedNetworkClientId + ].status, + ).toBe('unknown'); }, ); + }); - expect(simplifiedNetworkClients).toStrictEqual([ - [ - 'goerli', - { - type: NetworkClientType.Infura, - infuraProjectId: 'some-infura-project-id', - chainId: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].chainId, - ticker: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].ticker, - network: InfuraNetworkType.goerli, - }, - ], - [ - 'linea-goerli', - { - type: NetworkClientType.Infura, - infuraProjectId: 'some-infura-project-id', - chainId: - BUILT_IN_NETWORKS[InfuraNetworkType['linea-goerli']] - .chainId, - ticker: - BUILT_IN_NETWORKS[InfuraNetworkType['linea-goerli']].ticker, - network: InfuraNetworkType['linea-goerli'], - }, - ], - [ - 'linea-mainnet', - { - type: NetworkClientType.Infura, - infuraProjectId: 'some-infura-project-id', - chainId: - BUILT_IN_NETWORKS[InfuraNetworkType['linea-mainnet']] - .chainId, - ticker: - BUILT_IN_NETWORKS[InfuraNetworkType['linea-mainnet']] - .ticker, - network: InfuraNetworkType['linea-mainnet'], - }, - ], - [ - 'linea-sepolia', - { - type: NetworkClientType.Infura, - infuraProjectId: 'some-infura-project-id', - chainId: - BUILT_IN_NETWORKS[InfuraNetworkType['linea-sepolia']] - .chainId, - ticker: - BUILT_IN_NETWORKS[InfuraNetworkType['linea-sepolia']] - .ticker, - network: InfuraNetworkType['linea-sepolia'], - }, - ], - [ - 'mainnet', - { - type: NetworkClientType.Infura, - infuraProjectId: 'some-infura-project-id', - chainId: BUILT_IN_NETWORKS[InfuraNetworkType.mainnet].chainId, - ticker: BUILT_IN_NETWORKS[InfuraNetworkType.mainnet].ticker, - network: InfuraNetworkType.mainnet, - }, - ], - [ - 'sepolia', + it('stores the EIP-1559 support of the second network, not the first', async () => { + await withController( { - type: NetworkClientType.Infura, - infuraProjectId: 'some-infura-project-id', - chainId: BUILT_IN_NETWORKS[InfuraNetworkType.sepolia].chainId, - ticker: BUILT_IN_NETWORKS[InfuraNetworkType.sepolia].ticker, - network: InfuraNetworkType.sepolia, - }, - ], - ]); - }, - ); - }); - }); - - describe('if a provider config representing a custom network is present in state', () => { - describe('if it does not point to a network configuration', () => { - describe("if it does not match an existing network configuration's RPC URL", () => { - it('incorporates the network into the list of network clients, using the chain ID and lowercased RPC URL for identification', async () => { - await withController( - { - state: { - providerConfig: { - type: NetworkType.rpc, - chainId: toHex(2), - rpcUrl: 'HTTPS://TEST.NETWORK.2', - ticker: 'TEST', - }, - networkConfigurations: { - 'AAAA-AAAA-AAAA-AAAA': { - id: 'AAAA-AAAA-AAAA-AAAA', - rpcUrl: 'https://test.network.1', - chainId: toHex(1), - ticker: 'TEST', + state: { + selectedNetworkClientId: networkType, + networkConfigurations: { + testNetworkConfigurationId: { + id: 'testNetworkConfigurationId', + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + ticker: 'ABC', + }, }, }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeNetworkClient = buildFakeClient(); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - const networkClients = controller.getNetworkClientRegistry(); - const simplifiedNetworkClients = Object.entries(networkClients) - .map( - ([networkClientId, networkClient]) => - [networkClientId, networkClient.configuration] as const, - ) - .sort( - ( - [networkClientId1, _networkClient1], - [networkClientId2, _networkClient2], - ) => { - return networkClientId1.localeCompare(networkClientId2); - }, - ); - - expect(simplifiedNetworkClients).toStrictEqual([ - [ - 'AAAA-AAAA-AAAA-AAAA', - { - type: NetworkClientType.Custom, - ticker: 'TEST', - chainId: toHex(1), - rpcUrl: 'https://test.network.1', - }, - ], - [ - 'goerli', - { - type: NetworkClientType.Infura, - infuraProjectId: 'some-infura-project-id', - chainId: - BUILT_IN_NETWORKS[InfuraNetworkType.goerli].chainId, - ticker: - BUILT_IN_NETWORKS[InfuraNetworkType.goerli].ticker, - network: InfuraNetworkType.goerli, - }, - ], - [ - 'https://test.network.2', - { - type: NetworkClientType.Custom, - ticker: 'TEST', - chainId: toHex(2), - rpcUrl: 'HTTPS://TEST.NETWORK.2', - }, - ], - [ - 'linea-goerli', - { - type: NetworkClientType.Infura, - infuraProjectId: 'some-infura-project-id', - chainId: - BUILT_IN_NETWORKS[InfuraNetworkType['linea-goerli']] - .chainId, - ticker: - BUILT_IN_NETWORKS[InfuraNetworkType['linea-goerli']] - .ticker, - network: InfuraNetworkType['linea-goerli'], - }, - ], - [ - 'linea-mainnet', - { - type: NetworkClientType.Infura, - infuraProjectId: 'some-infura-project-id', - chainId: - BUILT_IN_NETWORKS[InfuraNetworkType['linea-mainnet']] - .chainId, - ticker: - BUILT_IN_NETWORKS[InfuraNetworkType['linea-mainnet']] - .ticker, - network: InfuraNetworkType['linea-mainnet'], - }, - ], - [ - 'linea-sepolia', - { - type: NetworkClientType.Infura, - infuraProjectId: 'some-infura-project-id', - chainId: - BUILT_IN_NETWORKS[InfuraNetworkType['linea-sepolia']] - .chainId, - ticker: - BUILT_IN_NETWORKS[InfuraNetworkType['linea-sepolia']] - .ticker, - network: InfuraNetworkType['linea-sepolia'], - }, - ], - [ - 'mainnet', - { - type: NetworkClientType.Infura, - infuraProjectId: 'some-infura-project-id', - chainId: - BUILT_IN_NETWORKS[InfuraNetworkType.mainnet].chainId, - ticker: - BUILT_IN_NETWORKS[InfuraNetworkType.mainnet].ticker, - network: InfuraNetworkType.mainnet, - }, - ], - [ - 'sepolia', - { - type: NetworkClientType.Infura, - infuraProjectId: 'some-infura-project-id', - chainId: - BUILT_IN_NETWORKS[InfuraNetworkType.sepolia].chainId, - ticker: - BUILT_IN_NETWORKS[InfuraNetworkType.sepolia].ticker, - network: InfuraNetworkType.sepolia, - }, - ], - ]); - }, - ); - }); - }); - - describe("if it matches an existing network configuration's RPC URL exactly", () => { - it('does not incorporate the network into the list of network clients again, prioritizing the network configuration instead', async () => { - await withController( - { - state: { - providerConfig: { - type: NetworkType.rpc, - chainId: toHex(2), - rpcUrl: 'https://test.network', - ticker: 'TEST', - }, - networkConfigurations: { - 'AAAA-AAAA-AAAA-AAAA': { - id: 'AAAA-AAAA-AAAA-AAAA', - rpcUrl: 'https://test.network', - chainId: toHex(1), - ticker: 'TEST', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeNetworkClient = buildFakeClient(); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - const networkClients = controller.getNetworkClientRegistry(); - const simplifiedNetworkClients = Object.entries(networkClients) - .map( - ([networkClientId, networkClient]) => - [networkClientId, networkClient.configuration] as const, - ) - .sort( - ( - [networkClientId1, _networkClient1], - [networkClientId2, _networkClient2], - ) => { - return networkClientId1.localeCompare(networkClientId2); - }, - ); - - expect(simplifiedNetworkClients).toStrictEqual([ - [ - 'AAAA-AAAA-AAAA-AAAA', - { - type: NetworkClientType.Custom, - ticker: 'TEST', - chainId: '0x1', - rpcUrl: 'https://test.network', - }, - ], - [ - 'goerli', - { - type: NetworkClientType.Infura, - infuraProjectId: 'some-infura-project-id', - chainId: - BUILT_IN_NETWORKS[InfuraNetworkType.goerli].chainId, - ticker: - BUILT_IN_NETWORKS[InfuraNetworkType.goerli].ticker, - network: InfuraNetworkType.goerli, - }, - ], - [ - 'linea-goerli', - { - type: NetworkClientType.Infura, - infuraProjectId: 'some-infura-project-id', - chainId: - BUILT_IN_NETWORKS[InfuraNetworkType['linea-goerli']] - .chainId, - ticker: - BUILT_IN_NETWORKS[InfuraNetworkType['linea-goerli']] - .ticker, - network: InfuraNetworkType['linea-goerli'], - }, - ], - [ - 'linea-mainnet', - { - type: NetworkClientType.Infura, - infuraProjectId: 'some-infura-project-id', - chainId: - BUILT_IN_NETWORKS[InfuraNetworkType['linea-mainnet']] - .chainId, - ticker: - BUILT_IN_NETWORKS[InfuraNetworkType['linea-mainnet']] - .ticker, - network: InfuraNetworkType['linea-mainnet'], - }, - ], - [ - 'linea-sepolia', - { - type: NetworkClientType.Infura, - infuraProjectId: 'some-infura-project-id', - chainId: - BUILT_IN_NETWORKS[InfuraNetworkType['linea-sepolia']] - .chainId, - ticker: - BUILT_IN_NETWORKS[InfuraNetworkType['linea-sepolia']] - .ticker, - network: InfuraNetworkType['linea-sepolia'], - }, - ], - [ - 'mainnet', - { - type: NetworkClientType.Infura, - chainId: - BUILT_IN_NETWORKS[InfuraNetworkType.mainnet].chainId, - ticker: - BUILT_IN_NETWORKS[InfuraNetworkType.mainnet].ticker, - infuraProjectId: 'some-infura-project-id', - network: InfuraNetworkType.mainnet, - }, - ], - [ - 'sepolia', - { - type: NetworkClientType.Infura, - infuraProjectId: 'some-infura-project-id', - chainId: - BUILT_IN_NETWORKS[InfuraNetworkType.sepolia].chainId, - ticker: - BUILT_IN_NETWORKS[InfuraNetworkType.sepolia].ticker, - network: InfuraNetworkType.sepolia, - }, - ], - ]); - }, - ); - }); - }); - - describe("if it matches an existing network configuration's RPC URL case-insensitively", () => { - it('does not incorporate the network into the list of network clients again, prioritizing the network configuration instead', async () => { - await withController( - { - state: { - providerConfig: { - type: NetworkType.rpc, - chainId: toHex(2), - rpcUrl: 'https://TEST.NETWORK', - ticker: 'TEST', - }, - networkConfigurations: { - 'AAAA-AAAA-AAAA-AAAA': { - id: 'AAAA-AAAA-AAAA-AAAA', - rpcUrl: 'https://test.network', - chainId: toHex(1), - ticker: 'TEST', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeNetworkClient = buildFakeClient(); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - const networkClients = controller.getNetworkClientRegistry(); - const simplifiedNetworkClients = Object.entries(networkClients) - .map( - ([networkClientId, networkClient]) => - [networkClientId, networkClient.configuration] as const, - ) - .sort( - ( - [networkClientId1, _networkClient1], - [networkClientId2, _networkClient2], - ) => { - return networkClientId1.localeCompare(networkClientId2); - }, - ); - - expect(simplifiedNetworkClients).toStrictEqual([ - [ - 'AAAA-AAAA-AAAA-AAAA', - { - type: NetworkClientType.Custom, - ticker: 'TEST', - chainId: toHex(1), - rpcUrl: 'https://test.network', - }, - ], - [ - 'goerli', - { - type: NetworkClientType.Infura, - infuraProjectId: 'some-infura-project-id', - chainId: - BUILT_IN_NETWORKS[InfuraNetworkType.goerli].chainId, - ticker: - BUILT_IN_NETWORKS[InfuraNetworkType.goerli].ticker, - network: InfuraNetworkType.goerli, - }, - ], - [ - 'linea-goerli', - { - type: NetworkClientType.Infura, - infuraProjectId: 'some-infura-project-id', - chainId: - BUILT_IN_NETWORKS[InfuraNetworkType['linea-goerli']] - .chainId, - ticker: - BUILT_IN_NETWORKS[InfuraNetworkType['linea-goerli']] - .ticker, - network: InfuraNetworkType['linea-goerli'], - }, - ], - [ - 'linea-mainnet', - { - type: NetworkClientType.Infura, - infuraProjectId: 'some-infura-project-id', - chainId: - BUILT_IN_NETWORKS[InfuraNetworkType['linea-mainnet']] - .chainId, - ticker: - BUILT_IN_NETWORKS[InfuraNetworkType['linea-mainnet']] - .ticker, - network: InfuraNetworkType['linea-mainnet'], - }, - ], - [ - 'linea-sepolia', - { - type: NetworkClientType.Infura, - infuraProjectId: 'some-infura-project-id', - chainId: - BUILT_IN_NETWORKS[InfuraNetworkType['linea-sepolia']] - .chainId, - ticker: - BUILT_IN_NETWORKS[InfuraNetworkType['linea-sepolia']] - .ticker, - network: InfuraNetworkType['linea-sepolia'], - }, - ], - [ - 'mainnet', - { - type: NetworkClientType.Infura, - chainId: - BUILT_IN_NETWORKS[InfuraNetworkType.mainnet].chainId, - ticker: - BUILT_IN_NETWORKS[InfuraNetworkType.mainnet].ticker, - infuraProjectId: 'some-infura-project-id', - network: InfuraNetworkType.mainnet, - }, - ], - [ - 'sepolia', - { - type: NetworkClientType.Infura, - infuraProjectId: 'some-infura-project-id', - chainId: - BUILT_IN_NETWORKS[InfuraNetworkType.sepolia].chainId, - ticker: - BUILT_IN_NETWORKS[InfuraNetworkType.sepolia].ticker, - network: InfuraNetworkType.sepolia, - }, - ], - ]); - }, - ); - }); - }); - }); - - describe('if it points to a network configuration', () => { - it('does not incorporate the network into the list of network clients again, prioritizing the network configuration', async () => { - await withController( - { - state: { - providerConfig: { - type: NetworkType.rpc, - chainId: toHex(2), - rpcUrl: 'https://test.network.2', - id: 'AAAA-AAAA-AAAA-AAAA', - ticker: 'TEST', - }, - networkConfigurations: { - 'AAAA-AAAA-AAAA-AAAA': { - id: 'AAAA-AAAA-AAAA-AAAA', - rpcUrl: 'https://test.network.1', - chainId: toHex(1), - ticker: 'TEST', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeNetworkClient = buildFakeClient(); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - const networkClients = controller.getNetworkClientRegistry(); - const simplifiedNetworkClients = Object.entries(networkClients) - .map( - ([networkClientId, networkClient]) => - [networkClientId, networkClient.configuration] as const, - ) - .sort( - ( - [networkClientId1, _networkClient1], - [networkClientId2, _networkClient2], - ) => { - return networkClientId1.localeCompare(networkClientId2); - }, - ); - - expect(simplifiedNetworkClients).toStrictEqual([ - [ - 'AAAA-AAAA-AAAA-AAAA', - { - type: NetworkClientType.Custom, - ticker: 'TEST', - chainId: toHex(1), - rpcUrl: 'https://test.network.1', - }, - ], - [ - 'goerli', - { - type: NetworkClientType.Infura, - infuraProjectId: 'some-infura-project-id', - chainId: - BUILT_IN_NETWORKS[InfuraNetworkType.goerli].chainId, - ticker: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].ticker, - network: InfuraNetworkType.goerli, - }, - ], - [ - 'linea-goerli', - { - type: NetworkClientType.Infura, - infuraProjectId: 'some-infura-project-id', - chainId: - BUILT_IN_NETWORKS[InfuraNetworkType['linea-goerli']] - .chainId, - ticker: - BUILT_IN_NETWORKS[InfuraNetworkType['linea-goerli']] - .ticker, - network: InfuraNetworkType['linea-goerli'], - }, - ], - [ - 'linea-mainnet', - { - type: NetworkClientType.Infura, - infuraProjectId: 'some-infura-project-id', - chainId: - BUILT_IN_NETWORKS[InfuraNetworkType['linea-mainnet']] - .chainId, - ticker: - BUILT_IN_NETWORKS[InfuraNetworkType['linea-mainnet']] - .ticker, - network: InfuraNetworkType['linea-mainnet'], - }, - ], - [ - 'linea-sepolia', - { - type: NetworkClientType.Infura, - infuraProjectId: 'some-infura-project-id', - chainId: - BUILT_IN_NETWORKS[InfuraNetworkType['linea-sepolia']] - .chainId, - ticker: - BUILT_IN_NETWORKS[InfuraNetworkType['linea-sepolia']] - .ticker, - network: InfuraNetworkType['linea-sepolia'], - }, - ], - [ - 'mainnet', - { - type: NetworkClientType.Infura, - chainId: - BUILT_IN_NETWORKS[InfuraNetworkType.mainnet].chainId, - ticker: BUILT_IN_NETWORKS[InfuraNetworkType.mainnet].ticker, - infuraProjectId: 'some-infura-project-id', - network: InfuraNetworkType.mainnet, - }, - ], - [ - 'sepolia', - { - type: NetworkClientType.Infura, - infuraProjectId: 'some-infura-project-id', - chainId: - BUILT_IN_NETWORKS[InfuraNetworkType.sepolia].chainId, - ticker: BUILT_IN_NETWORKS[InfuraNetworkType.sepolia].ticker, - network: InfuraNetworkType.sepolia, - }, - ], - ]); - }, - ); - }); - }); - }); - }); - - describe('lookupNetwork', () => { - describe('if a networkClientId param is passed', () => { - it('updates the network status', async () => { - await withController( - { infuraProjectId: 'some-infura-project-id' }, - async ({ controller }) => { - const fakeNetworkClient = buildFakeClient(); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.lookupNetwork('mainnet'); - - expect(controller.state.networksMetadata.mainnet.status).toBe( - 'available', - ); - }, - ); - }); - it('throws an error if the network is not found', async () => { - await withController( - { infuraProjectId: 'some-infura-project-id' }, - async ({ controller }) => { - await expect(() => - controller.lookupNetwork('non-existent-network-id'), - ).rejects.toThrow( - 'No custom network client was found with the ID "non-existent-network-id".', - ); - }, - ); - }); - }); - - [NetworkType.mainnet, NetworkType.goerli, NetworkType.sepolia].forEach( - (networkType) => { - // TODO: Either fix this lint violation or explain why it's necessary to ignore. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - describe(`when the provider config in state contains a network type of "${networkType}"`, () => { - describe('if the network was switched after the eth_getBlockByNumber request started but before it completed', () => { - it('stores the network status of the second network, not the first', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ type: networkType }), - networkConfigurations: { - testNetworkConfigurationId: { - id: 'testNetworkConfigurationId', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'ABC', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', + infuraProjectId: 'some-infura-project-id', }, async ({ controller, messenger }) => { const fakeProviders = [ @@ -2229,14 +1189,18 @@ describe('NetworkController', () => { request: { method: 'eth_getBlockByNumber', }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + response: { + result: POST_1559_BLOCK, + }, }, // Called via `lookupNetwork` directly { request: { method: 'eth_getBlockByNumber', }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + response: { + result: POST_1559_BLOCK, + }, beforeCompleting: () => { // TODO: Either fix this lint violation or explain why it's necessary to ignore. // eslint-disable-next-line @typescript-eslint/no-floating-promises @@ -2252,7 +1216,9 @@ describe('NetworkController', () => { request: { method: 'eth_getBlockByNumber', }, - error: GENERIC_JSON_RPC_ERROR, + response: { + result: PRE_1559_BLOCK, + }, }, ]), ]; @@ -2278,118 +1244,15 @@ describe('NetworkController', () => { .mockReturnValue(fakeNetworkClients[1]); await controller.initializeProvider(); expect( - controller.state.networksMetadata[networkType].status, - ).toBe('available'); + controller.state.networksMetadata[networkType].EIPS[1559], + ).toBe(true); await waitForStateChanges({ messenger, propertyPath: [ 'networksMetadata', 'testNetworkConfigurationId', - 'status', - ], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect( - controller.state.networksMetadata[ - controller.state.selectedNetworkClientId - ].status, - ).toBe('unknown'); - }, - ); - }); - - it('stores the EIP-1559 support of the second network, not the first', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ type: networkType }), - networkConfigurations: { - testNetworkConfigurationId: { - id: 'testNetworkConfigurationId', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'ABC', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller, messenger }) => { - const fakeProviders = [ - buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - beforeCompleting: () => { - // TODO: Either fix this lint violation or explain why it's necessary to ignore. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - controller.setActiveNetwork( - 'testNetworkConfigurationId', - ); - }, - }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - chainId: BUILT_IN_NETWORKS[networkType].chainId, - ticker: BUILT_IN_NETWORKS[networkType].ticker, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - ticker: 'ABC', - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - expect( - controller.state.networksMetadata[networkType].EIPS[1559], - ).toBe(true); - - await waitForStateChanges({ - messenger, - propertyPath: [ - 'networksMetadata', - 'testNetworkConfigurationId', - 'EIPS', + 'EIPS', ], operation: async () => { await controller.lookupNetwork(); @@ -2408,7 +1271,7 @@ describe('NetworkController', () => { await withController( { state: { - providerConfig: buildProviderConfig({ type: networkType }), + selectedNetworkClientId: networkType, networkConfigurations: { testNetworkConfigurationId: { id: 'testNetworkConfigurationId', @@ -2512,9 +1375,10 @@ describe('NetworkController', () => { }); lookupNetworkTests({ - expectedProviderConfig: buildProviderConfig({ type: networkType }), + expectedNetworkClientConfiguration: + buildInfuraNetworkClientConfiguration(networkType), initialState: { - providerConfig: buildProviderConfig({ type: networkType }), + selectedNetworkClientId: networkType, }, operation: async (controller) => { await controller.lookupNetwork(); @@ -2524,17 +1388,21 @@ describe('NetworkController', () => { }, ); - describe(`when the provider config in state contains a network type of "rpc"`, () => { + describe('when selectedNetworkClientId in state is the ID of a network configuration', () => { describe('if the network was switched after the eth_getBlockByNumber request started but before it completed', () => { it('stores the network status of the second network, not the first', async () => { await withController( { state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - }), + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurations: { + 'AAAA-AAAA-AAAA-AAAA': { + id: 'AAAA-AAAA-AAAA-AAAA', + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + ticker: 'TEST', + }, + }, }, infuraProjectId: 'some-infura-project-id', }, @@ -2593,8 +1461,7 @@ describe('NetworkController', () => { .mockReturnValue(fakeNetworkClients[1]); await controller.initializeProvider(); expect( - controller.state.networksMetadata['https://mock-rpc-url'] - .status, + controller.state.networksMetadata['AAAA-AAAA-AAAA-AAAA'].status, ).toBe('available'); await waitForStateChanges({ @@ -2620,11 +1487,15 @@ describe('NetworkController', () => { await withController( { state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - }), + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurations: { + 'AAAA-AAAA-AAAA-AAAA': { + id: 'AAAA-AAAA-AAAA-AAAA', + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + ticker: 'TEST', + }, + }, }, infuraProjectId: 'some-infura-project-id', }, @@ -2689,7 +1560,7 @@ describe('NetworkController', () => { .mockReturnValue(fakeNetworkClients[1]); await controller.initializeProvider(); expect( - controller.state.networksMetadata['https://mock-rpc-url'] + controller.state.networksMetadata['AAAA-AAAA-AAAA-AAAA'] .EIPS[1559], ).toBe(true); @@ -2706,7 +1577,7 @@ describe('NetworkController', () => { .EIPS[1559], ).toBe(false); expect( - controller.state.networksMetadata['https://mock-rpc-url'] + controller.state.networksMetadata['AAAA-AAAA-AAAA-AAAA'] .EIPS[1559], ).toBe(true); }, @@ -2717,11 +1588,15 @@ describe('NetworkController', () => { await withController( { state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - }), + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurations: { + 'AAAA-AAAA-AAAA-AAAA': { + id: 'AAAA-AAAA-AAAA-AAAA', + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + ticker: 'TEST', + }, + }, }, infuraProjectId: 'some-infura-project-id', }, @@ -2810,9 +1685,18 @@ describe('NetworkController', () => { }); lookupNetworkTests({ - expectedProviderConfig: buildProviderConfig({ type: NetworkType.rpc }), + expectedNetworkClientConfiguration: + buildCustomNetworkClientConfiguration(), initialState: { - providerConfig: buildProviderConfig({ type: NetworkType.rpc }), + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurations: { + 'AAAA-AAAA-AAAA-AAAA': { + id: 'AAAA-AAAA-AAAA-AAAA', + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + ticker: 'TEST', + }, + }, }, operation: async (controller) => { await controller.lookupNetwork(); @@ -2822,19 +1706,13 @@ describe('NetworkController', () => { }); describe('setProviderType', () => { - for (const { - networkType, - chainId, - ticker, - blockExplorerUrl, - } of INFURA_NETWORKS) { + for (const { networkType } of INFURA_NETWORKS) { // TODO: Either fix this lint violation or explain why it's necessary to ignore. // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - describe(`given a network type of "${networkType}"`, () => { + describe(`given the Infura network "${networkType}"`, () => { refreshNetworkTests({ - expectedProviderConfig: buildProviderConfig({ - type: networkType, - }), + expectedNetworkClientConfiguration: + buildInfuraNetworkClientConfiguration(networkType), operation: async (controller) => { await controller.setProviderType(networkType); }, @@ -2843,18 +1721,17 @@ describe('NetworkController', () => { // TODO: Either fix this lint violation or explain why it's necessary to ignore. // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - it(`overwrites the provider configuration using a predetermined chainId, ticker, and blockExplorerUrl for "${networkType}", clearing id, rpcUrl, and nickname`, async () => { + it(`sets selectedNetworkClientId in state to the Infura network "${networkType}"`, async () => { await withController( { state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - nickname: 'test-chain', - ticker: 'TEST', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer.com', + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurations: { + 'AAAA-AAAA-AAAA-AAAA': { + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + ticker: 'TEST', + id: 'AAAA-AAAA-AAAA-AAAA', }, }, }, @@ -2866,48 +1743,18 @@ describe('NetworkController', () => { await controller.setProviderType(networkType); - expect(controller.state.providerConfig).toStrictEqual({ - type: networkType, - rpcUrl: undefined, - chainId, - ticker, - nickname: undefined, - rpcPrefs: { blockExplorerUrl }, - id: undefined, - }); + expect(controller.state.selectedNetworkClientId).toBe(networkType); }, ); }); - - // TODO: Either fix this lint violation or explain why it's necessary to ignore. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - it(`updates state.selectedNetworkClientId, setting it to ${networkType}`, async () => { - await withController({}, async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - await controller.setProviderType(networkType); - - expect(controller.state.selectedNetworkClientId).toStrictEqual( - networkType, - ); - }); - }); } - describe('given a network type of "rpc"', () => { + describe('given the ID of a network configuration', () => { it('throws because there is no way to switch to a custom RPC endpoint using this method', async () => { await withController( { state: { - providerConfig: { - type: NetworkType.rpc, - rpcUrl: 'http://somethingexisting.com', - chainId: toHex(99999), - ticker: 'something existing', - nickname: 'something existing', - }, + selectedNetworkClientId: 'mainnet', }, }, async ({ controller }) => { @@ -3009,15 +1856,13 @@ describe('NetworkController', () => { describe('setActiveNetwork', () => { refreshNetworkTests({ - expectedProviderConfig: { - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - nickname: 'something existing', - id: 'testNetworkConfigurationId', - rpcPrefs: undefined, - type: NetworkType.rpc, - }, + expectedNetworkClientConfiguration: buildCustomNetworkClientConfiguration( + { + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(111), + ticker: 'TEST', + }, + ), initialState: { networkConfigurations: { testNetworkConfigurationId: { @@ -3035,17 +1880,17 @@ describe('NetworkController', () => { }, }); - describe('if the given ID does not match a network configuration in networkConfigurations or a built-in network type', () => { + describe('if the given ID refers to no existing network clients (derived from known Infura networks and network configurations)', () => { it('throws', async () => { await withController( { state: { networkConfigurations: { - testNetworkConfigurationId: { + 'AAAA-AAAA-AAAA-AAAA': { rpcUrl: 'https://mock-rpc-url', chainId: toHex(111), ticker: 'TEST', - id: 'testNetworkConfigurationId', + id: 'AAAA-AAAA-AAAA-AAAA', }, }, }, @@ -3056,10 +1901,10 @@ describe('NetworkController', () => { mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); await expect(() => - controller.setActiveNetwork('invalidNetworkConfigurationId'), + controller.setActiveNetwork('invalidNetworkClientId'), ).rejects.toThrow( new Error( - 'networkConfigurationId invalidNetworkConfigurationId does not match a configured networkConfiguration or built-in network type', + "Custom network client not found with ID 'invalidNetworkClientId'", ), ); }, @@ -3067,36 +1912,18 @@ describe('NetworkController', () => { }); }); - describe('if the network config does not contain an RPC URL', () => { - it('throws', async () => { + describe('if the ID refers to a network client created for a network configuration', () => { + it('assigns selectedNetworkClientId in state to the ID', async () => { + const testNetworkClientId = 'AAAA-AAAA-AAAA-AAAA'; await withController( - // @ts-expect-error RPC URL intentionally omitted { state: { - providerConfig: { - type: NetworkType.rpc, - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - nickname: 'something existing', - rpcPrefs: undefined, - }, networkConfigurations: { - testNetworkConfigurationId1: { + [testNetworkClientId]: { rpcUrl: 'https://mock-rpc-url', chainId: toHex(111), ticker: 'TEST', - nickname: 'something existing', - id: 'testNetworkConfigurationId1', - rpcPrefs: undefined, - }, - testNetworkConfigurationId2: { - rpcUrl: undefined, - chainId: toHex(222), - ticker: 'something existing', - nickname: 'something existing', - id: 'testNetworkConfigurationId2', - rpcPrefs: undefined, + id: testNetworkClientId, }, }, }, @@ -3104,90 +1931,67 @@ describe('NetworkController', () => { async ({ controller }) => { const fakeProvider = buildFakeProvider(); const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); + mockCreateNetworkClient() + .calledWith({ + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(111), + type: NetworkClientType.Custom, + ticker: 'TEST', + }) + .mockReturnValue(fakeNetworkClient); - await expect(() => - controller.setActiveNetwork('testNetworkConfigurationId2'), - ).rejects.toThrow( - 'rpcUrl must be provided for custom RPC endpoints', - ); + await controller.setActiveNetwork(testNetworkClientId); - expect(createNetworkClientMock).not.toHaveBeenCalled(); - const { provider, blockTracker } = - controller.getProviderAndBlockTracker(); - expect(provider).toBeUndefined(); - expect(blockTracker).toBeUndefined(); + expect(controller.state.selectedNetworkClientId).toStrictEqual( + testNetworkClientId, + ); }, ); }); }); - describe('if the network config does not contain a chain ID', () => { - it('throws', async () => { - await withController( - // @ts-expect-error chain ID intentionally omitted - { - state: { - providerConfig: { - type: NetworkType.rpc, - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - nickname: 'something existing', - rpcPrefs: undefined, - }, - networkConfigurations: { - testNetworkConfigurationId1: { - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - nickname: 'something existing', - id: 'testNetworkConfigurationId1', - rpcPrefs: undefined, - }, - testNetworkConfigurationId2: { - rpcUrl: 'http://somethingexisting.com', - chainId: undefined, - ticker: 'something existing', - nickname: 'something existing', - id: 'testNetworkConfigurationId2', - rpcPrefs: undefined, - }, - }, - }, + for (const { networkType } of INFURA_NETWORKS) { + // This is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + describe(`if the ID refers to a network client created for the Infura network "${networkType}"`, () => { + refreshNetworkTests({ + expectedNetworkClientConfiguration: + buildInfuraNetworkClientConfiguration(networkType), + operation: async (controller) => { + await controller.setActiveNetwork(networkType); }, - async ({ controller }) => { + }); + + // This is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + it(`sets selectedNetworkClientId in state to "${networkType}"`, async () => { + await withController({}, async ({ controller }) => { const fakeProvider = buildFakeProvider(); const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await expect(() => - controller.setActiveNetwork('testNetworkConfigurationId2'), - ).rejects.toThrow( - 'chainId must be provided for custom RPC endpoints', - ); + await controller.setActiveNetwork(networkType); - expect(createNetworkClientMock).not.toHaveBeenCalled(); - const { provider, blockTracker } = - controller.getProviderAndBlockTracker(); - expect(provider).toBeUndefined(); - expect(blockTracker).toBeUndefined(); - }, - ); + expect(controller.state.selectedNetworkClientId).toStrictEqual( + networkType, + ); + }); + }); }); - }); + } - it('overwrites the provider configuration given a networkConfigurationId that matches a configured networkConfiguration', async () => { + it('is able to be called via messenger action', async () => { + const testNetworkClientId = 'testNetworkConfigurationId'; await withController( { state: { networkConfigurations: { - testNetworkConfigurationId: { + [testNetworkClientId]: { rpcUrl: 'https://mock-rpc-url', chainId: toHex(111), ticker: 'TEST', nickname: 'something existing', - id: 'testNetworkConfigurationId', + id: testNetworkClientId, rpcPrefs: { blockExplorerUrl: 'https://test-block-explorer-2.com', }, @@ -3195,7 +1999,7 @@ describe('NetworkController', () => { }, }, }, - async ({ controller }) => { + async ({ controller, messenger }) => { const fakeProvider = buildFakeProvider(); const fakeNetworkClient = buildFakeClient(fakeProvider); mockCreateNetworkClient() @@ -3207,180 +2011,18 @@ describe('NetworkController', () => { }) .mockReturnValue(fakeNetworkClient); - await controller.setActiveNetwork('testNetworkConfigurationId'); + await messenger.call( + 'NetworkController:setActiveNetwork', + testNetworkClientId, + ); - expect(controller.state.providerConfig).toStrictEqual({ - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - nickname: 'something existing', - id: 'testNetworkConfigurationId', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer-2.com', - }, - }); + expect(controller.state.selectedNetworkClientId).toStrictEqual( + testNetworkClientId, + ); }, ); }); - - it('updates state.selectedNetworkClientId setting it to the networkConfiguration.id', async () => { - const testNetworkClientId = 'testNetworkConfigurationId'; - await withController( - { - state: { - networkConfigurations: { - [testNetworkClientId]: { - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - nickname: 'something existing', - id: testNetworkClientId, - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer-2.com', - }, - }, - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - type: NetworkClientType.Custom, - ticker: 'TEST', - }) - .mockReturnValue(fakeNetworkClient); - - await controller.setActiveNetwork(testNetworkClientId); - - expect(controller.state.selectedNetworkClientId).toStrictEqual( - testNetworkClientId, - ); - }, - ); - }); - - for (const { - networkType, - chainId, - ticker, - blockExplorerUrl, - } of INFURA_NETWORKS) { - // TODO: Either fix this lint violation or explain why it's necessary to ignore. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - describe(`given a network type of "${networkType}"`, () => { - refreshNetworkTests({ - expectedProviderConfig: buildProviderConfig({ - type: networkType, - }), - operation: async (controller) => { - await controller.setActiveNetwork(networkType); - }, - }); - }); - - // TODO: Either fix this lint violation or explain why it's necessary to ignore. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - it(`overwrites the provider configuration using a predetermined chainId, ticker, and blockExplorerUrl for "${networkType}", clearing id, rpcUrl, and nickname`, async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - nickname: 'test-chain', - ticker: 'TEST', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer.com', - }, - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - await controller.setActiveNetwork(networkType); - - expect(controller.state.providerConfig).toStrictEqual({ - type: networkType, - rpcUrl: undefined, - chainId, - ticker, - nickname: undefined, - rpcPrefs: { blockExplorerUrl }, - id: undefined, - }); - }, - ); - }); - - // TODO: Either fix this lint violation or explain why it's necessary to ignore. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - it(`updates state.selectedNetworkClientId, setting it to ${networkType}`, async () => { - await withController({}, async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - await controller.setActiveNetwork(networkType); - - expect(controller.state.selectedNetworkClientId).toStrictEqual( - networkType, - ); - }); - }); - } - - it('is able to be called via messenger action', async () => { - const testNetworkClientId = 'testNetworkConfigurationId'; - await withController( - { - state: { - networkConfigurations: { - [testNetworkClientId]: { - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - nickname: 'something existing', - id: testNetworkClientId, - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer-2.com', - }, - }, - }, - }, - }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - type: NetworkClientType.Custom, - ticker: 'TEST', - }) - .mockReturnValue(fakeNetworkClient); - - await messenger.call( - 'NetworkController:setActiveNetwork', - testNetworkClientId, - ); - - expect(controller.state.selectedNetworkClientId).toStrictEqual( - testNetworkClientId, - ); - }, - ); - }); - }); + }); describe('getEIP1559Compatibility', () => { describe('if no provider has been set yet', () => { @@ -3758,11 +2400,12 @@ describe('NetworkController', () => { (networkType) => { // TODO: Either fix this lint violation or explain why it's necessary to ignore. // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - describe(`when the type in the provider configuration is "${networkType}"`, () => { + describe(`when selectedNetworkClientId in state is the Infura network "${networkType}"`, () => { refreshNetworkTests({ - expectedProviderConfig: buildProviderConfig({ type: networkType }), + expectedNetworkClientConfiguration: + buildInfuraNetworkClientConfiguration(networkType), initialState: { - providerConfig: buildProviderConfig({ type: networkType }), + selectedNetworkClientId: networkType, }, operation: async (controller) => { await controller.resetConnection(); @@ -3772,11 +2415,24 @@ describe('NetworkController', () => { }, ); - describe(`when the type in the provider configuration is "rpc"`, () => { + describe('when selectedNetworkClientId in state is the ID of a network configuration', () => { refreshNetworkTests({ - expectedProviderConfig: buildProviderConfig({ type: NetworkType.rpc }), + expectedNetworkClientConfiguration: + buildCustomNetworkClientConfiguration({ + rpcUrl: 'https://test.network.1', + chainId: toHex(1337), + ticker: 'TEST', + }), initialState: { - providerConfig: buildProviderConfig({ type: NetworkType.rpc }), + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurations: { + 'AAAA-AAAA-AAAA-AAAA': { + id: 'AAAA-AAAA-AAAA-AAAA', + rpcUrl: 'https://test.network.1', + chainId: toHex(1337), + ticker: 'TEST', + }, + }, }, operation: async (controller) => { await controller.resetConnection(); @@ -3785,33 +2441,6 @@ describe('NetworkController', () => { }); }); - describe('NetworkController:getProviderConfig action', () => { - it('returns the provider config in state', async () => { - await withController( - { - state: { - providerConfig: { - type: NetworkType.mainnet, - ...BUILT_IN_NETWORKS.mainnet, - }, - }, - }, - async ({ messenger }) => { - // TODO: Either fix this lint violation or explain why it's necessary to ignore. - // eslint-disable-next-line @typescript-eslint/await-thenable - const providerConfig = await messenger.call( - 'NetworkController:getProviderConfig', - ); - - expect(providerConfig).toStrictEqual({ - type: NetworkType.mainnet, - ...BUILT_IN_NETWORKS.mainnet, - }); - }, - ); - }); - }); - describe('NetworkController:getEthQuery action', () => { it('returns a EthQuery object that can be used to make requests to the currently selected network', async () => { await withController(async ({ controller, messenger }) => { @@ -4198,44 +2827,32 @@ describe('NetworkController', () => { }); describe('if the setActive option is not given', () => { - it('does not update the provider config to the new network configuration by default', async () => { - const originalProvider = { - type: NetworkType.rpc, - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TICKER', - id: 'testNetworkConfigurationId', - }; + it('does not update selectedNetworkClientId to refer to the new network configuration by default', async () => { + await withController(async ({ controller }) => { + const originalSelectedNetworkClientId = + controller.state.selectedNetworkClientId; - await withController( - { - state: { - providerConfig: originalProvider, - }, - }, - async ({ controller }) => { - uuidV4Mock.mockReturnValue('AAAA-AAAA-AAAA-AAAA'); + uuidV4Mock.mockReturnValue('AAAA-AAAA-AAAA-AAAA'); - await controller.upsertNetworkConfiguration( - { - rpcUrl: 'https://test.network', - chainId: toHex(111), - ticker: 'TICKER', - }, - { - referrer: 'https://test-dapp.com', - source: 'dapp', - }, - ); + await controller.upsertNetworkConfiguration( + { + rpcUrl: 'https://test.network', + chainId: toHex(111), + ticker: 'TICKER', + }, + { + referrer: 'https://test-dapp.com', + source: 'dapp', + }, + ); - expect(controller.state.providerConfig).toStrictEqual( - originalProvider, - ); - }, - ); + expect(controller.state.selectedNetworkClientId).toStrictEqual( + originalSelectedNetworkClientId, + ); + }); }); - it('does not set the new network to active by default', async () => { + it('does not re-point the provider and block tracker proxies to the new network by default', async () => { await withController( { infuraProjectId: 'some-infura-project-id' }, async ({ controller }) => { @@ -4312,45 +2929,33 @@ describe('NetworkController', () => { }); describe('if the setActive option is false', () => { - it('does not update the provider config to the new network configuration by default', async () => { - const originalProvider = { - type: NetworkType.rpc, - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TICKER', - id: 'testNetworkConfigurationId', - }; + it('does not update selectedNetworkClientId to refer to the new network configuration', async () => { + await withController(async ({ controller }) => { + const originalSelectedNetworkClientId = + controller.state.selectedNetworkClientId; - await withController( - { - state: { - providerConfig: originalProvider, - }, - }, - async ({ controller }) => { - uuidV4Mock.mockReturnValue('AAAA-AAAA-AAAA-AAAA'); + uuidV4Mock.mockReturnValue('AAAA-AAAA-AAAA-AAAA'); - await controller.upsertNetworkConfiguration( - { - rpcUrl: 'https://test.network', - chainId: toHex(111), - ticker: 'TICKER', - }, - { - setActive: false, - referrer: 'https://test-dapp.com', - source: 'dapp', - }, - ); + await controller.upsertNetworkConfiguration( + { + rpcUrl: 'https://test.network', + chainId: toHex(111), + ticker: 'TICKER', + }, + { + setActive: false, + referrer: 'https://test-dapp.com', + source: 'dapp', + }, + ); - expect(controller.state.providerConfig).toStrictEqual( - originalProvider, - ); - }, - ); + expect(controller.state.selectedNetworkClientId).toStrictEqual( + originalSelectedNetworkClientId, + ); + }); }); - it('does not set the new network to active by default', async () => { + it('does not re-point the provider and block tracker proxies to the new network', async () => { await withController( { infuraProjectId: 'some-infura-project-id' }, async ({ controller }) => { @@ -4428,7 +3033,7 @@ describe('NetworkController', () => { }); describe('if the setActive option is true', () => { - it('updates the provider config to the new network configuration', async () => { + it('updates selectedNetworkClientId to refer to the new network configuration', async () => { await withController(async ({ controller }) => { uuidV4Mock.mockReturnValue('AAAA-AAAA-AAAA-AAAA'); const newCustomNetworkClient = buildFakeClient(); @@ -4458,30 +3063,19 @@ describe('NetworkController', () => { }, ); - expect(controller.state.providerConfig).toStrictEqual({ - type: NetworkType.rpc, - rpcUrl: 'https://test.network', - chainId: toHex(111), - ticker: 'TICKER', - nickname: 'test network', - rpcPrefs: { - blockExplorerUrl: 'https://some.chainscan.io', - }, - id: 'AAAA-AAAA-AAAA-AAAA', - }); + expect(controller.state.selectedNetworkClientId).toBe( + 'AAAA-AAAA-AAAA-AAAA', + ); }); }); refreshNetworkTests({ - expectedProviderConfig: { - type: NetworkType.rpc, - rpcUrl: 'https://some.other.network', - chainId: toHex(222), - ticker: 'TICKER2', - id: 'BBBB-BBBB-BBBB-BBBB', - nickname: undefined, - rpcPrefs: undefined, - }, + expectedNetworkClientConfiguration: + buildCustomNetworkClientConfiguration({ + rpcUrl: 'https://some.other.network', + chainId: toHex(222), + ticker: 'TICKER2', + }), initialState: { networkConfigurations: { 'AAAA-AAAA-AAAA-AAAA': { @@ -5125,781 +3719,188 @@ describe('NetworkController', () => { }); }); - describe('removeNetworkConfiguration', () => { - describe('given an ID that identifies a network configuration in state', () => { - it('removes the network configuration from state', async () => { - await withController( - { - state: { - networkConfigurations: { - 'AAAA-AAAA-AAAA-AAAA': { - rpcUrl: 'https://test.network', - ticker: 'TICKER', - chainId: toHex(111), - id: 'AAAA-AAAA-AAAA-AAAA', - }, - }, - }, - }, - async ({ controller }) => { - controller.removeNetworkConfiguration('AAAA-AAAA-AAAA-AAAA'); - - expect(controller.state.networkConfigurations).toStrictEqual({}); - }, - ); - }); - - it('destroys and removes the network client in the network client registry that corresponds to the given ID', async () => { - await withController( - { - state: { - networkConfigurations: { - 'AAAA-AAAA-AAAA-AAAA': { - rpcUrl: 'https://test.network', - ticker: 'TICKER', - chainId: toHex(111), - id: 'AAAA-AAAA-AAAA-AAAA', - }, - }, - }, - }, - async ({ controller }) => { - mockCreateNetworkClientWithDefaultsForBuiltInNetworkClients() - .calledWith({ - chainId: toHex(111), - rpcUrl: 'https://test.network', - type: NetworkClientType.Custom, - ticker: 'TEST', - }) - .mockReturnValue(buildFakeClient()); - const networkClientToDestroy = Object.values( - controller.getNetworkClientRegistry(), - ).find(({ configuration }) => { - return ( - configuration.type === NetworkClientType.Custom && - configuration.chainId === toHex(111) && - configuration.rpcUrl === 'https://test.network' - ); - }); - assert(networkClientToDestroy); - jest.spyOn(networkClientToDestroy, 'destroy'); - - controller.removeNetworkConfiguration('AAAA-AAAA-AAAA-AAAA'); - - expect(networkClientToDestroy.destroy).toHaveBeenCalled(); - expect(controller.getNetworkClientRegistry()).not.toMatchObject({ - 'https://test.network': expect.objectContaining({ - configuration: { - chainId: toHex(111), - rpcUrl: 'https://test.network', - type: NetworkClientType.Custom, - ticker: 'TEST', - }, - }), - }); - }, - ); - }); - }); - - describe('given an ID that does not identify a network configuration in state', () => { - it('throws', async () => { - await withController(async ({ controller }) => { - expect(() => - controller.removeNetworkConfiguration('NONEXISTENT'), - ).toThrow( - `networkConfigurationId NONEXISTENT does not match a configured networkConfiguration`, - ); - }); - }); - - it('does not update the network client registry', async () => { - await withController(async ({ controller }) => { - mockCreateNetworkClientWithDefaultsForBuiltInNetworkClients(); - const networkClients = controller.getNetworkClientRegistry(); - - try { - controller.removeNetworkConfiguration('NONEXISTENT'); - } catch { - // ignore error (it is tested elsewhere) - } - - expect(controller.getNetworkClientRegistry()).toStrictEqual( - networkClients, - ); - }); - }); - }); - }); - - describe('rollbackToPreviousProvider', () => { - describe('if a provider has not been set', () => { - [NetworkType.mainnet, NetworkType.goerli, NetworkType.sepolia].forEach( - (networkType) => { - // TODO: Either fix this lint violation or explain why it's necessary to ignore. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - describe(`when the type in the provider configuration is "${networkType}"`, () => { - refreshNetworkTests({ - expectedProviderConfig: buildProviderConfig({ - type: networkType, - }), - initialState: { - providerConfig: buildProviderConfig({ type: networkType }), - }, - operation: async (controller) => { - await controller.rollbackToPreviousProvider(); - }, - }); - }); - }, - ); - - describe(`when the type in the provider configuration is "rpc"`, () => { - refreshNetworkTests({ - expectedProviderConfig: buildProviderConfig({ - type: NetworkType.rpc, - }), - initialState: { - providerConfig: buildProviderConfig({ type: NetworkType.rpc }), - }, - operation: async (controller) => { - await controller.rollbackToPreviousProvider(); - }, - }); - }); - }); - - describe('if a provider has been set', () => { - for (const { networkType } of INFURA_NETWORKS) { - // TODO: Either fix this lint violation or explain why it's necessary to ignore. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - describe(`if the previous provider configuration had a type of "${networkType}"`, () => { - it('emits networkWillChange with state payload', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: networkType, - }), - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - nickname: 'test network', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer.com', - }, - }, - }, - }, - }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.setActiveNetwork('testNetworkConfiguration'); - - const networkWillChange = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:networkWillChange', - filter: ([networkState]) => networkState === controller.state, - operation: () => { - // Intentionally not awaited because we're capturing an event - // emitted partway through the operation - // TODO: Either fix this lint violation or explain why it's necessary to ignore. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - controller.rollbackToPreviousProvider(); - }, - }); - - await expect(networkWillChange).toBeFulfilled(); - }, - ); - }); - - it('emits networkDidChange with state payload', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: networkType, - }), - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - nickname: 'test network', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer.com', - }, - }, - }, - }, - }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.setActiveNetwork('testNetworkConfiguration'); - - const networkDidChange = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:networkDidChange', - filter: ([networkState]) => networkState === controller.state, - operation: () => { - // Intentionally not awaited because we're capturing an event - // emitted partway through the operation - // TODO: Either fix this lint violation or explain why it's necessary to ignore. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - controller.rollbackToPreviousProvider(); - }, - }); - - await expect(networkDidChange).toBeFulfilled(); - }, - ); - }); - - it('overwrites the the current provider configuration with the previous provider configuration', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: networkType, - }), - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - nickname: 'test network', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer.com', - }, - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider(), - buildFakeProvider(), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - ticker: 'TEST', - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - chainId: BUILT_IN_NETWORKS[networkType].chainId, - ticker: BUILT_IN_NETWORKS[networkType].ticker, - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); - expect(controller.state.providerConfig).toStrictEqual({ - type: 'rpc', - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - nickname: 'test network', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer.com', - }, - }); - - await controller.rollbackToPreviousProvider(); - - expect(controller.state.providerConfig).toStrictEqual( - buildProviderConfig({ - type: networkType, - }), - ); - }, - ); - }); - - it('resets the network status to "unknown" before updating the provider', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: networkType, - }), - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller, messenger }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]), - buildFakeProvider(), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - ticker: 'TEST', - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - chainId: BUILT_IN_NETWORKS[networkType].chainId, - ticker: BUILT_IN_NETWORKS[networkType].ticker, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); - expect( - controller.state.networksMetadata[ - controller.state.selectedNetworkClientId - ].status, - ).toBe('available'); - - await waitForStateChanges({ - messenger, - propertyPath: ['networksMetadata', networkType, 'status'], - // We only care about the first state change, because it - // happens before networkDidChange - count: 1, - operation: () => { - // Intentionally not awaited because we want to check state - // while this operation is in-progress - // TODO: Either fix this lint violation or explain why it's necessary to ignore. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - controller.rollbackToPreviousProvider(); - }, - beforeResolving: () => { - expect( - controller.state.networksMetadata[ - controller.state.selectedNetworkClientId - ].status, - ).toBe('unknown'); - }, - }); - }, - ); - }); - - // TODO: Either fix this lint violation or explain why it's necessary to ignore. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - it(`initializes a provider pointed to the "${networkType}" Infura network`, async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: networkType, - }), - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider(), - buildFakeProvider([ - { - request: { - method: 'test', - }, - response: { - result: 'test response', - }, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - ticker: 'TEST', - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - chainId: BUILT_IN_NETWORKS[networkType].chainId, - ticker: BUILT_IN_NETWORKS[networkType].ticker, - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); - - await controller.rollbackToPreviousProvider(); - - const { provider } = controller.getProviderAndBlockTracker(); - assert(provider, 'Provider is somehow unset'); - const promisifiedSendAsync = promisify(provider.sendAsync).bind( - provider, - ); - const response = await promisifiedSendAsync({ - id: '1', - jsonrpc: '2.0', - method: 'test', - }); - expect(response.result).toBe('test response'); - }, - ); - }); - - it('replaces the provider object underlying the provider proxy without creating a new instance of the proxy itself', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: networkType, - }), - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - }, - }, + describe('removeNetworkConfiguration', () => { + describe('given an ID that identifies a network configuration in state', () => { + it('removes the network configuration from state', async () => { + await withController( + { + state: { + networkConfigurations: { + 'AAAA-AAAA-AAAA-AAAA': { + rpcUrl: 'https://test.network', + ticker: 'TICKER', + chainId: toHex(111), + id: 'AAAA-AAAA-AAAA-AAAA', }, - infuraProjectId: 'some-infura-project-id', }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider(), - buildFakeProvider(), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - ticker: 'TEST', - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - chainId: BUILT_IN_NETWORKS[networkType].chainId, - ticker: BUILT_IN_NETWORKS[networkType].ticker, - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); - const { provider: providerBefore } = - controller.getProviderAndBlockTracker(); + }, + }, + async ({ controller }) => { + controller.removeNetworkConfiguration('AAAA-AAAA-AAAA-AAAA'); - await controller.rollbackToPreviousProvider(); + expect(controller.state.networkConfigurations).toStrictEqual({}); + }, + ); + }); - const { provider: providerAfter } = - controller.getProviderAndBlockTracker(); - expect(providerBefore).toBe(providerAfter); + it('destroys and removes the network client in the network client registry that corresponds to the given ID', async () => { + await withController( + { + state: { + networkConfigurations: { + 'AAAA-AAAA-AAAA-AAAA': { + rpcUrl: 'https://test.network', + ticker: 'TICKER', + chainId: toHex(111), + id: 'AAAA-AAAA-AAAA-AAAA', + }, }, - ); - }); + }, + }, + async ({ controller }) => { + mockCreateNetworkClientWithDefaultsForBuiltInNetworkClients() + .calledWith({ + chainId: toHex(111), + rpcUrl: 'https://test.network', + type: NetworkClientType.Custom, + ticker: 'TEST', + }) + .mockReturnValue(buildFakeClient()); + const networkClientToDestroy = Object.values( + controller.getNetworkClientRegistry(), + ).find(({ configuration }) => { + return ( + configuration.type === NetworkClientType.Custom && + configuration.chainId === toHex(111) && + configuration.rpcUrl === 'https://test.network' + ); + }); + assert(networkClientToDestroy); + jest.spyOn(networkClientToDestroy, 'destroy'); - it('emits infuraIsBlocked or infuraIsUnblocked, depending on whether Infura is blocking requests for the previous network', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: networkType, - }), - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - }, - }, + controller.removeNetworkConfiguration('AAAA-AAAA-AAAA-AAAA'); + + expect(networkClientToDestroy.destroy).toHaveBeenCalled(); + expect(controller.getNetworkClientRegistry()).not.toMatchObject({ + 'https://test.network': expect.objectContaining({ + configuration: { + chainId: toHex(111), + rpcUrl: 'https://test.network', + type: NetworkClientType.Custom, + ticker: 'TEST', }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller, messenger }) => { - const fakeProviders = [ - buildFakeProvider(), - buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - ticker: 'TEST', - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - chainId: BUILT_IN_NETWORKS[networkType].chainId, - ticker: BUILT_IN_NETWORKS[networkType].ticker, - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); - const promiseForNoInfuraIsUnblockedEvents = - waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - count: 0, - }); - const promiseForInfuraIsBlocked = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - }); + }), + }); + }, + ); + }); + }); - await controller.rollbackToPreviousProvider(); + describe('given an ID that does not identify a network configuration in state', () => { + it('throws', async () => { + await withController(async ({ controller }) => { + expect(() => + controller.removeNetworkConfiguration('NONEXISTENT'), + ).toThrow( + `networkConfigurationId NONEXISTENT does not match a configured networkConfiguration`, + ); + }); + }); - await expect( - promiseForNoInfuraIsUnblockedEvents, - ).toBeFulfilled(); - await expect(promiseForInfuraIsBlocked).toBeFulfilled(); - }, - ); - }); + it('does not update the network client registry', async () => { + await withController(async ({ controller }) => { + mockCreateNetworkClientWithDefaultsForBuiltInNetworkClients(); + const networkClients = controller.getNetworkClientRegistry(); - it('checks the status of the previous network again and updates state accordingly', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: networkType, - }), - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller, messenger }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - error: rpcErrors.methodNotFound(), - }, - ]), - buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - ticker: 'TEST', - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - chainId: BUILT_IN_NETWORKS[networkType].chainId, - ticker: BUILT_IN_NETWORKS[networkType].ticker, - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); - expect( - controller.state.networksMetadata[ - controller.state.selectedNetworkClientId - ].status, - ).toBe('unavailable'); + try { + controller.removeNetworkConfiguration('NONEXISTENT'); + } catch { + // ignore error (it is tested elsewhere) + } - await waitForStateChanges({ - messenger, - propertyPath: ['networksMetadata', networkType, 'status'], - operation: async () => { - await controller.rollbackToPreviousProvider(); - }, - }); - expect( - controller.state.networksMetadata[ - controller.state.selectedNetworkClientId - ].status, - ).toBe('available'); - }, - ); - }); + expect(controller.getNetworkClientRegistry()).toStrictEqual( + networkClients, + ); + }); + }); + }); + }); - it('checks whether the previous network supports EIP-1559 again and updates state accordingly', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: networkType, - }), - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', + describe('rollbackToPreviousProvider', () => { + describe('when called not following any network switches', () => { + [NetworkType.mainnet, NetworkType.goerli, NetworkType.sepolia].forEach( + (networkType) => { + // TODO: Either fix this lint violation or explain why it's necessary to ignore. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + describe(`when selectedNetworkClientId in state is the Infura network "${networkType}"`, () => { + refreshNetworkTests({ + expectedNetworkClientConfiguration: + buildInfuraNetworkClientConfiguration(networkType), + initialState: { + selectedNetworkClientId: networkType, }, - async ({ controller, messenger }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - ]), - buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - ticker: 'TEST', - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - chainId: BUILT_IN_NETWORKS[networkType].chainId, - ticker: BUILT_IN_NETWORKS[networkType].ticker, - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); - expect( - controller.state.networksMetadata[ - controller.state.selectedNetworkClientId - ].EIPS[1559], - ).toBe(false); - - await waitForStateChanges({ - messenger, - propertyPath: ['networksMetadata', networkType, 'EIPS'], - count: 2, - operation: async () => { - await controller.rollbackToPreviousProvider(); - }, - }); - expect( - controller.state.networksMetadata[ - controller.state.selectedNetworkClientId - ].EIPS[1559], - ).toBe(true); + operation: async (controller) => { + await controller.rollbackToPreviousProvider(); }, - ); + }); }); + }, + ); + + describe('when selectedNetworkClientId in state is the ID of a network configuration', () => { + refreshNetworkTests({ + expectedNetworkClientConfiguration: + buildCustomNetworkClientConfiguration({ + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + ticker: 'TEST', + }), + initialState: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurations: { + 'AAAA-AAAA-AAAA-AAAA': { + id: 'AAAA-AAAA-AAAA-AAAA', + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + ticker: 'TEST', + }, + }, + }, + operation: async (controller) => { + await controller.rollbackToPreviousProvider(); + }, }); - } + }); + }); - describe(`if the previous provider configuration had a type of "rpc"`, () => { + for (const { networkType } of INFURA_NETWORKS) { + // TODO: Either fix this lint violation or explain why it's necessary to ignore. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + describe(`when called following a network switch away from the Infura network "${networkType}"`, () => { it('emits networkWillChange with state payload', async () => { await withController( { state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - }), + selectedNetworkClientId: networkType, + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + ticker: 'TEST', + nickname: 'test network', + rpcPrefs: { + blockExplorerUrl: 'https://test-block-explorer.com', + }, + }, + }, }, }, async ({ controller, messenger }) => { const fakeProvider = buildFakeProvider(); const fakeNetworkClient = buildFakeClient(fakeProvider); mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.setProviderType(InfuraNetworkType.goerli); + await controller.setActiveNetwork('testNetworkConfiguration'); const networkWillChange = waitForPublishedEvents({ messenger, @@ -5923,16 +3924,26 @@ describe('NetworkController', () => { await withController( { state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - }), + selectedNetworkClientId: networkType, + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + ticker: 'TEST', + nickname: 'test network', + rpcPrefs: { + blockExplorerUrl: 'https://test-block-explorer.com', + }, + }, + }, }, }, async ({ controller, messenger }) => { const fakeProvider = buildFakeProvider(); const fakeNetworkClient = buildFakeClient(fakeProvider); mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.setProviderType(InfuraNetworkType.goerli); + await controller.setActiveNetwork('testNetworkConfiguration'); const networkDidChange = waitForPublishedEvents({ messenger, @@ -5952,20 +3963,226 @@ describe('NetworkController', () => { ); }); - it('overwrites the the current provider configuration with the previous provider configuration', async () => { + it('sets selectedNetworkClientId in state to the previous version', async () => { await withController( { state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, + selectedNetworkClientId: networkType, + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + ticker: 'TEST', + nickname: 'test network', + rpcPrefs: { + blockExplorerUrl: 'https://test-block-explorer.com', + }, + }, + }, + }, + infuraProjectId: 'some-infura-project-id', + }, + async ({ controller }) => { + const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ rpcUrl: 'https://mock-rpc-url', chainId: toHex(1337), - nickname: 'network', + type: NetworkClientType.Custom, ticker: 'TEST', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer.com', + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + network: networkType, + infuraProjectId: 'some-infura-project-id', + chainId: BUILT_IN_NETWORKS[networkType].chainId, + ticker: BUILT_IN_NETWORKS[networkType].ticker, + type: NetworkClientType.Infura, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.setActiveNetwork('testNetworkConfiguration'); + expect(controller.state.selectedNetworkClientId).toBe( + 'testNetworkConfiguration', + ); + + await controller.rollbackToPreviousProvider(); + + expect(controller.state.selectedNetworkClientId).toBe( + networkType, + ); + }, + ); + }); + + it('resets the network status to "unknown" before updating the provider', async () => { + await withController( + { + state: { + selectedNetworkClientId: networkType, + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + ticker: 'TEST', + }, + }, + }, + infuraProjectId: 'some-infura-project-id', + }, + async ({ controller, messenger }) => { + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + }, + ]), + buildFakeProvider(), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + type: NetworkClientType.Custom, + ticker: 'TEST', + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + network: networkType, + chainId: BUILT_IN_NETWORKS[networkType].chainId, + ticker: BUILT_IN_NETWORKS[networkType].ticker, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.setActiveNetwork('testNetworkConfiguration'); + expect( + controller.state.networksMetadata[ + controller.state.selectedNetworkClientId + ].status, + ).toBe('available'); + + await waitForStateChanges({ + messenger, + propertyPath: ['networksMetadata', networkType, 'status'], + // We only care about the first state change, because it + // happens before networkDidChange + count: 1, + operation: () => { + // Intentionally not awaited because we want to check state + // while this operation is in-progress + // TODO: Either fix this lint violation or explain why it's necessary to ignore. + // eslint-disable-next-line @typescript-eslint/no-floating-promises + controller.rollbackToPreviousProvider(); + }, + beforeResolving: () => { + expect( + controller.state.networksMetadata[ + controller.state.selectedNetworkClientId + ].status, + ).toBe('unknown'); + }, + }); + }, + ); + }); + + // This is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + it(`initializes a provider pointed to the "${networkType}" Infura network`, async () => { + await withController( + { + state: { + selectedNetworkClientId: networkType, + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + ticker: 'TEST', + }, + }, + }, + infuraProjectId: 'some-infura-project-id', + }, + async ({ controller }) => { + const fakeProviders = [ + buildFakeProvider(), + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response', + }, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + type: NetworkClientType.Custom, + ticker: 'TEST', + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + network: networkType, + infuraProjectId: 'some-infura-project-id', + chainId: BUILT_IN_NETWORKS[networkType].chainId, + ticker: BUILT_IN_NETWORKS[networkType].ticker, + type: NetworkClientType.Infura, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.setActiveNetwork('testNetworkConfiguration'); + + await controller.rollbackToPreviousProvider(); + + const { provider } = controller.getProviderAndBlockTracker(); + assert(provider, 'Provider is somehow unset'); + const promisifiedSendAsync = promisify(provider.sendAsync).bind( + provider, + ); + const response = await promisifiedSendAsync({ + id: '1', + jsonrpc: '2.0', + method: 'test', + }); + expect(response.result).toBe('test response'); + }, + ); + }); + + it('replaces the provider object underlying the provider proxy without creating a new instance of the proxy itself', async () => { + await withController( + { + state: { + selectedNetworkClientId: networkType, + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + ticker: 'TEST', }, - }), + }, }, infuraProjectId: 'some-infura-project-id', }, @@ -5976,65 +4193,128 @@ describe('NetworkController', () => { buildFakeClient(fakeProviders[1]), ]; mockCreateNetworkClient() - .calledWith({ - network: InfuraNetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - chainId: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].chainId, - ticker: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].ticker, - }) - .mockReturnValue(fakeNetworkClients[0]) .calledWith({ rpcUrl: 'https://mock-rpc-url', chainId: toHex(1337), type: NetworkClientType.Custom, ticker: 'TEST', }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + network: networkType, + infuraProjectId: 'some-infura-project-id', + chainId: BUILT_IN_NETWORKS[networkType].chainId, + ticker: BUILT_IN_NETWORKS[networkType].ticker, + type: NetworkClientType.Infura, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.setActiveNetwork('testNetworkConfiguration'); + const { provider: providerBefore } = + controller.getProviderAndBlockTracker(); + + await controller.rollbackToPreviousProvider(); + + const { provider: providerAfter } = + controller.getProviderAndBlockTracker(); + expect(providerBefore).toBe(providerAfter); + }, + ); + }); + + it('emits infuraIsBlocked or infuraIsUnblocked, depending on whether Infura is blocking requests for the previous network', async () => { + await withController( + { + state: { + selectedNetworkClientId: networkType, + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + ticker: 'TEST', + }, + }, + }, + infuraProjectId: 'some-infura-project-id', + }, + async ({ controller, messenger }) => { + const fakeProviders = [ + buildFakeProvider(), + buildFakeProvider([ + { + request: { + method: 'eth_getBlockByNumber', + }, + error: BLOCKED_INFURA_JSON_RPC_ERROR, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + type: NetworkClientType.Custom, + ticker: 'TEST', + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + network: networkType, + infuraProjectId: 'some-infura-project-id', + chainId: BUILT_IN_NETWORKS[networkType].chainId, + ticker: BUILT_IN_NETWORKS[networkType].ticker, + type: NetworkClientType.Infura, + }) .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); - expect(controller.state.providerConfig).toStrictEqual({ - type: 'goerli', - rpcUrl: undefined, - chainId: toHex(5), - ticker: 'GoerliETH', - nickname: undefined, - rpcPrefs: { - blockExplorerUrl: 'https://goerli.etherscan.io', - }, - id: undefined, + await controller.setActiveNetwork('testNetworkConfiguration'); + const promiseForNoInfuraIsUnblockedEvents = + waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsUnblocked', + count: 0, + }); + const promiseForInfuraIsBlocked = waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsBlocked', }); await controller.rollbackToPreviousProvider(); - expect(controller.state.providerConfig).toStrictEqual( - buildProviderConfig({ - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - nickname: 'network', - ticker: 'TEST', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer.com', - }, - }), - ); + + await expect(promiseForNoInfuraIsUnblockedEvents).toBeFulfilled(); + await expect(promiseForInfuraIsBlocked).toBeFulfilled(); }, ); }); - it('resets the network state to "unknown" before updating the provider', async () => { + it('checks the status of the previous network again and updates state accordingly', async () => { await withController( { state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - }), + selectedNetworkClientId: networkType, + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + ticker: 'TEST', + }, + }, }, infuraProjectId: 'some-infura-project-id', }, async ({ controller, messenger }) => { const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'eth_getBlockByNumber', + }, + error: rpcErrors.methodNotFound(), + }, + ]), buildFakeProvider([ { request: { @@ -6043,86 +4323,85 @@ describe('NetworkController', () => { response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, }, ]), - buildFakeProvider(), ]; const fakeNetworkClients = [ buildFakeClient(fakeProviders[0]), buildFakeClient(fakeProviders[1]), ]; mockCreateNetworkClient() - .calledWith({ - network: InfuraNetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - chainId: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].chainId, - ticker: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].ticker, - }) - .mockReturnValue(fakeNetworkClients[0]) .calledWith({ rpcUrl: 'https://mock-rpc-url', chainId: toHex(1337), type: NetworkClientType.Custom, ticker: 'TEST', }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + network: networkType, + infuraProjectId: 'some-infura-project-id', + chainId: BUILT_IN_NETWORKS[networkType].chainId, + ticker: BUILT_IN_NETWORKS[networkType].ticker, + type: NetworkClientType.Infura, + }) .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); + await controller.setActiveNetwork('testNetworkConfiguration'); expect( controller.state.networksMetadata[ controller.state.selectedNetworkClientId ].status, - ).toBe('available'); + ).toBe('unavailable'); await waitForStateChanges({ messenger, - propertyPath: [ - 'networksMetadata', - 'https://mock-rpc-url', - 'status', - ], - // We only care about the first state change, because it - // happens before networkDidChange - count: 1, - operation: () => { - // Intentionally not awaited because we want to check state - // while this operation is in-progress - // TODO: Either fix this lint violation or explain why it's necessary to ignore. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - controller.rollbackToPreviousProvider(); - }, - beforeResolving: () => { - expect( - controller.state.networksMetadata[ - controller.state.selectedNetworkClientId - ].status, - ).toBe('unknown'); + propertyPath: ['networksMetadata', networkType, 'status'], + operation: async () => { + await controller.rollbackToPreviousProvider(); }, }); + expect( + controller.state.networksMetadata[ + controller.state.selectedNetworkClientId + ].status, + ).toBe('available'); }, ); }); - it('initializes a provider pointed to the given RPC URL', async () => { + it('checks whether the previous network supports EIP-1559 again and updates state accordingly', async () => { await withController( { state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - }), + selectedNetworkClientId: networkType, + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + ticker: 'TEST', + }, + }, }, infuraProjectId: 'some-infura-project-id', }, - async ({ controller }) => { + async ({ controller, messenger }) => { const fakeProviders = [ - buildFakeProvider(), buildFakeProvider([ { request: { - method: 'test', + method: 'eth_getBlockByNumber', }, response: { - result: 'test response', + result: PRE_1559_BLOCK, + }, + }, + ]), + buildFakeProvider([ + { + request: { + method: 'eth_getBlockByNumber', + }, + response: { + result: POST_1559_BLOCK, }, }, ]), @@ -6133,274 +4412,574 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ - network: InfuraNetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - chainId: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].chainId, - ticker: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].ticker, + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + type: NetworkClientType.Custom, + ticker: 'TEST', }) .mockReturnValue(fakeNetworkClients[0]) .calledWith({ + network: networkType, + infuraProjectId: 'some-infura-project-id', + chainId: BUILT_IN_NETWORKS[networkType].chainId, + ticker: BUILT_IN_NETWORKS[networkType].ticker, + type: NetworkClientType.Infura, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.setActiveNetwork('testNetworkConfiguration'); + expect( + controller.state.networksMetadata[ + controller.state.selectedNetworkClientId + ].EIPS[1559], + ).toBe(false); + + await waitForStateChanges({ + messenger, + propertyPath: ['networksMetadata', networkType, 'EIPS'], + count: 2, + operation: async () => { + await controller.rollbackToPreviousProvider(); + }, + }); + expect( + controller.state.networksMetadata[ + controller.state.selectedNetworkClientId + ].EIPS[1559], + ).toBe(true); + }, + ); + }); + }); + } + + describe('when called following a network switch away from a network configuration', () => { + it('emits networkWillChange with state payload', async () => { + await withController( + { + state: { + selectedNetworkClientId: 'testNetworkConfiguration', + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + ticker: 'TEST', + }, + }, + }, + }, + async ({ controller, messenger }) => { + const fakeProvider = buildFakeProvider(); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + await controller.setProviderType(InfuraNetworkType.goerli); + + const networkWillChange = waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:networkWillChange', + filter: ([networkState]) => networkState === controller.state, + operation: () => { + // Intentionally not awaited because we're capturing an event + // emitted partway through the operation + // eslint-disable-next-line @typescript-eslint/no-floating-promises + controller.rollbackToPreviousProvider(); + }, + }); + + await expect(networkWillChange).toBeFulfilled(); + }, + ); + }); + + it('emits networkDidChange with state payload', async () => { + await withController( + { + state: { + selectedNetworkClientId: 'testNetworkConfiguration', + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + ticker: 'TEST', + }, + }, + }, + }, + async ({ controller, messenger }) => { + const fakeProvider = buildFakeProvider(); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + await controller.setProviderType(InfuraNetworkType.goerli); + + const networkDidChange = waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:networkDidChange', + filter: ([networkState]) => networkState === controller.state, + operation: () => { + // Intentionally not awaited because we're capturing an event + // emitted partway through the operation + // eslint-disable-next-line @typescript-eslint/no-floating-promises + controller.rollbackToPreviousProvider(); + }, + }); + + await expect(networkDidChange).toBeFulfilled(); + }, + ); + }); + + it('sets selectedNetworkClientId to the previous version', async () => { + await withController( + { + state: { + selectedNetworkClientId: 'testNetworkConfiguration', + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + ticker: 'TEST', + }, + }, + }, + infuraProjectId: 'some-infura-project-id', + }, + async ({ controller }) => { + const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + network: InfuraNetworkType.goerli, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + chainId: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].chainId, + ticker: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].ticker, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + type: NetworkClientType.Custom, + ticker: 'TEST', + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.setProviderType('goerli'); + expect(controller.state.selectedNetworkClientId).toBe('goerli'); + + await controller.rollbackToPreviousProvider(); + expect(controller.state.selectedNetworkClientId).toBe( + 'testNetworkConfiguration', + ); + }, + ); + }); + + it('resets the network state to "unknown" before updating the provider', async () => { + await withController( + { + state: { + selectedNetworkClientId: 'testNetworkConfiguration', + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', rpcUrl: 'https://mock-rpc-url', chainId: toHex(1337), - type: NetworkClientType.Custom, ticker: 'TEST', - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); - - await controller.rollbackToPreviousProvider(); - - const { provider } = controller.getProviderAndBlockTracker(); - assert(provider, 'Provider is somehow unset'); - const promisifiedSendAsync = promisify(provider.sendAsync).bind( - provider, - ); - const response = await promisifiedSendAsync({ - id: '1', - jsonrpc: '2.0', - method: 'test', - }); - expect(response.result).toBe('test response'); + }, + }, }, - ); - }); + infuraProjectId: 'some-infura-project-id', + }, + async ({ controller, messenger }) => { + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + }, + ]), + buildFakeProvider(), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + network: InfuraNetworkType.goerli, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + chainId: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].chainId, + ticker: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].ticker, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + type: NetworkClientType.Custom, + ticker: 'TEST', + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.setProviderType('goerli'); + expect( + controller.state.networksMetadata[ + controller.state.selectedNetworkClientId + ].status, + ).toBe('available'); - it('replaces the provider object underlying the provider proxy without creating a new instance of the proxy itself', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - }), + await waitForStateChanges({ + messenger, + propertyPath: [ + 'networksMetadata', + 'testNetworkConfiguration', + 'status', + ], + // We only care about the first state change, because it + // happens before networkDidChange + count: 1, + operation: () => { + // Intentionally not awaited because we want to check state + // while this operation is in-progress + // eslint-disable-next-line @typescript-eslint/no-floating-promises + controller.rollbackToPreviousProvider(); }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: InfuraNetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - chainId: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].chainId, - ticker: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].ticker, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ + beforeResolving: () => { + expect( + controller.state.networksMetadata[ + controller.state.selectedNetworkClientId + ].status, + ).toBe('unknown'); + }, + }); + }, + ); + }); + + it('initializes a provider pointed to the given RPC URL', async () => { + await withController( + { + state: { + selectedNetworkClientId: 'testNetworkConfiguration', + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', rpcUrl: 'https://mock-rpc-url', chainId: toHex(1337), - type: NetworkClientType.Custom, ticker: 'TEST', - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); - const { provider: providerBefore } = - controller.getProviderAndBlockTracker(); + }, + }, + }, + infuraProjectId: 'some-infura-project-id', + }, + async ({ controller }) => { + const fakeProviders = [ + buildFakeProvider(), + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response', + }, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + network: InfuraNetworkType.goerli, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + chainId: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].chainId, + ticker: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].ticker, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + type: NetworkClientType.Custom, + ticker: 'TEST', + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.setProviderType('goerli'); - await controller.rollbackToPreviousProvider(); + await controller.rollbackToPreviousProvider(); - const { provider: providerAfter } = - controller.getProviderAndBlockTracker(); - expect(providerBefore).toBe(providerAfter); - }, - ); - }); + const { provider } = controller.getProviderAndBlockTracker(); + assert(provider, 'Provider is somehow unset'); + const promisifiedSendAsync = promisify(provider.sendAsync).bind( + provider, + ); + const response = await promisifiedSendAsync({ + id: '1', + jsonrpc: '2.0', + method: 'test', + }); + expect(response.result).toBe('test response'); + }, + ); + }); - it('emits infuraIsUnblocked', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, + it('replaces the provider object underlying the provider proxy without creating a new instance of the proxy itself', async () => { + await withController( + { + state: { + selectedNetworkClientId: 'testNetworkConfiguration', + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', rpcUrl: 'https://mock-rpc-url', chainId: toHex(1337), - }), + ticker: 'TEST', + }, }, - infuraProjectId: 'some-infura-project-id', }, - async ({ controller, messenger }) => { - const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: InfuraNetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - chainId: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].chainId, - ticker: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].ticker, - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ + infuraProjectId: 'some-infura-project-id', + }, + async ({ controller }) => { + const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + network: InfuraNetworkType.goerli, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + chainId: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].chainId, + ticker: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].ticker, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + type: NetworkClientType.Custom, + ticker: 'TEST', + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.setProviderType('goerli'); + const { provider: providerBefore } = + controller.getProviderAndBlockTracker(); + + await controller.rollbackToPreviousProvider(); + + const { provider: providerAfter } = + controller.getProviderAndBlockTracker(); + expect(providerBefore).toBe(providerAfter); + }, + ); + }); + + it('emits infuraIsUnblocked', async () => { + await withController( + { + state: { + selectedNetworkClientId: 'testNetworkConfiguration', + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', rpcUrl: 'https://mock-rpc-url', chainId: toHex(1337), - type: NetworkClientType.Custom, ticker: 'TEST', - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); - - const promiseForInfuraIsUnblocked = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - operation: async () => { - await controller.rollbackToPreviousProvider(); }, - }); - - await expect(promiseForInfuraIsUnblocked).toBeFulfilled(); + }, }, - ); - }); + infuraProjectId: 'some-infura-project-id', + }, + async ({ controller, messenger }) => { + const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + network: InfuraNetworkType.goerli, + infuraProjectId: 'some-infura-project-id', + chainId: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].chainId, + ticker: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].ticker, + type: NetworkClientType.Infura, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + type: NetworkClientType.Custom, + ticker: 'TEST', + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.setProviderType('goerli'); + + const promiseForInfuraIsUnblocked = waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsUnblocked', + operation: async () => { + await controller.rollbackToPreviousProvider(); + }, + }); - it('checks the status of the previous network again and updates state accordingly', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, + await expect(promiseForInfuraIsUnblocked).toBeFulfilled(); + }, + ); + }); + + it('checks the status of the previous network again and updates state accordingly', async () => { + await withController( + { + state: { + selectedNetworkClientId: 'testNetworkConfiguration', + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', rpcUrl: 'https://mock-rpc-url', chainId: toHex(1337), - }), + ticker: 'TEST', + }, }, - infuraProjectId: 'some-infura-project-id', }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - error: rpcErrors.methodNotFound(), + infuraProjectId: 'some-infura-project-id', + }, + async ({ controller }) => { + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'eth_getBlockByNumber', }, - ]), - buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + error: rpcErrors.methodNotFound(), + }, + ]), + buildFakeProvider([ + { + request: { + method: 'eth_getBlockByNumber', }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: InfuraNetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - chainId: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].chainId, - ticker: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].ticker, - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - ticker: 'TEST', - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); - expect( - controller.state.networksMetadata[ - controller.state.selectedNetworkClientId - ].status, - ).toBe('unavailable'); + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + network: InfuraNetworkType.goerli, + infuraProjectId: 'some-infura-project-id', + chainId: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].chainId, + ticker: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].ticker, + type: NetworkClientType.Infura, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + type: NetworkClientType.Custom, + ticker: 'TEST', + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.setProviderType('goerli'); + expect( + controller.state.networksMetadata[ + controller.state.selectedNetworkClientId + ].status, + ).toBe('unavailable'); - await controller.rollbackToPreviousProvider(); - expect( - controller.state.networksMetadata[ - controller.state.selectedNetworkClientId - ].status, - ).toBe('available'); - }, - ); - }); + await controller.rollbackToPreviousProvider(); + expect( + controller.state.networksMetadata[ + controller.state.selectedNetworkClientId + ].status, + ).toBe('available'); + }, + ); + }); - it('checks whether the previous network supports EIP-1559 again and updates state accordingly', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, + it('checks whether the previous network supports EIP-1559 again and updates state accordingly', async () => { + await withController( + { + state: { + selectedNetworkClientId: 'testNetworkConfiguration', + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', rpcUrl: 'https://mock-rpc-url', chainId: toHex(1337), - }), + ticker: 'TEST', + }, }, - infuraProjectId: 'some-infura-project-id', }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: PRE_1559_BLOCK, - }, + infuraProjectId: 'some-infura-project-id', + }, + async ({ controller }) => { + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'eth_getBlockByNumber', }, - ]), - buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, + response: { + result: PRE_1559_BLOCK, }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: InfuraNetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - chainId: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].chainId, - ticker: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].ticker, - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - ticker: 'TEST', - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); - expect( - controller.state.networksMetadata[ - controller.state.selectedNetworkClientId - ].EIPS[1559], - ).toBe(false); + }, + ]), + buildFakeProvider([ + { + request: { + method: 'eth_getBlockByNumber', + }, + response: { + result: POST_1559_BLOCK, + }, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + network: InfuraNetworkType.goerli, + infuraProjectId: 'some-infura-project-id', + chainId: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].chainId, + ticker: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].ticker, + type: NetworkClientType.Infura, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + type: NetworkClientType.Custom, + ticker: 'TEST', + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.setProviderType('goerli'); + expect( + controller.state.networksMetadata[ + controller.state.selectedNetworkClientId + ].EIPS[1559], + ).toBe(false); - await controller.rollbackToPreviousProvider(); - expect( - controller.state.networksMetadata[ - controller.state.selectedNetworkClientId - ].EIPS[1559], - ).toBe(true); - }, - ); - }); + await controller.rollbackToPreviousProvider(); + expect( + controller.state.networksMetadata[ + controller.state.selectedNetworkClientId + ].EIPS[1559], + ).toBe(true); + }, + ); }); }); }); @@ -6525,17 +5104,17 @@ function mockCreateNetworkClientWithDefaultsForBuiltInNetworkClients({ * covered by these tests. * * @param args - Arguments. - * @param args.expectedProviderConfig - The provider configuration that the - * operation is expected to set. + * @param args.expectedNetworkClientConfiguration - The network client + * configuration that the operation is expected to set. * @param args.initialState - The initial state of the network controller. * @param args.operation - The operation to test. */ function refreshNetworkTests({ - expectedProviderConfig, + expectedNetworkClientConfiguration, initialState, operation, }: { - expectedProviderConfig: ProviderConfig; + expectedNetworkClientConfiguration: NetworkClientConfiguration; initialState?: Partial; operation: (controller: NetworkController) => Promise; }) { @@ -6595,7 +5174,7 @@ function refreshNetworkTests({ ); }); - if (expectedProviderConfig.type === NetworkType.rpc) { + if (expectedNetworkClientConfiguration.type === NetworkClientType.Custom) { it('sets the provider to a custom RPC provider initialized with the RPC target and chain ID', async () => { await withController( { @@ -6619,10 +5198,10 @@ function refreshNetworkTests({ await operation(controller); expect(createNetworkClientMock).toHaveBeenCalledWith({ - chainId: expectedProviderConfig.chainId, - rpcUrl: expectedProviderConfig.rpcUrl, + chainId: expectedNetworkClientConfiguration.chainId, + rpcUrl: expectedNetworkClientConfiguration.rpcUrl, type: NetworkClientType.Custom, - ticker: expectedProviderConfig.ticker, + ticker: expectedNetworkClientConfiguration.ticker, }); const { provider } = controller.getProviderAndBlockTracker(); assert(provider); @@ -6642,7 +5221,7 @@ function refreshNetworkTests({ } else { // TODO: Either fix this lint violation or explain why it's necessary to ignore. // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - it(`sets the provider to an Infura provider pointed to ${expectedProviderConfig.type}`, async () => { + it(`sets the provider to an Infura provider pointed to ${expectedNetworkClientConfiguration.network}`, async () => { await withController( { infuraProjectId: 'infura-project-id', @@ -6665,11 +5244,8 @@ function refreshNetworkTests({ await operation(controller); expect(createNetworkClientMock).toHaveBeenCalledWith({ - network: expectedProviderConfig.type, + ...expectedNetworkClientConfiguration, infuraProjectId: 'infura-project-id', - chainId: BUILT_IN_NETWORKS[expectedProviderConfig.type].chainId, - ticker: BUILT_IN_NETWORKS[expectedProviderConfig.type].ticker, - type: NetworkClientType.Infura, }); const { provider } = controller.getProviderAndBlockTracker(); assert(provider); @@ -6700,45 +5276,38 @@ function refreshNetworkTests({ buildFakeClient(fakeProviders[0]), buildFakeClient(fakeProviders[1]), ]; - const initializationNetworkClientOptions: Parameters< + const { selectedNetworkClientId } = controller.state; + let initializationNetworkClientOptions: Parameters< typeof createNetworkClient - >[0] = - controller.state.providerConfig.type === NetworkType.rpc - ? { - chainId: toHex(controller.state.providerConfig.chainId), - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - rpcUrl: controller.state.providerConfig.rpcUrl!, - type: NetworkClientType.Custom, - ticker: controller.state.providerConfig.ticker, - } - : { - network: controller.state.providerConfig.type, - infuraProjectId: 'infura-project-id', - chainId: - BUILT_IN_NETWORKS[controller.state.providerConfig.type] - .chainId, - ticker: - BUILT_IN_NETWORKS[controller.state.providerConfig.type] - .ticker, - type: NetworkClientType.Infura, - }; + >[0]; + + if (isInfuraNetworkType(selectedNetworkClientId)) { + initializationNetworkClientOptions = { + network: selectedNetworkClientId, + infuraProjectId: 'infura-project-id', + chainId: BUILT_IN_NETWORKS[selectedNetworkClientId].chainId, + ticker: BUILT_IN_NETWORKS[selectedNetworkClientId].ticker, + type: NetworkClientType.Infura, + }; + } else { + const networkConfiguration = + controller.state.networkConfigurations[selectedNetworkClientId]; + initializationNetworkClientOptions = { + chainId: networkConfiguration.chainId, + rpcUrl: networkConfiguration.rpcUrl, + type: NetworkClientType.Custom, + ticker: networkConfiguration.ticker, + }; + } + const operationNetworkClientOptions: Parameters< typeof createNetworkClient >[0] = - expectedProviderConfig.type === NetworkType.rpc - ? { - chainId: toHex(expectedProviderConfig.chainId), - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - rpcUrl: expectedProviderConfig.rpcUrl!, - type: NetworkClientType.Custom, - ticker: expectedProviderConfig.ticker, - } + expectedNetworkClientConfiguration.type === NetworkClientType.Custom + ? expectedNetworkClientConfiguration : { - network: expectedProviderConfig.type, + ...expectedNetworkClientConfiguration, infuraProjectId: 'infura-project-id', - chainId: BUILT_IN_NETWORKS[expectedProviderConfig.type].chainId, - ticker: BUILT_IN_NETWORKS[expectedProviderConfig.type].ticker, - type: NetworkClientType.Infura, }; mockCreateNetworkClient() .calledWith(initializationNetworkClientOptions) @@ -6758,7 +5327,11 @@ function refreshNetworkTests({ ); }); - lookupNetworkTests({ expectedProviderConfig, initialState, operation }); + lookupNetworkTests({ + expectedNetworkClientConfiguration, + initialState, + operation, + }); } /** @@ -6767,17 +5340,17 @@ function refreshNetworkTests({ * covered by these tests. * * @param args - Arguments. - * @param args.expectedProviderConfig - The provider configuration that the - * operation is expected to set. + * @param args.expectedNetworkClientConfiguration - The network client + * configuration that the operation is expected to set. * @param args.initialState - The initial state of the network controller. * @param args.operation - The operation to test. */ function lookupNetworkTests({ - expectedProviderConfig, + expectedNetworkClientConfiguration, initialState, operation, }: { - expectedProviderConfig: ProviderConfig; + expectedNetworkClientConfiguration: NetworkClientConfiguration; initialState?: Partial; operation: (controller: NetworkController) => Promise; }) { @@ -6994,7 +5567,7 @@ function lookupNetworkTests({ ); }); - if (expectedProviderConfig.type === NetworkType.rpc) { + if (expectedNetworkClientConfiguration.type === NetworkClientType.Custom) { it('emits infuraIsUnblocked', async () => { await withController( { @@ -7096,7 +5669,7 @@ function lookupNetworkTests({ }); describe('if a country blocked error is encountered while retrieving the network details of the current network', () => { - if (expectedProviderConfig.type === NetworkType.rpc) { + if (expectedNetworkClientConfiguration.type === NetworkClientType.Custom) { it('updates the network in state to "unknown"', async () => { await withController( { @@ -7410,7 +5983,7 @@ function lookupNetworkTests({ ); }); - if (expectedProviderConfig.type === NetworkType.rpc) { + if (expectedNetworkClientConfiguration.type === NetworkClientType.Custom) { it('emits infuraIsUnblocked', async () => { await withController( { @@ -7614,35 +6187,6 @@ async function withController( } } -/** - * Builds a complete ProviderConfig object, filling in values that are not - * provided with defaults. - * - * @param config - An incomplete ProviderConfig object. - * @returns The complete ProviderConfig object. - */ -function buildProviderConfig( - config: Partial = {}, -): ProviderConfig { - if (config.type && config.type !== NetworkType.rpc) { - return { - ...BUILT_IN_NETWORKS[config.type], - // This is redundant with the spread operation below, but this was - // required for TypeScript to understand that this property was set to an - // Infura type. - type: config.type, - ...config, - }; - } - return { - type: NetworkType.rpc, - chainId: toHex(1337), - rpcUrl: 'http://doesntmatter.com', - ticker: 'TEST', - ...config, - }; -} - /** * Builds an object that `createNetworkClient` returns. * diff --git a/packages/selected-network-controller/tests/SelectedNetworkController.test.ts b/packages/selected-network-controller/tests/SelectedNetworkController.test.ts index 26d08127ce..a36ebb50fc 100644 --- a/packages/selected-network-controller/tests/SelectedNetworkController.test.ts +++ b/packages/selected-network-controller/tests/SelectedNetworkController.test.ts @@ -243,7 +243,6 @@ describe('SelectedNetworkController', () => { messenger.publish( 'NetworkController:stateChange', { - providerConfig: { chainId: '0x5', ticker: 'ETH', type: 'goerli' }, selectedNetworkClientId: 'goerli', networkConfigurations: {}, networksMetadata: {}, @@ -281,7 +280,6 @@ describe('SelectedNetworkController', () => { messenger.publish( 'NetworkController:stateChange', { - providerConfig: { chainId: '0x5', ticker: 'ETH', type: 'goerli' }, selectedNetworkClientId: 'goerli', networkConfigurations: {}, networksMetadata: {}, diff --git a/packages/transaction-controller/src/TransactionController.test.ts b/packages/transaction-controller/src/TransactionController.test.ts index 88a60cfc10..ed7f8b6fb7 100644 --- a/packages/transaction-controller/src/TransactionController.test.ts +++ b/packages/transaction-controller/src/TransactionController.test.ts @@ -9,7 +9,6 @@ import { ControllerMessenger } from '@metamask/base-controller'; import { ChainId, NetworkType, - NetworksTicker, toHex, BUILT_IN_NETWORKS, ORIGIN_METAMASK, @@ -338,11 +337,6 @@ const MOCK_NETWORK: MockNetwork = { status: NetworkStatus.Available, }, }, - providerConfig: { - type: NetworkType.goerli, - chainId: ChainId.goerli, - ticker: NetworksTicker.goerli, - }, networkConfigurations: {}, }, subscribe: () => undefined, @@ -360,11 +354,6 @@ const MOCK_MAINNET_NETWORK: MockNetwork = { status: NetworkStatus.Available, }, }, - providerConfig: { - type: NetworkType.mainnet, - chainId: ChainId.mainnet, - ticker: NetworksTicker.mainnet, - }, networkConfigurations: {}, }, subscribe: () => undefined, @@ -382,11 +371,6 @@ const MOCK_LINEA_MAINNET_NETWORK: MockNetwork = { status: NetworkStatus.Available, }, }, - providerConfig: { - type: NetworkType['linea-mainnet'], - chainId: ChainId['linea-mainnet'], - ticker: NetworksTicker['linea-mainnet'], - }, networkConfigurations: {}, }, subscribe: () => undefined, @@ -404,11 +388,6 @@ const MOCK_LINEA_GOERLI_NETWORK: MockNetwork = { status: NetworkStatus.Available, }, }, - providerConfig: { - type: NetworkType['linea-goerli'], - chainId: ChainId['linea-goerli'], - ticker: NetworksTicker['linea-goerli'], - }, networkConfigurations: {}, }, subscribe: () => undefined, @@ -3418,58 +3397,6 @@ describe('TransactionController', () => { expect.objectContaining(externalTransactionToConfirm), ); }); - - it('publishes TransactionController:transactionConfirmed with transaction chainId regardless of whether it matches globally selected chainId', async () => { - const mockGloballySelectedNetwork = { - ...MOCK_NETWORK, - state: { - ...MOCK_NETWORK.state, - providerConfig: { - type: NetworkType.sepolia, - chainId: ChainId.sepolia, - ticker: NetworksTicker.sepolia, - }, - }, - }; - const { controller, messenger } = setupController({ - network: mockGloballySelectedNetwork, - }); - - const confirmedEventListener = jest.fn(); - messenger.subscribe( - 'TransactionController:transactionConfirmed', - confirmedEventListener, - ); - - const externalTransactionToConfirm = { - from: ACCOUNT_MOCK, - to: ACCOUNT_2_MOCK, - id: '1', - chainId: ChainId.goerli, // doesn't match globally selected chainId (which is sepolia) - status: TransactionStatus.confirmed, - txParams: { - gasUsed: undefined, - from: ACCOUNT_MOCK, - to: ACCOUNT_2_MOCK, - }, - // TODO: Replace `any` with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any; - const externalTransactionReceipt = { - gasUsed: '0x5208', - }; - const externalBaseFeePerGas = '0x14'; - - await controller.confirmExternalTransaction( - externalTransactionToConfirm, - externalTransactionReceipt, - externalBaseFeePerGas, - ); - - expect(confirmedEventListener).toHaveBeenCalledWith( - expect.objectContaining(externalTransactionToConfirm), - ); - }); }); describe('updateTransactionSendFlowHistory', () => {