From 5afcc9d8abd4a448a382de7644b69450533f309b Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Tue, 7 May 2024 16:37:16 -0600 Subject: [PATCH 01/49] Group network configurations by chain ID Currently, in the client, it is possible to have multiple networks with different RPC endpoint URLs representing the same chain. This creates a problem because if all we have is a chain ID, we don't know which URL to use for requests. To solve this, we plan on consolidating the UX on the client side such that each network corresponds to exactly one chain. Users can then select which default RPC URL they'd like to use for requests. This commit implements the controller changes necessary to support this UX. Here are some more details on the changes here: - The concept of a network configuration has been repurposed such that instead of representing an RPC endpoint, it now represents a whole chain. - A network configuration may have multiple RPC endpoints, and one of them must be designated as the default. - Some RPC endpoints are special in that they represent Infura API URLs; these have the same object shape as "non-Infura" (custom) RPC endpoints, but the Infura project ID is hidden and injected into the RPC URL when creating the network client. - There is no longer a 1-to-1 relationship between network configuration and network client; rather, the 1-to-1 relationship exists between RPC endpoint and network client. This means that the ID of the network client which is created for an RPC endpoint is stored on that RPC endpoint instead of the whole network configuration. - The `networkConfigurations` state property has been replaced with `networkConfigurationsByChainId`. This continues to be an object, but the data inside is organized such that network configurations are identified by chain ID instead of network client ID as they were previously. - The methods `upsertNetworkConfiguration` and `removeNetworkConfiguration` have been removed. These methods always did more than simply add or remove a network configuration; they also updated the registry of network clients. Instead, these methods have been replaced with `addNetwork`, `updateNetwork`, and `removeNetwork`. - `addNetwork` creates new network clients for each RPC endpoint in the given network configuration. - `updateNetwork` takes a chain ID referring to a network configuration and a draft version of that network configuration, and adds or removes network clients for added or removed RPC endpoints. - `removeNetwork` takes a chain ID referring to a network configuration and removes the network clients for each of its RPC endpoints. - In addition, due to the changes to network configuration itself, there are new restrictions on `networkConfigurationsByChainId`, which are validated on initialization and on update. These are: - The network controller cannot be initialized with an empty collection of network configurations. This is because there must be a selected network client so that consumers have a provider to use out of the gate. - Consequently, the last network configuration cannot be removed. - The chain ID of a network configuration must match the same chain that it's filed under in `networkConfigurationsByChainId`. - No two network configurations can have the same chain ID. - A RPC endpoint in a network configuration must have a well-formed URL. - A network configuration cannot have duplicate RPC endpoints. - No two RPC endpoints (regardless of network configuration) can have the same URL. Equality is currently determined by normalizing URLs as per RFC 3986 and may include data like request headers in the future. - If a network configuration has an Infura RPC endpoint, its chain ID must match the set chain ID of the network configuration. - Changing the chain ID of a network configuration is possible, but any existing Infura RPC endpoint must be replaced with the one that matches the new chain ID. - No two RPC endpoints (regardless of network configuration) can have the same network client ID. - Finally, the `trackMetaMetricsEvent` option has been removed from the constructor. This was previously used in `upsertNetworkConfiguration` to create a MetaMetrics event when a new network added, but I've added a new event `NetworkController:networkAdded` to allow the client to do this on its own accord. --- .../src/AssetsContractController.test.ts | 1 - .../src/NftController.test.ts | 4 +- .../src/TokenDetectionController.test.ts | 55 +- .../src/TokenListController.test.ts | 10 +- .../src/TokenRatesController.test.ts | 44 +- .../src/TokensController.test.ts | 4 +- packages/controller-utils/CHANGELOG.md | 7 + packages/controller-utils/src/types.ts | 31 + packages/controller-utils/src/util.test.ts | 4 + packages/controller-utils/src/util.ts | 9 +- .../ens-controller/src/EnsController.test.ts | 22 +- .../src/GasFeeController.test.ts | 61 +- packages/network-controller/CHANGELOG.md | 35 + packages/network-controller/package.json | 1 + .../src/NetworkController.ts | 1437 ++- packages/network-controller/src/index.ts | 36 +- .../tests/NetworkController.test.ts | 9080 ++++++++++++----- packages/network-controller/tests/helpers.ts | 266 +- .../src/QueuedRequestController.test.ts | 23 +- .../tests/SelectedNetworkController.test.ts | 2 +- .../src/TransactionController.test.ts | 12 +- .../TransactionControllerIntegration.test.ts | 423 +- packages/transaction-controller/tsconfig.json | 2 +- tests/helpers.ts | 47 + yarn.lock | 1 + 25 files changed, 8277 insertions(+), 3340 deletions(-) diff --git a/packages/assets-controllers/src/AssetsContractController.test.ts b/packages/assets-controllers/src/AssetsContractController.test.ts index 8600501e0ef..bdd0db6f665 100644 --- a/packages/assets-controllers/src/AssetsContractController.test.ts +++ b/packages/assets-controllers/src/AssetsContractController.test.ts @@ -81,7 +81,6 @@ async function setupAssetContractControllers({ const networkController = new NetworkController({ infuraProjectId, messenger, - trackMetaMetricsEvent: jest.fn(), }); if (useNetworkControllerProvider) { await networkController.initializeProvider(); diff --git a/packages/assets-controllers/src/NftController.test.ts b/packages/assets-controllers/src/NftController.test.ts index 05e29616432..db229700e96 100644 --- a/packages/assets-controllers/src/NftController.test.ts +++ b/packages/assets-controllers/src/NftController.test.ts @@ -32,7 +32,7 @@ import type { NetworkControllerGetNetworkClientByIdAction, NetworkControllerNetworkDidChangeEvent, } from '@metamask/network-controller'; -import { defaultState as defaultNetworkState } from '@metamask/network-controller'; +import { getDefaultNetworkControllerState } from '@metamask/network-controller'; import type { PreferencesControllerStateChangeEvent } from '@metamask/preferences-controller'; import { getDefaultPreferencesState, @@ -262,7 +262,7 @@ function setupController({ selectedNetworkClientId: NetworkClientId; }) => { messenger.publish('NetworkController:networkDidChange', { - ...defaultNetworkState, + ...getDefaultNetworkControllerState(), selectedNetworkClientId, }); }; diff --git a/packages/assets-controllers/src/TokenDetectionController.test.ts b/packages/assets-controllers/src/TokenDetectionController.test.ts index 1012531be9b..d957a8c6d6c 100644 --- a/packages/assets-controllers/src/TokenDetectionController.test.ts +++ b/packages/assets-controllers/src/TokenDetectionController.test.ts @@ -4,7 +4,7 @@ import { ChainId, NetworkType, convertHexToDecimal, - BUILT_IN_NETWORKS, + InfuraNetworkType, } from '@metamask/controller-utils'; import type { InternalAccount } from '@metamask/keyring-api'; import type { KeyringControllerState } from '@metamask/keyring-controller'; @@ -14,9 +14,13 @@ import type { NetworkController, NetworkClientId, } from '@metamask/network-controller'; -import { defaultState as defaultNetworkState } from '@metamask/network-controller'; +import { getDefaultNetworkControllerState } from '@metamask/network-controller'; import type { AutoManagedNetworkClient } from '@metamask/network-controller/src/create-auto-managed-network-client'; import type { CustomNetworkClientConfiguration } from '@metamask/network-controller/src/types'; +import { + buildCustomRpcEndpoint, + buildInfuraNetworkConfiguration, +} from '@metamask/network-controller/tests/helpers'; import { getDefaultPreferencesState, type PreferencesState, @@ -109,22 +113,23 @@ const sampleTokenB = { }; const mockNetworkConfigurations: Record = { - [NetworkType.mainnet]: { - ...BUILT_IN_NETWORKS[NetworkType.mainnet], - rpcUrl: 'https://mainnet.infura.io/v3/fakekey', - }, - [NetworkType.goerli]: { - ...BUILT_IN_NETWORKS[NetworkType.goerli], - rpcUrl: 'https://goerli.infura.io/v3/fakekey', - }, + [InfuraNetworkType.mainnet]: buildInfuraNetworkConfiguration( + InfuraNetworkType.mainnet, + ), + [InfuraNetworkType.goerli]: buildInfuraNetworkConfiguration( + InfuraNetworkType.goerli, + ), polygon: { + blockExplorerUrl: 'https://polygonscan.com/', chainId: '0x89', - nickname: 'Polygon Mainnet', - rpcUrl: `https://polygon-mainnet.infura.io/v3/fakekey`, - ticker: 'MATIC', - rpcPrefs: { - blockExplorerUrl: 'https://polygonscan.com/', - }, + defaultRpcEndpointUrl: 'https://polygon-mainnet.infura.io/v3/fakekey', + name: 'Polygon Mainnet', + nativeTokenName: 'MATIC', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + url: 'https://polygon-mainnet.infura.io/v3/fakekey', + }), + ], }, }; @@ -283,7 +288,7 @@ describe('TokenDetectionController', () => { }, async ({ controller, mockNetworkState }) => { mockNetworkState({ - ...defaultNetworkState, + ...getDefaultNetworkControllerState(), selectedNetworkClientId: NetworkType.goerli, }); await controller.start(); @@ -360,7 +365,7 @@ describe('TokenDetectionController', () => { callActionSpy, }) => { mockNetworkState({ - ...defaultNetworkState, + ...getDefaultNetworkControllerState(), selectedNetworkClientId: 'polygon', }); mockGetNetworkClientById( @@ -1288,7 +1293,7 @@ describe('TokenDetectionController', () => { }); triggerNetworkDidChange({ - ...defaultNetworkState, + ...getDefaultNetworkControllerState(), selectedNetworkClientId: 'polygon', }); await advanceTime({ clock, duration: 1 }); @@ -1344,7 +1349,7 @@ describe('TokenDetectionController', () => { }); triggerNetworkDidChange({ - ...defaultNetworkState, + ...getDefaultNetworkControllerState(), selectedNetworkClientId: 'goerli', }); await advanceTime({ clock, duration: 1 }); @@ -1390,7 +1395,7 @@ describe('TokenDetectionController', () => { }); triggerNetworkDidChange({ - ...defaultNetworkState, + ...getDefaultNetworkControllerState(), selectedNetworkClientId: 'mainnet', }); await advanceTime({ clock, duration: 1 }); @@ -1438,7 +1443,7 @@ describe('TokenDetectionController', () => { }); triggerNetworkDidChange({ - ...defaultNetworkState, + ...getDefaultNetworkControllerState(), selectedNetworkClientId: 'polygon', }); await advanceTime({ clock, duration: 1 }); @@ -1487,7 +1492,7 @@ describe('TokenDetectionController', () => { }); triggerNetworkDidChange({ - ...defaultNetworkState, + ...getDefaultNetworkControllerState(), selectedNetworkClientId: 'polygon', }); await advanceTime({ clock, duration: 1 }); @@ -1793,7 +1798,7 @@ describe('TokenDetectionController', () => { callActionSpy, }) => { mockNetworkState({ - ...defaultNetworkState, + ...getDefaultNetworkControllerState(), selectedNetworkClientId: NetworkType.goerli, }); triggerPreferencesStateChange({ @@ -2101,7 +2106,7 @@ async function withController( const mockNetworkState = jest.fn(); controllerMessenger.registerActionHandler( 'NetworkController:getState', - mockNetworkState.mockReturnValue({ ...defaultNetworkState }), + mockNetworkState.mockReturnValue({ ...getDefaultNetworkControllerState() }), ); const mockTokensState = jest.fn(); controllerMessenger.registerActionHandler( diff --git a/packages/assets-controllers/src/TokenListController.test.ts b/packages/assets-controllers/src/TokenListController.test.ts index 790f68b045c..11b6119c85e 100644 --- a/packages/assets-controllers/src/TokenListController.test.ts +++ b/packages/assets-controllers/src/TokenListController.test.ts @@ -659,7 +659,7 @@ describe('TokenListController', () => { ); onNetworkStateChangeCallback({ selectedNetworkClientId, - networkConfigurations: {}, + networkConfigurationsByChainId: {}, networksMetadata: {}, // @ts-expect-error This property isn't used and will get removed later. providerConfig: {}, @@ -997,7 +997,7 @@ describe('TokenListController', () => { 'NetworkController:stateChange', { selectedNetworkClientId: InfuraNetworkType.goerli, - networkConfigurations: {}, + networkConfigurationsByChainId: {}, networksMetadata: {}, // @ts-expect-error This property isn't used and will get removed later. providerConfig: {}, @@ -1018,7 +1018,7 @@ describe('TokenListController', () => { 'NetworkController:stateChange', { selectedNetworkClientId: selectedCustomNetworkClientId, - networkConfigurations: {}, + networkConfigurationsByChainId: {}, networksMetadata: {}, // @ts-expect-error This property isn't used and will get removed later. providerConfig: {}, @@ -1098,7 +1098,7 @@ describe('TokenListController', () => { 'NetworkController:stateChange', { selectedNetworkClientId: InfuraNetworkType.mainnet, - networkConfigurations: {}, + networkConfigurationsByChainId: {}, networksMetadata: {}, // @ts-expect-error This property isn't used and will get removed later. providerConfig: {}, @@ -1148,7 +1148,7 @@ describe('TokenListController', () => { 'NetworkController:stateChange', { selectedNetworkClientId: selectedCustomNetworkClientId, - networkConfigurations: {}, + networkConfigurationsByChainId: {}, networksMetadata: {}, // @ts-expect-error This property isn't used and will get removed later. providerConfig: {}, diff --git a/packages/assets-controllers/src/TokenRatesController.test.ts b/packages/assets-controllers/src/TokenRatesController.test.ts index df8833ff4e7..0b54ac14b38 100644 --- a/packages/assets-controllers/src/TokenRatesController.test.ts +++ b/packages/assets-controllers/src/TokenRatesController.test.ts @@ -11,7 +11,7 @@ import type { NetworkClientId, NetworkState, } from '@metamask/network-controller'; -import { defaultState as defaultNetworkState } from '@metamask/network-controller'; +import { getDefaultNetworkControllerState } from '@metamask/network-controller'; import type { NetworkClientConfiguration } from '@metamask/network-controller/src/types'; import { getDefaultPreferencesState, @@ -47,8 +47,6 @@ import type { TokensControllerState } from './TokensController'; const defaultSelectedAddress = '0x0000000000000000000000000000000000000001'; const mockTokenAddress = '0x0000000000000000000000000000000000000010'; -const defaultSelectedNetworkClientId = 'AAAA-BBBB-CCCC-DDDD'; - type MainControllerMessenger = ControllerMessenger< AllowedActions | AddApprovalRequest, AllowedEvents @@ -640,8 +638,8 @@ describe('TokenRatesController', () => { .spyOn(controller, 'updateExchangeRates') .mockResolvedValue(); triggerNetworkStateChange({ - ...defaultNetworkState, - selectedNetworkClientId: defaultSelectedNetworkClientId, + ...getDefaultNetworkControllerState(), + selectedNetworkClientId: 'AAAA-BBBB-CCCC-DDDD', }); expect(updateExchangeRatesSpy).toHaveBeenCalledTimes(1); @@ -668,8 +666,8 @@ describe('TokenRatesController', () => { .spyOn(controller, 'updateExchangeRates') .mockResolvedValue(); triggerNetworkStateChange({ - ...defaultNetworkState, - selectedNetworkClientId: defaultSelectedNetworkClientId, + ...getDefaultNetworkControllerState(), + selectedNetworkClientId: 'AAAA-BBBB-CCCC-DDDD', }); expect(updateExchangeRatesSpy).toHaveBeenCalledTimes(1); @@ -722,8 +720,8 @@ describe('TokenRatesController', () => { await controller.start(); jest.spyOn(controller, 'updateExchangeRates').mockResolvedValue(); triggerNetworkStateChange({ - ...defaultNetworkState, - selectedNetworkClientId: defaultSelectedNetworkClientId, + ...getDefaultNetworkControllerState(), + selectedNetworkClientId: 'AAAA-BBBB-CCCC-DDDD', }); expect(controller.state.marketData).toStrictEqual({}); @@ -776,8 +774,8 @@ describe('TokenRatesController', () => { await controller.start(); jest.spyOn(controller, 'updateExchangeRates').mockResolvedValue(); triggerNetworkStateChange({ - ...defaultNetworkState, - selectedNetworkClientId: defaultSelectedNetworkClientId, + ...getDefaultNetworkControllerState(), + selectedNetworkClientId: 'AAAA-BBBB-CCCC-DDDD', }); expect(controller.state.marketData).toStrictEqual({}); @@ -804,8 +802,8 @@ describe('TokenRatesController', () => { .spyOn(controller, 'updateExchangeRates') .mockResolvedValue(); triggerNetworkStateChange({ - ...defaultNetworkState, - selectedNetworkClientId: defaultSelectedNetworkClientId, + ...getDefaultNetworkControllerState(), + selectedNetworkClientId: 'AAAA-BBBB-CCCC-DDDD', }); expect(updateExchangeRatesSpy).not.toHaveBeenCalled(); @@ -833,8 +831,8 @@ describe('TokenRatesController', () => { .spyOn(controller, 'updateExchangeRates') .mockResolvedValue(); triggerNetworkStateChange({ - ...defaultNetworkState, - selectedNetworkClientId: defaultSelectedNetworkClientId, + ...getDefaultNetworkControllerState(), + selectedNetworkClientId: 'AAAA-BBBB-CCCC-DDDD', }); expect(updateExchangeRatesSpy).not.toHaveBeenCalled(); @@ -860,8 +858,8 @@ describe('TokenRatesController', () => { .spyOn(controller, 'updateExchangeRates') .mockResolvedValue(); triggerNetworkStateChange({ - ...defaultNetworkState, - selectedNetworkClientId: defaultSelectedNetworkClientId, + ...getDefaultNetworkControllerState(), + selectedNetworkClientId: 'AAAA-BBBB-CCCC-DDDD', }); expect(updateExchangeRatesSpy).not.toHaveBeenCalled(); @@ -913,8 +911,8 @@ describe('TokenRatesController', () => { async ({ controller, triggerNetworkStateChange }) => { jest.spyOn(controller, 'updateExchangeRates').mockResolvedValue(); triggerNetworkStateChange({ - ...defaultNetworkState, - selectedNetworkClientId: defaultSelectedNetworkClientId, + ...getDefaultNetworkControllerState(), + selectedNetworkClientId: 'AAAA-BBBB-CCCC-DDDD', }); expect(controller.state.marketData).toStrictEqual({}); @@ -966,8 +964,8 @@ describe('TokenRatesController', () => { async ({ controller, triggerNetworkStateChange }) => { jest.spyOn(controller, 'updateExchangeRates').mockResolvedValue(); triggerNetworkStateChange({ - ...defaultNetworkState, - selectedNetworkClientId: defaultSelectedNetworkClientId, + ...getDefaultNetworkControllerState(), + selectedNetworkClientId: 'AAAA-BBBB-CCCC-DDDD', }); expect(controller.state.marketData).toStrictEqual({}); @@ -2372,7 +2370,7 @@ async function withController( controllerMessenger.registerActionHandler( 'NetworkController:getState', networkStateMock.mockReturnValue({ - ...defaultNetworkState, + ...getDefaultNetworkControllerState(), ...mockNetworkState, }), ); @@ -2487,7 +2485,7 @@ async function callUpdateExchangeRatesMethod({ // As with many BaseControllerV1-based controllers, runtime config // modification is allowed by the API but not supported in practice. triggerNetworkStateChange({ - ...defaultNetworkState, + ...getDefaultNetworkControllerState(), selectedNetworkClientId, }); } diff --git a/packages/assets-controllers/src/TokensController.test.ts b/packages/assets-controllers/src/TokensController.test.ts index 2d8fb47cb17..0a231c6b9a8 100644 --- a/packages/assets-controllers/src/TokensController.test.ts +++ b/packages/assets-controllers/src/TokensController.test.ts @@ -17,7 +17,7 @@ import type { NetworkClientConfiguration, NetworkClientId, } from '@metamask/network-controller'; -import { defaultState as defaultNetworkState } from '@metamask/network-controller'; +import { getDefaultNetworkControllerState } from '@metamask/network-controller'; import type { PreferencesState } from '@metamask/preferences-controller'; import { getDefaultPreferencesState } from '@metamask/preferences-controller'; import nock from 'nock'; @@ -2258,7 +2258,7 @@ async function withController( selectedNetworkClientId: NetworkClientId; }) => { messenger.publish('NetworkController:networkDidChange', { - ...defaultNetworkState, + ...getDefaultNetworkControllerState(), selectedNetworkClientId, }); }; diff --git a/packages/controller-utils/CHANGELOG.md b/packages/controller-utils/CHANGELOG.md index fbbca40d6b5..23a46e3a868 100644 --- a/packages/controller-utils/CHANGELOG.md +++ b/packages/controller-utils/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add `BlockExplorerUrl` for looking up the block explorer URL for any Infura network ([#4268](https://github.com/MetaMask/core/pull/4286)) +- Add `NetworkNickname` for looking up the common nickname for any Infura network ([#4268](https://github.com/MetaMask/core/pull/4286)) +- Add `Partialize` for making select keys in an object type optional ([#4268](https://github.com/MetaMask/core/pull/4286)) +- `toHex` now supports converting a `bigint` into a hex string ([#4268](https://github.com/MetaMask/core/pull/4286)) + ## [11.0.0] ### Added diff --git a/packages/controller-utils/src/types.ts b/packages/controller-utils/src/types.ts index ec89800f8bf..34a0bebd03f 100644 --- a/packages/controller-utils/src/types.ts +++ b/packages/controller-utils/src/types.ts @@ -95,3 +95,34 @@ export enum NetworksTicker { // eslint-disable-next-line @typescript-eslint/naming-convention rpc = '', } + +export const BlockExplorerUrl = { + [BuiltInNetworkName.Mainnet]: 'https://etherscan.io', + [BuiltInNetworkName.Goerli]: 'https://goerli.etherscan.io', + [BuiltInNetworkName.Sepolia]: 'https://sepolia.etherscan.io', + [BuiltInNetworkName.LineaGoerli]: 'https://goerli.lineascan.build', + [BuiltInNetworkName.LineaSepolia]: 'https://sepolia.lineascan.build', + [BuiltInNetworkName.LineaMainnet]: 'https://lineascan.build', +} as const satisfies Record; +export type BlockExplorerUrl = + (typeof BlockExplorerUrl)[keyof typeof BlockExplorerUrl]; + +export const NetworkNickname = { + [BuiltInNetworkName.Mainnet]: 'Mainnet', + [BuiltInNetworkName.Goerli]: 'Goerli', + [BuiltInNetworkName.Sepolia]: 'Sepolia', + [BuiltInNetworkName.LineaGoerli]: 'Linea Goerli', + [BuiltInNetworkName.LineaSepolia]: 'Linea Sepolia', + [BuiltInNetworkName.LineaMainnet]: 'Linea Mainnet', +} as const satisfies Record; +export type NetworkNickname = + (typeof NetworkNickname)[keyof typeof NetworkNickname]; + +/** + * Makes a selection of keys in a Record optional. + * + * @template Type - The Record that you want to operate on. + * @template Key - The union of keys you want to make optional. + */ +export type Partialize = Omit & + Partial>; diff --git a/packages/controller-utils/src/util.test.ts b/packages/controller-utils/src/util.test.ts index 40a3e8b366d..71dd33e90d6 100644 --- a/packages/controller-utils/src/util.test.ts +++ b/packages/controller-utils/src/util.test.ts @@ -74,6 +74,10 @@ describe('util', () => { expect(util.toHex(new BN(4919))).toBe('0x1337'); }); + it('converts a bigint to a string prepended with "0x"', () => { + expect(util.toHex(4919n)).toBe('0x1337'); + }); + it('parses a string as a number in decimal format and converts it to a hex string prepended with "0x"', () => { expect(util.toHex('4919')).toBe('0x1337'); }); diff --git a/packages/controller-utils/src/util.ts b/packages/controller-utils/src/util.ts index 2024be2e2ca..4d14f71e6f9 100644 --- a/packages/controller-utils/src/util.ts +++ b/packages/controller-utils/src/util.ts @@ -209,13 +209,14 @@ export function fromHex(value: string | BN): BN { * @param value - An integer, an integer encoded as a base-10 string, or a BN. * @returns The integer encoded as a hex string. */ -export function toHex(value: number | string | BN): Hex { +export function toHex(value: number | bigint | string | BN): Hex { if (typeof value === 'string' && isStrictHexString(value)) { return value; } - const hexString = BN.isBN(value) - ? value.toString(16) - : new BN(value.toString(), 10).toString(16); + const hexString = + BN.isBN(value) || typeof value === 'bigint' + ? value.toString(16) + : new BN(value.toString(), 10).toString(16); return `0x${hexString}`; } diff --git a/packages/ens-controller/src/EnsController.test.ts b/packages/ens-controller/src/EnsController.test.ts index 912e63acc02..744b93212eb 100644 --- a/packages/ens-controller/src/EnsController.test.ts +++ b/packages/ens-controller/src/EnsController.test.ts @@ -5,7 +5,7 @@ import { toHex, InfuraNetworkType, } from '@metamask/controller-utils'; -import { defaultState as defaultNetworkState } from '@metamask/network-controller'; +import { getDefaultNetworkControllerState } from '@metamask/network-controller'; import type { ExtractAvailableAction, @@ -189,7 +189,7 @@ describe('EnsController', () => { provider: getProvider(), onNetworkDidChange: (listener) => { listener({ - ...defaultNetworkState, + ...getDefaultNetworkControllerState(), selectedNetworkClientId: InfuraNetworkType.mainnet, }); }, @@ -502,7 +502,7 @@ describe('EnsController', () => { provider: getProvider(), onNetworkDidChange: (listener) => { listener({ - ...defaultNetworkState, + ...getDefaultNetworkControllerState(), selectedNetworkClientId: InfuraNetworkType.mainnet, }); }, @@ -527,7 +527,7 @@ describe('EnsController', () => { provider: getProvider(), onNetworkDidChange: (listener) => { listener({ - ...defaultNetworkState, + ...getDefaultNetworkControllerState(), selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', }); }, @@ -555,7 +555,7 @@ describe('EnsController', () => { provider: getProvider(), onNetworkDidChange: (listener) => { listener({ - ...defaultNetworkState, + ...getDefaultNetworkControllerState(), selectedNetworkClientId: InfuraNetworkType.mainnet, }); }, @@ -581,7 +581,7 @@ describe('EnsController', () => { provider: getProvider(), onNetworkDidChange: (listener) => { listener({ - ...defaultNetworkState, + ...getDefaultNetworkControllerState(), selectedNetworkClientId: InfuraNetworkType.mainnet, }); }, @@ -606,7 +606,7 @@ describe('EnsController', () => { provider: getProvider(), onNetworkDidChange: (listener) => { listener({ - ...defaultNetworkState, + ...getDefaultNetworkControllerState(), selectedNetworkClientId: InfuraNetworkType.mainnet, }); }, @@ -634,7 +634,7 @@ describe('EnsController', () => { provider: getProvider(), onNetworkDidChange: (listener) => { listener({ - ...defaultNetworkState, + ...getDefaultNetworkControllerState(), selectedNetworkClientId: InfuraNetworkType.mainnet, }); }, @@ -662,7 +662,7 @@ describe('EnsController', () => { provider: getProvider(), onNetworkDidChange: (listener) => { listener({ - ...defaultNetworkState, + ...getDefaultNetworkControllerState(), selectedNetworkClientId: InfuraNetworkType.mainnet, }); }, @@ -692,7 +692,7 @@ describe('EnsController', () => { provider: getProvider(), onNetworkDidChange: (listener) => { listener({ - ...defaultNetworkState, + ...getDefaultNetworkControllerState(), selectedNetworkClientId: InfuraNetworkType.mainnet, }); }, @@ -721,7 +721,7 @@ describe('EnsController', () => { provider: getProvider(), onNetworkDidChange: (listener) => { listener({ - ...defaultNetworkState, + ...getDefaultNetworkControllerState(), selectedNetworkClientId: InfuraNetworkType.mainnet, }); }, diff --git a/packages/gas-fee-controller/src/GasFeeController.test.ts b/packages/gas-fee-controller/src/GasFeeController.test.ts index 2c4d59b02af..9e7e699096c 100644 --- a/packages/gas-fee-controller/src/GasFeeController.test.ts +++ b/packages/gas-fee-controller/src/GasFeeController.test.ts @@ -16,6 +16,10 @@ import type { import type { Hex } from '@metamask/utils'; import * as sinon from 'sinon'; +import { + buildCustomNetworkConfiguration, + buildCustomRpcEndpoint, +} from '../../network-controller/tests/helpers'; import determineGasFeeCalculations from './determineGasFeeCalculations'; import { fetchGasEstimates, @@ -80,7 +84,6 @@ const setupNetworkController = async ({ messenger: restrictedMessenger, state, infuraProjectId: '123', - trackMetaMetricsEvent: jest.fn(), }); if (initializeProvider) { @@ -336,13 +339,15 @@ describe('GasFeeController', () => { .fn() .mockReturnValue(true), networkControllerState: { - networkConfigurations: { - 'AAAA-BBBB-CCCC-DDDD': { - id: 'AAAA-BBBB-CCCC-DDDD', + networkConfigurationsByChainId: { + [toHex(1337)]: buildCustomNetworkConfiguration({ chainId: toHex(1337), - rpcUrl: 'http://some/url', - ticker: 'TEST', - }, + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-BBBB-CCCC-DDDD', + }), + ], + }), }, selectedNetworkClientId: 'AAAA-BBBB-CCCC-DDDD', }, @@ -400,13 +405,15 @@ describe('GasFeeController', () => { .fn() .mockReturnValue(true), networkControllerState: { - networkConfigurations: { - 'AAAA-BBBB-CCCC-DDDD': { - id: 'AAAA-BBBB-CCCC-DDDD', + networkConfigurationsByChainId: { + [toHex(1337)]: buildCustomNetworkConfiguration({ chainId: toHex(1337), - rpcUrl: 'http://some/url', - ticker: 'TEST', - }, + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-BBBB-CCCC-DDDD', + }), + ], + }), }, selectedNetworkClientId: 'AAAA-BBBB-CCCC-DDDD', }, @@ -753,13 +760,15 @@ describe('GasFeeController', () => { await setupGasFeeController({ ...defaultConstructorOptions, networkControllerState: { - networkConfigurations: { - 'AAAA-BBBB-CCCC-DDDD': { - id: 'AAAA-BBBB-CCCC-DDDD', + networkConfigurationsByChainId: { + [toHex(1337)]: buildCustomNetworkConfiguration({ chainId: toHex(1337), - rpcUrl: 'http://some/url', - ticker: 'TEST', - }, + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-BBBB-CCCC-DDDD', + }), + ], + }), }, selectedNetworkClientId: 'AAAA-BBBB-CCCC-DDDD', }, @@ -907,13 +916,15 @@ describe('GasFeeController', () => { await setupGasFeeController({ ...defaultConstructorOptions, networkControllerState: { - networkConfigurations: { - 'AAAA-BBBB-CCCC-DDDD': { - id: 'AAAA-BBBB-CCCC-DDDD', + networkConfigurationsByChainId: { + [toHex(1337)]: buildCustomNetworkConfiguration({ chainId: toHex(1337), - rpcUrl: 'http://some/url', - ticker: 'TEST', - }, + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-BBBB-CCCC-DDDD', + }), + ], + }), }, selectedNetworkClientId: 'AAAA-BBBB-CCCC-DDDD', }, diff --git a/packages/network-controller/CHANGELOG.md b/packages/network-controller/CHANGELOG.md index de3beeb5346..e5d59230a82 100644 --- a/packages/network-controller/CHANGELOG.md +++ b/packages/network-controller/CHANGELOG.md @@ -7,15 +7,50 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- **BREAKING:** Add `networkConfigurationsByChainId` to `NetworkState` (type: `Record`) ([#4268](https://github.com/MetaMask/core/pull/4286)) + - This property replaces `networkConfigurations`, and, as its name implies, organizes network configurations by chain ID rather than network client ID. + - If no initial state or this property is not included in initial state, the default value of this property will now include configurations for known Infura networks (Mainnet, Goerli, Sepolia, Linea Goerli, Linea Sepolia, and Linea Mainnet) by default. +- Add `getNetworkConfigurationByChainId` method and `NetworkController:getNetworkConfigurationByChainId` messenger action ([#4268](https://github.com/MetaMask/core/pull/4286)) +- Add `addNetwork`, which replaces one half of `upsertNetworkConfiguration` and can be used to add new network clients for a chain ([#4268](https://github.com/MetaMask/core/pull/4286)) +- Add `updateNetwork`, which replaces one half of `upsertNetworkConfiguration` and can be used to recreate the network clients for an existing chain based on an updated configuration ([#4268](https://github.com/MetaMask/core/pull/4286)) +- Add `removeNetwork`, which replaces `removeNetworkConfiguration` and can be used to remove existing network clients for a chain ([#4268](https://github.com/MetaMask/core/pull/4286)) +- Add `getDefaultNetworkControllerState` function, which replaces `defaultState` and matches patterns in other controllers ([#4268](https://github.com/MetaMask/core/pull/4286)) + ### 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. +- **BREAKING:** Replace `NetworkConfiguration` type with a new definition ([#4268](https://github.com/MetaMask/core/pull/4286)) + - A network configuration no longer represents a single RPC endpoint but rather a collection of RPC endpoints that can all be used to interface with a single chain. + - The only property that has been retained on this type is `chainId`. + - `ticker` has been renamed to `nativeTokenName`. + - `nickname` has been renamed to `name`. + - `blockExplorerUrl` has been pulled out of `rpcPrefs`. + - `rpcEndpoints` has been added as well. This is an an array of objects, where each object has properties `name`, `networkClientId` (optional), `type`, and `url`. + - `defaultRpcEndpointUrl` has been added. This must point to an entry in `rpcEndpoints`. + - `id` has been removed. Previously, this represented the ID of the network client associated with the network configuration. Since network clients are now created from RPC endpoints, the equivalent to this is the `networkClientId` property on an `RpcEndpoint`. +- **BREAKING:** The network controller messenger must now allow the action `NetworkController:getNetworkConfigurationByChainId` ([#4268](https://github.com/MetaMask/core/pull/4286)) +- **BREAKING:** The network controller messenger must now allow the event `NetworkController:networkAdded` ([#4268](https://github.com/MetaMask/core/pull/4286)) +- **BREAKING:** The `NetworkController` constructor will now throw if the initial state provided is invalid ([#4268](https://github.com/MetaMask/core/pull/4286)) + - `networkConfigurationsByChainId` cannot be empty. + - The `chainId` of a network configuration in `networkConfigurationsByChainId` must match the chain ID it is filed under. + - The `defaultRpcEndpointUrl` of a network configuration in `networkConfigurationsByChainId` must match an entry in its `rpcEndpoints`. + - `selectedNetworkClientId` must match the `networkClientId` of an RPC endpoint in `networkConfigurationsByChainId`. +- **BREAKING:** Update `getNetworkConfigurationByNetworkClientId` so that when given an Infura network name (that is, a value from `InfuraNetworkType`), it will return a masked version of the RPC endpoint URL for the associated Infura network ([#4268](https://github.com/MetaMask/core/pull/4286)) + - If you want the unmasked version, you'll need the `url` property from the network _client_ configuration, which you can get by calling `getNetworkClientById` and then accessing the `configuration` property off of the network client. +- **BREAKING:** Update `loadBackup` to take and update `networkConfigurationsByChainId` instead of `networkConfigurations` ([#4268](https://github.com/MetaMask/core/pull/4286)) ### 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. +- **BREAKING:** Remove `networkConfigurations` from `NetworkState`, which has been replaced with `networkConfigurationsByChainId` ([#4268](https://github.com/MetaMask/core/pull/4286)) +- **BREAKING:** Remove `upsertNetworkConfiguration` and `removeNetworkConfiguration`, which have been replaced with `addNetwork`, `updateNetwork`, and `removeNetwork` ([#4268](https://github.com/MetaMask/core/pull/4286)) +- **BREAKING:** Remove `defaultState`, which has been replaced with `getDefaultNetworkControllerState` ([#4268](https://github.com/MetaMask/core/pull/4286)) +- **BREAKING:** Remove `trackMetaMetricsEvent` option from the NetworkController constructor ([#4268](https://github.com/MetaMask/core/pull/4286)) + - Previously, this was used in `upsertNetworkConfiguration` to create a MetaMetrics event when a new network was added. This can now be achieved by subscribing to the `NetworkController:networkAdded` event. ## [19.0.0] diff --git a/packages/network-controller/package.json b/packages/network-controller/package.json index b720f8f4b66..947ed829b50 100644 --- a/packages/network-controller/package.json +++ b/packages/network-controller/package.json @@ -54,6 +54,7 @@ "@metamask/utils": "^8.3.0", "async-mutex": "^0.5.0", "immer": "^9.0.6", + "uri-js": "^4.4.1", "uuid": "^8.3.2" }, "devDependencies": { diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index d9fe7cc474c..29b6b1261c7 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -4,12 +4,16 @@ import type { RestrictedControllerMessenger, } from '@metamask/base-controller'; import { BaseController } from '@metamask/base-controller'; +import type { Partialize } from '@metamask/controller-utils'; import { - BUILT_IN_NETWORKS, + toHex, InfuraNetworkType, NetworkType, isSafeChainId, isInfuraNetworkType, + ChainId, + NetworksTicker, + NetworkNickname, } from '@metamask/controller-utils'; import EthQuery from '@metamask/eth-query'; import { errorCodes } from '@metamask/rpc-errors'; @@ -17,12 +21,15 @@ import { createEventEmitterProxy } from '@metamask/swappable-obj-proxy'; import type { SwappableProxy } from '@metamask/swappable-obj-proxy'; import type { Hex } from '@metamask/utils'; import { - assertIsStrictHexString, + hexToBigInt, + isStrictHexString, hasProperty, isPlainObject, } from '@metamask/utils'; import { strict as assert } from 'assert'; -import { v4 as random } from 'uuid'; +import * as URI from 'uri-js'; +import { inspect } from 'util'; +import { v4 as uuidV4 } from 'uuid'; import { INFURA_BLOCKED_KEY, NetworkStatus } from './constants'; import type { @@ -42,6 +49,9 @@ import type { const log = createModuleLogger(projectLogger, 'NetworkController'); +const INFURA_URL_REGEX = + /^https:\/\/(?[^.]+)\.infura\.io\/v\d+\/(?.+)$/u; + export type Block = { baseFeePerGas?: string; }; @@ -65,32 +75,178 @@ export type NetworkMetadata = { }; /** - * Custom RPC network information + * The type of an RPC endpoint. * - * @property rpcUrl - RPC target URL. - * @property chainId - Network ID as per EIP-155 - * @property nickname - Personalized network name. - * @property ticker - Currency ticker. - * @property rpcPrefs - Personalized preferences. + * @see CustomRpcEndpoint + * @see InfuraRpcEndpoint + */ +export enum RpcEndpointType { + Custom = 'custom', + Infura = 'infura', +} + +/** + * An Infura RPC endpoint is a reference to a specific network that Infura + * supports as well as an Infura account we own that we allow users to make use + * of for free. We need to disambiguate these endpoints from custom RPC + * endpoints, because while the types for these kinds of object both have the + * same interface, the URL for an Infura endpoint contains the Infura project + * ID, and we don't want this to be present in state. We therefore hide it by + * representing it in the URL as `{infuraProjectId}`, which we replace this when + * create network clients. But we need to know somehow that we only need to do + * this replacement for Infura endpoints and not custom endpoints — hence the + * type. + */ +export type InfuraRpcEndpoint = { + /** + * The user-facing name of the endpoint. + */ + name: string; + /** + * The identifier for the network client that has been created for this RPC + * endpoint. + */ + networkClientId: InfuraNetworkType; + /** + * The type of this endpoint, always "default". + */ + type: RpcEndpointType.Infura; + /** + * The URL of the endpoint. Expected to be a sort of template with the text + * `{infuraProjectId}`, which will get replaced with the Infura project ID. + */ + url: string; +}; + +/** + * A custom RPC endpoint is a reference to a user-defined server which fronts an + * EVM chain. It may refer to an Infura network, but only by coincidence. + */ +export type CustomRpcEndpoint = { + /** + * The user-facing name of the endpoint. + */ + name: string; + /** + * The identifier for the network client that has been created for this RPC + * endpoint. + */ + networkClientId: string; + /** + * The type of this endpoint, always "custom". + */ + type: RpcEndpointType.Custom; + /** + * The URL of the endpoint. + */ + url: string; +}; + +/** + * An RPC endpoint is a reference to a server which fronts an EVM chain. There + * are two varieties of RPC endpoints: Infura and custom. + * + * @see CustomRpcEndpoint + * @see InfuraRpcEndpoint + */ +export type RpcEndpoint = InfuraRpcEndpoint | CustomRpcEndpoint; + +/** + * From a user perspective, a network configuration holds information about a + * network that a user can select through the client. A "network" in this sense + * can explicitly refer to an EVM chain that the user explicitly adds or doesn't + * need to add (because it comes shipped with the client). The properties here + * therefore directly map to fields that a user sees and can edit for a network + * within the client. + * + * Internally, a network configuration represents a single conceptual EVM chain, + * which is represented tangibly via multiple RPC endpoints. A "network" is then + * something for which a network client object is created automatically or + * created on demand when it is added to the client. */ export type NetworkConfiguration = { - rpcUrl: string; + /** + * An optional URL that allows the user to view blocks and transactions that + * have occurred on the chain. + */ + blockExplorerUrl?: string; + /** + * The ID of the chain. Represented in hexadecimal format with a leading "0x" + * instead of decimal format so that when viewed out of context it can be + * unambiguously interpreted. + */ chainId: Hex; - ticker: string; - nickname?: string; - rpcPrefs?: { - blockExplorerUrl: string; - }; + /** + * The RPC endpoint URL that all requests will use by default in order to + * interact with the chain. The value of this property must match the `url` + * property of an entry in `rpcEndpoints`. + */ + defaultRpcEndpointUrl: string; + /** + * The user-facing nickname assigned to the chain. + */ + name: string; + /** + * The name of the token that represents the native currency for the chain. + */ + nativeTokenName: string; + /** + * The collection of possible RPC endpoints that the client can use to + * interact with the chain. + */ + rpcEndpoints: RpcEndpoint[]; }; /** - * The collection of network configurations in state. + * A custom RPC endpoint in a new network configuration, meant to be used in + * conjunction with `AddNetworkFields`. + * + * Custom RPC endpoints do not need a `networkClientId` property because it is + * assumed that they have not already been added and therefore network clients + * do not exist for them yet (and hence IDs need to be generated). */ -type NetworkConfigurations = Record< - NetworkConfigurationId, - NetworkConfiguration & { id: NetworkConfigurationId } +export type AddNetworkCustomRpcEndpointFields = Omit< + CustomRpcEndpoint, + 'networkClientId' >; +/** + * A new network configuration that `addNetwork` takes. + * + * Custom RPC endpoints do not need a `networkClientId` property because it is + * assumed that they have not already been added and are not represented by + * network clients yet. + * + */ +export type AddNetworkFields = Omit & { + rpcEndpoints: (InfuraRpcEndpoint | AddNetworkCustomRpcEndpointFields)[]; +}; + +/** + * A custom RPC endpoint in an updated representation of a network + * configuration, meant to be used in conjunction with `UpdateNetworkFields`. + * + * Custom RPC endpoints do not need a `networkClientId` property because it is + * assumed that they have not already been added and therefore network clients + * do not exist for them yet (and hence IDs need to be generated). + */ +export type UpdateNetworkCustomRpcEndpointFields = Partialize< + CustomRpcEndpoint, + 'networkClientId' +>; + +/** + * An updated representation of an existing network configuration that + * `updateNetwork` takes. + * + * Custom RPC endpoints may or may not have a `networkClientId` property; if + * they do, then it is assumed that they already exist, and if not, then it is + * assumed that they are new and are not represented by network clients yet. + */ +type UpdateNetworkFields = Omit & { + rpcEndpoints: (InfuraRpcEndpoint | UpdateNetworkCustomRpcEndpointFields)[]; +}; + /** * `Object.keys()` is intentionally generic: it returns the keys of an object, * but it cannot make guarantees about the contents of that object, so the type @@ -114,53 +270,6 @@ export function knownKeysOf( return Object.keys(object) as K[]; } -/** - * Asserts that the given value is of the given type if the given validation - * function returns a truthy result. - * - * @param value - The value to validate. - * @param validate - A function used to validate that the value is of the given - * type. Takes the `value` as an argument and is expected to return true or - * false. - * @param message - The message to throw if the function does not return a - * truthy result. - * @throws if the function does not return a truthy result. - */ -function assertOfType( - value: unknown, - validate: (value: unknown) => boolean, - message: string, -): asserts value is Type { - assert.ok(validate(value), message); -} - -/** - * Returns a portion of the given object with only the given keys. - * - * @param object - An object. - * @param keys - The keys to pick from the object. - * @returns the portion of the object. - */ -// TODO: Replace `any` with type -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function pick, Keys extends keyof Obj>( - object: Obj, - keys: Keys[], -): Pick { - const pickedObject = keys.reduce>>( - (finalObject, key) => { - return { ...finalObject, [key]: object[key] }; - }, - {}, - ); - assertOfType>( - pickedObject, - () => keys.every((key) => key in pickedObject), - 'The reduce did not produce an object with all of the desired keys.', - ); - return pickedObject; -} - /** * Type guard for determining whether the given value is an error object with a * `code` property, such as an instance of Error. @@ -190,26 +299,35 @@ export type CustomNetworkClientId = string; export type NetworkClientId = BuiltInNetworkClientId | CustomNetworkClientId; /** - * Information about networks not held by any other part of state. + * Extra information about each network, such as whether it is accessible or + * blocked and whether it supports EIP-1559, keyed by network client ID. */ -export type NetworksMetadata = { - [networkClientId: NetworkClientId]: NetworkMetadata; -}; +export type NetworksMetadata = Record; /** - * @type NetworkState - * - * Network controller state - * @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. + * The state that NetworkController stores. */ export type NetworkState = { + /** + * The ID of the network client that the proxies returned by + * `getSelectedNetworkClient` currently point to. + */ selectedNetworkClientId: NetworkClientId; - networkConfigurations: NetworkConfigurations; + /** + * The registry of networks and corresponding RPC endpoints that the + * controller can use to make requests for various chains. + * + * @see NetworkConfiguration + */ + networkConfigurationsByChainId: Record; + /** + * Extra information about each network, such as whether it is accessible or + * blocked and whether it supports EIP-1559, keyed by network client ID. + */ networksMetadata: NetworksMetadata; }; -const name = 'NetworkController'; +const controllerName = 'NetworkController'; /** * Represents the block tracker for the currently selected network. (Note that @@ -232,7 +350,7 @@ export type BlockTrackerProxy = SwappableProxy< export type ProviderProxy = SwappableProxy>; export type NetworkControllerStateChangeEvent = ControllerStateChangeEvent< - typeof name, + typeof controllerName, NetworkState >; @@ -275,15 +393,25 @@ export type NetworkControllerInfuraIsUnblockedEvent = { payload: []; }; +/** + * `networkAdded` is published after a network configuration is added to the + * network configuration registry and network clients are created for it. + */ +export type NetworkControllerNetworkAddedEvent = { + type: 'NetworkController:networkAdded'; + payload: [networkConfiguration: NetworkConfiguration]; +}; + export type NetworkControllerEvents = | NetworkControllerStateChangeEvent | NetworkControllerNetworkWillChangeEvent | NetworkControllerNetworkDidChangeEvent | NetworkControllerInfuraIsBlockedEvent - | NetworkControllerInfuraIsUnblockedEvent; + | NetworkControllerInfuraIsUnblockedEvent + | NetworkControllerNetworkAddedEvent; export type NetworkControllerGetStateAction = ControllerGetStateAction< - typeof name, + typeof controllerName, NetworkState >; @@ -328,6 +456,11 @@ export type NetworkControllerSetActiveNetworkAction = { handler: NetworkController['setActiveNetwork']; }; +export type NetworkControllerGetNetworkConfigurationByChainId = { + type: `NetworkController:getNetworkConfigurationByChainId`; + handler: NetworkController['getNetworkConfigurationByChainId']; +}; + export type NetworkControllerGetNetworkConfigurationByNetworkClientId = { type: `NetworkController:getNetworkConfigurationByNetworkClientId`; handler: NetworkController['getNetworkConfigurationByNetworkClientId']; @@ -342,10 +475,11 @@ export type NetworkControllerActions = | NetworkControllerFindNetworkClientIdByChainIdAction | NetworkControllerSetActiveNetworkAction | NetworkControllerSetProviderTypeAction + | NetworkControllerGetNetworkConfigurationByChainId | NetworkControllerGetNetworkConfigurationByNetworkClientId; export type NetworkControllerMessenger = RestrictedControllerMessenger< - typeof name, + typeof controllerName, NetworkControllerActions, NetworkControllerEvents, never, @@ -354,36 +488,70 @@ export type NetworkControllerMessenger = RestrictedControllerMessenger< export type NetworkControllerOptions = { messenger: NetworkControllerMessenger; - trackMetaMetricsEvent: () => void; infuraProjectId: string; state?: Partial; }; -export const defaultState: NetworkState = { - selectedNetworkClientId: NetworkType.mainnet, - networksMetadata: {}, - networkConfigurations: {}, -}; +/** + * Constructs a value for the state property `networkConfigurationsByChainId` + * which will be used if it has not been provided to the constructor. + * + * @returns The default value for `networkConfigurationsByChainId`. + */ +function getDefaultNetworkConfigurationsByChainId(): Record< + Hex, + NetworkConfiguration +> { + return Object.values(InfuraNetworkType).reduce( + (obj: Partial>, infuraNetworkType) => { + const chainId = ChainId[infuraNetworkType]; + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + const rpcEndpointUrl = `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`; + + const networkConfiguration: NetworkConfiguration = { + chainId, + defaultRpcEndpointUrl: rpcEndpointUrl, + name: NetworkNickname[infuraNetworkType], + nativeTokenName: NetworksTicker[infuraNetworkType], + rpcEndpoints: [ + { + name: `Infura ${NetworkNickname[infuraNetworkType] as string}`, + networkClientId: infuraNetworkType, + type: RpcEndpointType.Infura, + url: rpcEndpointUrl, + }, + ], + }; -type MetaMetricsEventPayload = { - event: string; - category: string; - referrer?: { url: string }; - actionId?: number; - environmentType?: string; - properties?: unknown; - sensitiveProperties?: unknown; - revenue?: number; - currency?: string; - value?: number; -}; + return { ...obj, [chainId]: networkConfiguration }; + }, + {}, + ) as Record; +} -type NetworkConfigurationId = string; +/** + * Constructs properties for the NetworkController state whose values will be + * used if not provided to the constructor. + * + * @returns The default NetworkController state. + */ +export function getDefaultNetworkControllerState(): NetworkState { + const networksMetadata = {}; + const networkConfigurationsByChainId = + getDefaultNetworkConfigurationsByChainId(); + + return { + selectedNetworkClientId: InfuraNetworkType.mainnet, + networksMetadata, + networkConfigurationsByChainId, + }; +} /** * The collection of auto-managed network clients that map to Infura networks. */ -type AutoManagedBuiltInNetworkClientRegistry = Record< +export type AutoManagedBuiltInNetworkClientRegistry = Record< BuiltInNetworkClientId, AutoManagedNetworkClient >; @@ -391,7 +559,7 @@ type AutoManagedBuiltInNetworkClientRegistry = Record< /** * The collection of auto-managed network clients that map to Infura networks. */ -type AutoManagedCustomNetworkClientRegistry = Record< +export type AutoManagedCustomNetworkClientRegistry = Record< CustomNetworkClientId, AutoManagedNetworkClient >; @@ -400,16 +568,142 @@ type AutoManagedCustomNetworkClientRegistry = Record< * The collection of auto-managed network clients that map to Infura networks * as well as custom networks that users have added. */ -type AutoManagedNetworkClientRegistry = { +export type AutoManagedNetworkClientRegistry = { [NetworkClientType.Infura]: AutoManagedBuiltInNetworkClientRegistry; [NetworkClientType.Custom]: AutoManagedCustomNetworkClientRegistry; }; +/** + * Determines whether the given URL is valid by attempting to parse it. + * + * @param url - The URL to test. + * @returns True if the URL is valid, false otherwise. + */ +function isValidUrl(url: string) { + const uri = URI.parse(url); + return ( + uri.error === undefined && (uri.scheme === 'http' || uri.scheme === 'https') + ); +} + +/** + * Given an Infura API URL, extracts the subdomain that identifies the Infura + * network. + * + * @param rpcEndpointUrl - The URL to operate on. + * @returns The Infura network name that the URL references. + * @throws if the URL is not an Infura API URL, or if an Infura network is not + * present in the URL. + */ +function deriveInfuraNetworkNameFromRpcEndpointUrl( + rpcEndpointUrl: string, +): InfuraNetworkType { + const match = INFURA_URL_REGEX.exec(rpcEndpointUrl); + + if (match?.groups) { + if (isInfuraNetworkType(match.groups.networkName)) { + return match.groups.networkName; + } + + throw new Error(`Unknown Infura network '${match.groups.networkName}'`); + } + + throw new Error('Could not derive Infura network from RPC endpoint URL'); +} + +/** + * Performs a series of checks that the given NetworkController state is + * internally consistent — that all parts of state that are supposed to match in + * fact do — so that working with the state later on doesn't cause unexpected + * errors. + * + * In the case of NetworkController, there are several parts of state that need + * to match. For instance, `defaultRpcEndpointUrl` needs to match an entry + * within `rpcEndpoints`, and `selectedNetworkClientId` needs to point to an RPC + * endpoint within a network configuration. + * + * @param state - The NetworkController state to verify. + * @throws if the state is invalid in some way. + */ +function validateNetworkControllerState(state: NetworkState) { + const networkConfigurationEntries = Object.entries( + state.networkConfigurationsByChainId, + ); + const networkClientIds = Object.values( + state.networkConfigurationsByChainId, + ).flatMap((networkConfiguration) => + networkConfiguration.rpcEndpoints.map( + (rpcEndpoint) => rpcEndpoint.networkClientId, + ), + ); + + if (networkConfigurationEntries.length === 0) { + throw new Error( + 'NetworkController state is invalid: `networkConfigurationsByChainId` cannot be empty', + ); + } + + for (const [chainId, networkConfiguration] of networkConfigurationEntries) { + if (chainId !== networkConfiguration.chainId) { + throw new Error( + `NetworkController state has invalid \`networkConfigurationsByChainId\`: Network configuration '${networkConfiguration.name}' is filed under '${chainId}' which does not match its \`chainId\` of '${networkConfiguration.chainId}'`, + ); + } + + if ( + !networkConfiguration.rpcEndpoints.some( + (rpcEndpoint) => + rpcEndpoint.url === networkConfiguration.defaultRpcEndpointUrl, + ) + ) { + throw new Error( + `NetworkController state has invalid \`networkConfigurationsByChainId\`: Network configuration '${networkConfiguration.name}' has a \`defaultRpcEndpointUrl\` that does not match an entry in \`rpcEndpoints\``, + ); + } + } + + if ([...new Set(networkClientIds)].length < networkClientIds.length) { + throw new Error( + 'NetworkController state has invalid `networkConfigurationsByChainId`: Every RPC endpoint across all network configurations must have a unique `networkClientId`', + ); + } + + if (!networkClientIds.includes(state.selectedNetworkClientId)) { + throw new Error( + `NetworkController state is invalid: \`selectedNetworkClientId\` ${inspect( + state.selectedNetworkClientId, + )} does not refer to an RPC endpoint within a network configuration`, + ); + } +} + +/** + * Transforms a map of chain ID to network configuration to a map of network + * client ID to network configuration. + * + * @param networkConfigurationsByChainId - The network configurations, keyed by + * chain ID. + * @returns The network configurations, keyed by network client ID. + */ +function buildNetworkConfigurationsByNetworkClientId( + networkConfigurationsByChainId: Record, +): Map { + return new Map( + Object.values(networkConfigurationsByChainId).flatMap( + (networkConfiguration) => { + return networkConfiguration.rpcEndpoints.map((rpcEndpoint) => { + return [rpcEndpoint.networkClientId, networkConfiguration]; + }); + }, + ), + ); +} + /** * Controller that creates and manages an Ethereum network provider. */ export class NetworkController extends BaseController< - typeof name, + typeof controllerName, NetworkState, NetworkControllerMessenger > { @@ -417,8 +711,6 @@ export class NetworkController extends BaseController< #infuraProjectId: string; - #trackMetaMetricsEvent: (event: MetaMetricsEventPayload) => void; - #previouslySelectedNetworkClientId: string; #providerProxy: ProviderProxy | undefined; @@ -431,14 +723,20 @@ export class NetworkController extends BaseController< | AutoManagedNetworkClient | AutoManagedNetworkClient; - constructor({ - messenger, - state, - infuraProjectId, - trackMetaMetricsEvent, - }: NetworkControllerOptions) { + #networkConfigurationsByNetworkClientId: Map< + NetworkClientId, + NetworkConfiguration + >; + + constructor({ messenger, state, infuraProjectId }: NetworkControllerOptions) { + const initialState = { ...getDefaultNetworkControllerState(), ...state }; + validateNetworkControllerState(initialState); + if (!infuraProjectId || typeof infuraProjectId !== 'string') { + throw new Error('Invalid Infura project ID'); + } + super({ - name, + name: controllerName, metadata: { selectedNetworkClientId: { persist: true, @@ -448,19 +746,22 @@ export class NetworkController extends BaseController< persist: true, anonymous: false, }, - networkConfigurations: { + networkConfigurationsByChainId: { persist: true, anonymous: false, }, }, messenger, - state: { ...defaultState, ...state }, + state: initialState, }); - if (!infuraProjectId || typeof infuraProjectId !== 'string') { - throw new Error('Invalid Infura project ID'); - } + this.#infuraProjectId = infuraProjectId; - this.#trackMetaMetricsEvent = trackMetaMetricsEvent; + this.#previouslySelectedNetworkClientId = + this.state.selectedNetworkClientId; + this.#networkConfigurationsByNetworkClientId = + buildNetworkConfigurationsByNetworkClientId( + this.state.networkConfigurationsByChainId, + ); this.messagingSystem.registerActionHandler( // TODO: Either fix this lint violation or explain why it's necessary to ignore. @@ -509,6 +810,13 @@ export class NetworkController extends BaseController< 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}:getNetworkConfigurationByChainId`, + this.getNetworkConfigurationByChainId.bind(this), + ); + + this.messagingSystem.registerActionHandler( + // ESLint is mistaken here; `name` is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions `${this.name}:getNetworkConfigurationByNetworkClientId`, this.getNetworkConfigurationByNetworkClientId.bind(this), ); @@ -519,9 +827,6 @@ export class NetworkController extends BaseController< `${this.name}:getSelectedNetworkClient`, this.getSelectedNetworkClient.bind(this), ); - - this.#previouslySelectedNetworkClientId = - this.state.selectedNetworkClientId; } /** @@ -562,12 +867,13 @@ export class NetworkController extends BaseController< } /** - * Returns all of the network clients that have been created so far, keyed by - * their identifier in the network client registry. This collection represents - * not only built-in networks but also any custom networks that consumers have - * added. + * Internally, the Infura and custom network clients are categorized by type + * so that when accessing either kind of network client, TypeScript knows + * which type to assign to the network client. For some cases it's more useful + * to be able to access network clients by ID instead of by type and then ID, + * so this function makes that possible. * - * @returns The list of known network clients. + * @returns The network clients registered so far, keyed by ID. */ getNetworkClientRegistry(): AutoManagedBuiltInNetworkClientRegistry & AutoManagedCustomNetworkClientRegistry { @@ -674,8 +980,9 @@ export class NetworkController extends BaseController< } /** - * Creates network clients for built-in and custom networks, then establishes - * the currently selected network client based on state. + * Ensures that network clients for Infura and custom RPC endpoints have been + * created. Then, consulting state, initializes and establishes the currently + * selected network client. */ async initializeProvider() { this.#applyNetworkSelection(this.state.selectedNetworkClientId); @@ -898,11 +1205,10 @@ export class NetworkController extends BaseController< /** * Changes the selected 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). + * @param networkClientId - The ID of a network client that will be used to + * make requests. * @throws if no network client is associated with the given - * `networkClientId`. + * network client ID. */ async setActiveNetwork(networkClientId: string) { this.#previouslySelectedNetworkClientId = @@ -1021,205 +1327,635 @@ export class NetworkController extends BaseController< } /** - * Returns a configuration object for the network identified by the given - * network client ID. If given an Infura network type, constructs one based on - * what we know about the network; otherwise attempts locates a network - * configuration in state that corresponds to the network client ID. + * Returns the network configuration that has been filed under the given chain + * ID. * - * @param networkClientId - The network client ID. - * @returns The configuration for the referenced network if one exists, or - * undefined otherwise. + * @param chainId - The chain ID to use as a key. + * @returns The network configuration if one exists, or undefined. + */ + getNetworkConfigurationByChainId( + chainId: Hex, + ): NetworkConfiguration | undefined { + return this.state.networkConfigurationsByChainId[chainId]; + } + + /** + * Returns the network configuration that contains an RPC endpoint with the + * given network client ID. + * + * @param networkClientId - The network client ID to use as a key. + * @returns The network configuration if one exists, or undefined. */ getNetworkConfigurationByNetworkClientId( networkClientId: NetworkClientId, ): NetworkConfiguration | undefined { - if (isInfuraNetworkType(networkClientId)) { - // TODO: Either fix this lint violation or explain why it's necessary to ignore. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - const rpcUrl = `https://${networkClientId}.infura.io/v3/${ - this.#infuraProjectId - }`; - return { - rpcUrl, - ...BUILT_IN_NETWORKS[networkClientId], - }; - } - - return this.state.networkConfigurations[networkClientId]; + return this.#networkConfigurationsByNetworkClientId.get(networkClientId); } /** - * Adds a new custom network or updates the information for an existing - * network. - * - * This may involve updating the `networkConfigurations` property in - * state as well and/or adding a new network client to the network client - * registry. The `rpcUrl` and `chainId` of the given object are used to - * determine which action to take: + * Creates and registers network clients for the collection of Infura and + * custom RPC endpoints that can be used to make requests for a particular + * chain, storing the given configuration object in state for later reference. * - * - If the `rpcUrl` corresponds to an existing network configuration - * (case-insensitively), then it is overwritten with the object. Furthermore, - * if the `chainId` is different from the existing network configuration, then - * the existing network client is replaced with a new one. - * - If the `rpcUrl` does not correspond to an existing network configuration - * (case-insensitively), then the object is used to add a new network - * configuration along with a new network client. - * - * @param networkConfiguration - The network configuration to add or update. - * @param options - Additional configuration options. - * @param options.referrer - Used to create a metrics event; the site from which the call originated, or 'metamask' for internal calls. - * @param options.source - Used to create a metrics event; where the event originated (i.e. from a dapp or from the network form). - * @param options.setActive - If true, switches to the network upon adding or updating it (default: false). - * @returns The ID for the added or updated network configuration. + * @param fields - The object that describes the new network/chain and lists + * the RPC endpoints which front that chain. + * @returns The newly added network configuration. + * @throws if any part of `fields` would produce invalid state. + * @see NetworkConfiguration */ - async upsertNetworkConfiguration( - networkConfiguration: NetworkConfiguration, - { - referrer, - source, - setActive = false, - }: { - referrer: string; - source: string; - setActive?: boolean; - }, - ): Promise { - const sanitizedNetworkConfiguration: NetworkConfiguration = pick( - networkConfiguration, - ['rpcUrl', 'chainId', 'ticker', 'nickname', 'rpcPrefs'], + addNetwork(fields: AddNetworkFields): NetworkConfiguration { + const { + blockExplorerUrl, + chainId, + defaultRpcEndpointUrl, + nativeTokenName, + rpcEndpoints: setOfRpcEndpointFields, + } = fields; + const rpcEndpointUrls = setOfRpcEndpointFields.map( + (rpcEndpointFields) => rpcEndpointFields.url, + ); + const infuraRpcEndpoints = setOfRpcEndpointFields.filter( + (rpcEndpointFields): rpcEndpointFields is InfuraRpcEndpoint => + rpcEndpointFields.type === RpcEndpointType.Infura, + ); + const networkClientIds = infuraRpcEndpoints.map( + (rpcEndpointFields) => rpcEndpointFields.networkClientId, ); - const { rpcUrl, chainId, ticker } = sanitizedNetworkConfiguration; - assertIsStrictHexString(chainId); - if (!isSafeChainId(chainId)) { + if (!isStrictHexString(chainId) || !isSafeChainId(chainId)) { throw new Error( - `Invalid chain ID "${chainId}": numerical value greater than max safe value.`, + `Cannot add network: Invalid \`chainId\` ${inspect( + chainId, + )} (must start with "0x" and not exceed the maximum)`, ); } - if (!rpcUrl) { + const existingNetworkConfigurationViaChain = + this.state.networkConfigurationsByChainId[chainId]; + if (existingNetworkConfigurationViaChain !== undefined) { throw new Error( - 'An rpcUrl is required to add or update network configuration', + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `Cannot add network for chain ${chainId} as another network for that chain already exists ('${existingNetworkConfigurationViaChain.name}')`, ); } - if (!referrer || !source) { + + if (blockExplorerUrl !== undefined && !isValidUrl(blockExplorerUrl)) { throw new Error( - 'referrer and source are required arguments for adding or updating a network configuration', + `Cannot add network: \`blockExplorerUrl\` ${inspect( + blockExplorerUrl, + )} is an invalid URL`, ); } - try { - new URL(rpcUrl); - // TODO: Replace `any` with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { - if (e.message.includes('Invalid URL')) { - throw new Error('rpcUrl must be a valid URL'); + + if (setOfRpcEndpointFields.length === 0) { + throw new Error( + 'Cannot add network: `rpcEndpoints` must be a non-empty array', + ); + } + for (const rpcEndpointFields of setOfRpcEndpointFields) { + const { url } = rpcEndpointFields; + + if (!isValidUrl(url)) { + throw new Error( + `Cannot add network: An entry in \`rpcEndpoints\` has invalid URL ${inspect( + url, + )}`, + ); } + + if ( + setOfRpcEndpointFields.some( + (otherNewRpcEndpointFields) => + otherNewRpcEndpointFields !== rpcEndpointFields && + URI.equal(otherNewRpcEndpointFields.url, rpcEndpointFields.url), + ) + ) { + throw new Error( + `Cannot add network: Each entry in rpcEndpoints must have a unique URL`, + ); + } + + for (const networkConfiguration of Object.values( + this.state.networkConfigurationsByChainId, + )) { + const rpcEndpoint = networkConfiguration.rpcEndpoints.find( + (existingRpcEndpoint) => + URI.equal(rpcEndpointFields.url, existingRpcEndpoint.url), + ); + if (rpcEndpoint) { + throw new Error( + // False negative - these are strings. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `Cannot add network that points to same RPC endpoint as existing network for chain ${networkConfiguration.chainId} ('${networkConfiguration.name}')`, + ); + } + } + } + + if ( + [...new Set(setOfRpcEndpointFields)].length < + setOfRpcEndpointFields.length + ) { + throw new Error( + 'Cannot add network: Each entry in rpcEndpoints must be unique', + ); } - if (!ticker) { + + if ([...new Set(networkClientIds)].length < networkClientIds.length) { throw new Error( - 'A ticker is required to add or update networkConfiguration', + 'Cannot add network: Each entry in rpcEndpoints must have a unique networkClientId', ); } - const autoManagedNetworkClientRegistry = - this.#ensureAutoManagedNetworkClientRegistryPopulated(); + if (infuraRpcEndpoints.length > 1) { + throw new Error( + 'Cannot add network: There cannot be more than one Infura RPC endpoint', + ); + } - const existingNetworkConfiguration = Object.values( - this.state.networkConfigurations, - ).find( - (networkConfig) => - networkConfig.rpcUrl.toLowerCase() === rpcUrl.toLowerCase(), + const infuraRpcEndpoint = setOfRpcEndpointFields.find( + (rpcEndpointFields) => rpcEndpointFields.type === RpcEndpointType.Infura, ); - const upsertedNetworkConfigurationId = existingNetworkConfiguration - ? existingNetworkConfiguration.id - : random(); - const networkClientId = upsertedNetworkConfigurationId; - - const customNetworkClientRegistry = - autoManagedNetworkClientRegistry[NetworkClientType.Custom]; - const existingAutoManagedNetworkClient = - customNetworkClientRegistry[networkClientId]; - const shouldDestroyExistingNetworkClient = - existingAutoManagedNetworkClient && - existingAutoManagedNetworkClient.configuration.chainId !== chainId; - if (shouldDestroyExistingNetworkClient) { - existingAutoManagedNetworkClient.destroy(); + if (infuraRpcEndpoint) { + const infuraNetworkName = deriveInfuraNetworkNameFromRpcEndpointUrl( + infuraRpcEndpoint.url, + ); + const infuraNetworkNickname = NetworkNickname[infuraNetworkName]; + const infuraChainId = ChainId[infuraNetworkName]; + if (chainId !== infuraChainId) { + throw new Error( + // This is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `Cannot add network with chain ID ${chainId} and Infura RPC endpoint for '${infuraNetworkNickname}' which represents ${infuraChainId}, as the two conflict`, + ); + } } - if ( - !existingAutoManagedNetworkClient || - shouldDestroyExistingNetworkClient - ) { - customNetworkClientRegistry[networkClientId] = - createAutoManagedNetworkClient({ - type: NetworkClientType.Custom, + + if (!rpcEndpointUrls.includes(defaultRpcEndpointUrl)) { + throw new Error( + `Cannot add network: \`defaultRpcEndpointUrl\` '${defaultRpcEndpointUrl}' must match an entry in \`rpcEndpoints\` (${inspect( + rpcEndpointUrls, + )})`, + ); + } + + let conflict: + | { + networkConfiguration: NetworkConfiguration; + rpcEndpoint: RpcEndpoint; + } + | undefined; + for (const existingNetworkConfiguration of Object.values( + this.state.networkConfigurationsByChainId, + )) { + const existingRpcEndpoint = + existingNetworkConfiguration.rpcEndpoints.find((rpcEndpoint) => + rpcEndpointUrls.includes(rpcEndpoint.url), + ); + if (existingRpcEndpoint) { + conflict = { + networkConfiguration: existingNetworkConfiguration, + rpcEndpoint: existingRpcEndpoint, + }; + } + } + if (conflict !== undefined) { + throw new Error( + `Cannot add network that points to same RPC endpoint as existing network for chain ${conflict.networkConfiguration.chainId} ("${conflict.networkConfiguration.name}")`, + ); + } + + const newRpcEndpoints = setOfRpcEndpointFields.map( + (defaultOrCustomRpcEndpointFields) => { + if (defaultOrCustomRpcEndpointFields.type === RpcEndpointType.Custom) { + return { + ...defaultOrCustomRpcEndpointFields, + networkClientId: uuidV4(), + }; + } + return defaultOrCustomRpcEndpointFields; + }, + ); + + const autoManagedNetworkClientRegistry = + this.#ensureAutoManagedNetworkClientRegistryPopulated(); + for (const rpcEndpoint of newRpcEndpoints) { + if (rpcEndpoint.type === RpcEndpointType.Infura) { + autoManagedNetworkClientRegistry[NetworkClientType.Infura][ + rpcEndpoint.networkClientId + ] = createAutoManagedNetworkClient({ chainId, - rpcUrl, - ticker, + infuraProjectId: this.#infuraProjectId, + network: rpcEndpoint.networkClientId, + ticker: nativeTokenName, + type: NetworkClientType.Infura, }); + } else { + autoManagedNetworkClientRegistry[NetworkClientType.Custom][ + rpcEndpoint.networkClientId + ] = createAutoManagedNetworkClient({ + chainId, + rpcUrl: rpcEndpoint.url, + ticker: nativeTokenName, + type: NetworkClientType.Custom, + }); + } } + const newNetworkConfiguration = { + ...fields, + rpcEndpoints: newRpcEndpoints, + }; + this.update((state) => { - state.networkConfigurations[upsertedNetworkConfigurationId] = { - id: upsertedNetworkConfigurationId, - ...sanitizedNetworkConfiguration, - }; + state.networkConfigurationsByChainId[chainId] = newNetworkConfiguration; }); - if (!existingNetworkConfiguration) { - this.#trackMetaMetricsEvent({ - event: 'Custom Network Added', - category: 'Network', - referrer: { - url: referrer, - }, - properties: { - // TODO: Either fix this lint violation or explain why it's necessary to ignore. - // eslint-disable-next-line @typescript-eslint/naming-convention - chain_id: chainId, - symbol: ticker, - source, - }, - }); - } + this.#networkConfigurationsByNetworkClientId = + buildNetworkConfigurationsByNetworkClientId( + this.state.networkConfigurationsByChainId, + ); - if (setActive) { - await this.setActiveNetwork(upsertedNetworkConfigurationId); - } + this.messagingSystem.publish( + `${controllerName}:networkAdded`, + newNetworkConfiguration, + ); - return upsertedNetworkConfigurationId; + return newNetworkConfiguration; } /** - * Removes a custom network from state. + * Updates the configuration for a previously stored network filed under the + * given chain ID, creating and registering new network clients to represent + * RPC endpoints that have been added and destroying and unregistering + * existing network clients for RPC endpoints that have been removed. * - * This involves updating the `networkConfigurations` property in state as - * well and removing the network client that corresponds to the network from - * the client registry. + * Note that if `chainId` is changed, then all network clients associated with + * that chain will be removed and re-added, even if none of the RPC endpoints + * have changed. * - * @param networkConfigurationId - The ID of an existing network - * configuration. + * @param chainId - The chain ID associated with an existing network. + * @param fields - The object that describes the updates to the network/chain, + * including the new set of RPC endpoints which should front that chain. + * @returns The updated network configuration. + * @throws if `chainId` does not refer to an existing network configuration, + * or if any part of `fields` would produce invalid state. + * @see NetworkConfiguration */ - removeNetworkConfiguration(networkConfigurationId: string) { - if (!this.state.networkConfigurations[networkConfigurationId]) { + updateNetwork( + chainId: Hex, + fields: UpdateNetworkFields, + ): NetworkConfiguration { + const existingNetworkConfiguration = + this.state.networkConfigurationsByChainId[chainId]; + + if (existingNetworkConfiguration === undefined) { throw new Error( - `networkConfigurationId ${networkConfigurationId} does not match a configured networkConfiguration`, + `Cannot find network configuration for chain ${inspect(chainId)}`, ); } + const existingChainId = chainId; + const { + blockExplorerUrl: newBlockExplorerUrl, + chainId: newChainId, + defaultRpcEndpointUrl: newInfuraRpcEndpointUrl, + nativeTokenName: newNativeTokenName, + rpcEndpoints: setOfNewRpcEndpointFields, + } = fields; + const newRpcEndpointUrls = setOfNewRpcEndpointFields.map( + (newRpcEndpointFields) => newRpcEndpointFields.url, + ); + const infuraRpcEndpoints = setOfNewRpcEndpointFields.filter( + (newRpcEndpointFields): newRpcEndpointFields is InfuraRpcEndpoint => + newRpcEndpointFields.type === RpcEndpointType.Infura, + ); + const newNetworkClientIds = setOfNewRpcEndpointFields + .map((newRpcEndpointFields) => newRpcEndpointFields.networkClientId) + .filter((networkClientId) => networkClientId !== undefined); + const networkConfigurationsForOtherChains = Object.values( + this.state.networkConfigurationsByChainId, + ).filter( + (networkConfiguration) => + networkConfiguration.chainId !== existingChainId, + ); + const autoManagedNetworkClientRegistry = this.#ensureAutoManagedNetworkClientRegistryPopulated(); - const networkClientId = networkConfigurationId; + + if (!isStrictHexString(newChainId) || !isSafeChainId(newChainId)) { + throw new Error( + `Cannot update network: New \`chainId\` ${inspect( + newChainId, + )} is invalid (must start with "0x" and not exceed the maximum)`, + ); + } + if (newChainId !== existingChainId) { + const existingNetworkConfigurationViaChain = + this.state.networkConfigurationsByChainId[newChainId]; + if (existingNetworkConfigurationViaChain !== undefined) { + throw new Error( + // False negative - these are strings. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `Cannot move network from chain ${existingChainId} to ${newChainId} as another network for that chain already exists ('${existingNetworkConfigurationViaChain.name}')`, + ); + } + } + + if (newBlockExplorerUrl !== undefined && !isValidUrl(newBlockExplorerUrl)) { + throw new Error( + `Cannot update network: \`blockExplorerUrl\` ${inspect( + newBlockExplorerUrl, + )} is an invalid URL`, + ); + } + + if (setOfNewRpcEndpointFields.length === 0) { + throw new Error( + 'Cannot update network: `rpcEndpoints` must be a non-empty array', + ); + } + for (const newRpcEndpointFields of setOfNewRpcEndpointFields) { + const { url, networkClientId } = newRpcEndpointFields; + + if (!isValidUrl(url)) { + throw new Error( + `Cannot update network: An entry in \`rpcEndpoints\` has invalid URL ${inspect( + url, + )}`, + ); + } + + if ( + networkClientId !== undefined && + !Object.values(autoManagedNetworkClientRegistry).some( + (networkClientsById) => networkClientId in networkClientsById, + ) + ) { + throw new Error( + `Cannot update network: RPC endpoint '${ + // This is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + url + }' refers to network client ${inspect( + networkClientId, + )} that does not exist`, + ); + } + + if ( + setOfNewRpcEndpointFields.some( + (otherNewRpcEndpointFields) => + otherNewRpcEndpointFields !== newRpcEndpointFields && + URI.equal(otherNewRpcEndpointFields.url, newRpcEndpointFields.url), + ) + ) { + throw new Error( + `Cannot update network: Each entry in rpcEndpoints must have a unique URL`, + ); + } + + for (const networkConfiguration of networkConfigurationsForOtherChains) { + const rpcEndpoint = networkConfiguration.rpcEndpoints.find( + (existingRpcEndpoint) => + URI.equal(newRpcEndpointFields.url, existingRpcEndpoint.url), + ); + if (rpcEndpoint) { + throw new Error( + // False negative - these are strings. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `Cannot update network to point to same RPC endpoint as existing network for chain ${networkConfiguration.chainId} ('${networkConfiguration.name}')`, + ); + } + } + } + + if ( + [...new Set(setOfNewRpcEndpointFields)].length < + setOfNewRpcEndpointFields.length + ) { + throw new Error( + 'Cannot update network: Each entry in rpcEndpoints must be unique', + ); + } + + if ([...new Set(newNetworkClientIds)].length < newNetworkClientIds.length) { + throw new Error( + 'Cannot update network: Each entry in rpcEndpoints must have a unique networkClientId', + ); + } + + if (infuraRpcEndpoints.length > 1) { + throw new Error( + 'Cannot update network: There cannot be more than one Infura RPC endpoint', + ); + } + + const infuraRpcEndpoint = setOfNewRpcEndpointFields.find( + (newRpcEndpointFields) => + newRpcEndpointFields.type === RpcEndpointType.Infura, + ); + if (infuraRpcEndpoint) { + const infuraNetworkName = deriveInfuraNetworkNameFromRpcEndpointUrl( + infuraRpcEndpoint.url, + ); + const infuraNetworkNickname = NetworkNickname[infuraNetworkName]; + const infuraChainId = ChainId[infuraNetworkName]; + if (newChainId !== infuraChainId) { + throw new Error( + // This is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `Cannot update network with chain ID ${newChainId} and Infura RPC endpoint for '${infuraNetworkNickname}' which represents ${infuraChainId}, as the two conflict`, + ); + } + } + + if (!newRpcEndpointUrls.includes(newInfuraRpcEndpointUrl)) { + throw new Error( + `Cannot update network: \`defaultRpcEndpointUrl\` '${newInfuraRpcEndpointUrl}' must match an entry in \`rpcEndpoints\` (${inspect( + newRpcEndpointUrls, + )})`, + ); + } + + const operations: { + type: 'add' | 'remove' | 'noop'; + value: RpcEndpoint; + }[] = []; + if (newChainId === existingChainId) { + for (const existingRpcEndpoint of existingNetworkConfiguration.rpcEndpoints) { + if ( + !setOfNewRpcEndpointFields.some( + (newRpcEndpointFields) => + newRpcEndpointFields.networkClientId === + existingRpcEndpoint.networkClientId && + newRpcEndpointFields.type === existingRpcEndpoint.type && + newRpcEndpointFields.url === existingRpcEndpoint.url, + ) + ) { + operations.push({ + type: 'remove', + value: existingRpcEndpoint, + }); + } + } + for (const newRpcEndpointFields of setOfNewRpcEndpointFields) { + const existingRpcEndpoint = + existingNetworkConfiguration.rpcEndpoints.find( + (rpcEndpoint) => + rpcEndpoint.networkClientId === + newRpcEndpointFields.networkClientId && + rpcEndpoint.type === newRpcEndpointFields.type && + rpcEndpoint.url === newRpcEndpointFields.url, + ); + if (existingRpcEndpoint === undefined) { + const newRpcEndpoint = + newRpcEndpointFields.type === RpcEndpointType.Infura + ? newRpcEndpointFields + : { ...newRpcEndpointFields, networkClientId: uuidV4() }; + operations.push({ + type: 'add', + value: newRpcEndpoint, + }); + } else { + operations.push({ + type: 'noop', + value: existingRpcEndpoint, + }); + } + } + } else { + for (const existingRpcEndpoint of existingNetworkConfiguration.rpcEndpoints) { + operations.push({ + type: 'remove', + value: existingRpcEndpoint, + }); + } + for (const newRpcEndpointFields of setOfNewRpcEndpointFields) { + const newRpcEndpoint = + newRpcEndpointFields.type === RpcEndpointType.Infura + ? newRpcEndpointFields + : { ...newRpcEndpointFields, networkClientId: uuidV4() }; + operations.push({ + type: 'add', + value: newRpcEndpoint, + }); + } + } + + const newRpcEndpoints: RpcEndpoint[] = []; + for (const operation of operations) { + if (operation.type === 'remove') { + const networkClient = this.getNetworkClientById( + operation.value.networkClientId, + ); + networkClient.destroy(); + delete autoManagedNetworkClientRegistry[ + networkClient.configuration.type + ][operation.value.networkClientId]; + } else { + if (operation.type === 'add') { + if (operation.value.type === RpcEndpointType.Infura) { + autoManagedNetworkClientRegistry[NetworkClientType.Infura][ + operation.value.networkClientId + ] = createAutoManagedNetworkClient({ + type: NetworkClientType.Infura, + chainId: newChainId, + network: operation.value.networkClientId, + infuraProjectId: this.#infuraProjectId, + ticker: newNativeTokenName, + }); + } else { + autoManagedNetworkClientRegistry[NetworkClientType.Custom][ + operation.value.networkClientId + ] = createAutoManagedNetworkClient({ + type: NetworkClientType.Custom, + chainId: newChainId, + rpcUrl: operation.value.url, + ticker: newNativeTokenName, + }); + } + } + newRpcEndpoints.push(operation.value); + } + } + + const updatedNetworkConfiguration = { + ...fields, + rpcEndpoints: newRpcEndpoints, + }; this.update((state) => { - delete state.networkConfigurations[networkConfigurationId]; + if (newChainId !== existingChainId) { + delete state.networkConfigurationsByChainId[existingChainId]; + } + + state.networkConfigurationsByChainId[newChainId] = + updatedNetworkConfiguration; }); - const customNetworkClientRegistry = - autoManagedNetworkClientRegistry[NetworkClientType.Custom]; - const existingAutoManagedNetworkClient = - customNetworkClientRegistry[networkClientId]; - existingAutoManagedNetworkClient.destroy(); - delete customNetworkClientRegistry[networkClientId]; + this.#networkConfigurationsByNetworkClientId = + buildNetworkConfigurationsByNetworkClientId( + this.state.networkConfigurationsByChainId, + ); + + return updatedNetworkConfiguration; + } + + /** + * Destroys and unregisters the network identified by the given chain ID, also + * removing the associated network configuration from state. + * + * @param chainId - The chain ID associated with an existing network. + * @throws if `chainId` does not refer to an existing network configuration, + * or if the currently selected network is being removed. + * @see NetworkConfiguration + */ + removeNetwork(chainId: Hex) { + const existingNetworkConfiguration = + this.state.networkConfigurationsByChainId[chainId]; + + if (existingNetworkConfiguration === undefined) { + throw new Error( + `Cannot find network configuration for chain ${inspect(chainId)}`, + ); + } + + if ( + existingNetworkConfiguration.rpcEndpoints.some( + (rpcEndpoint) => + rpcEndpoint.networkClientId === this.state.selectedNetworkClientId, + ) + ) { + throw new Error(`Cannot remove the currently selected network`); + } + + const autoManagedNetworkClientRegistry = + this.#ensureAutoManagedNetworkClientRegistryPopulated(); + + for (const rpcEndpoint of existingNetworkConfiguration.rpcEndpoints) { + if (rpcEndpoint.type === RpcEndpointType.Infura) { + autoManagedNetworkClientRegistry[NetworkClientType.Infura][ + rpcEndpoint.networkClientId + ].destroy(); + delete autoManagedNetworkClientRegistry[NetworkClientType.Infura][ + rpcEndpoint.networkClientId + ]; + } else { + autoManagedNetworkClientRegistry[NetworkClientType.Custom][ + rpcEndpoint.networkClientId + ].destroy(); + delete autoManagedNetworkClientRegistry[NetworkClientType.Custom][ + rpcEndpoint.networkClientId + ]; + } + } + + this.update((state) => { + delete state.networkConfigurationsByChainId[ + existingNetworkConfiguration.chainId + ]; + }); } /** @@ -1243,20 +1979,19 @@ export class NetworkController extends BaseController< } /** - * Updates the controller using the given backup data. + * Merges the given backup data into controller state. * * @param backup - The data that has been backed up. - * @param backup.networkConfigurations - Network configurations in the backup. + * @param backup.networkConfigurationsByChainId - Network configurations, + * keyed by chain ID. */ loadBackup({ - networkConfigurations, - }: { - networkConfigurations: NetworkState['networkConfigurations']; - }): void { + networkConfigurationsByChainId, + }: Pick): void { this.update((state) => { - state.networkConfigurations = { - ...state.networkConfigurations, - ...networkConfigurations, + state.networkConfigurationsByChainId = { + ...state.networkConfigurationsByChainId, + ...networkConfigurationsByChainId, }; }); } @@ -1287,99 +2022,77 @@ export class NetworkController extends BaseController< * @returns The populated network client registry. */ #ensureAutoManagedNetworkClientRegistryPopulated(): AutoManagedNetworkClientRegistry { - const autoManagedNetworkClientRegistry = - this.#autoManagedNetworkClientRegistry ?? - this.#createAutoManagedNetworkClientRegistry(); - this.#autoManagedNetworkClientRegistry = autoManagedNetworkClientRegistry; - return autoManagedNetworkClientRegistry; + return (this.#autoManagedNetworkClientRegistry ??= + this.#createAutoManagedNetworkClientRegistry()); } /** - * Constructs the registry of network clients based on the set of built-in - * networks as well as the custom networks in state. + * Constructs the registry of network clients based on the set of default + * and custom networks in state. * * @returns The network clients keyed by ID. */ #createAutoManagedNetworkClientRegistry(): AutoManagedNetworkClientRegistry { - return [ - ...this.#buildIdentifiedInfuraNetworkClientConfigurations(), - ...this.#buildIdentifiedCustomNetworkClientConfigurations(), - ].reduce( + const sortedChainIds = Object.keys( + this.state.networkConfigurationsByChainId, + ) + .map(hexToBigInt) + .sort() + .map(toHex); + const networkClientsWithIds = sortedChainIds.flatMap((chainId) => { + const networkConfiguration = + this.state.networkConfigurationsByChainId[chainId]; + return networkConfiguration.rpcEndpoints.map((rpcEndpoint) => { + if (rpcEndpoint.type === RpcEndpointType.Infura) { + const infuraNetworkName = deriveInfuraNetworkNameFromRpcEndpointUrl( + rpcEndpoint.url, + ); + return [ + rpcEndpoint.networkClientId, + createAutoManagedNetworkClient({ + type: NetworkClientType.Infura, + network: infuraNetworkName, + infuraProjectId: this.#infuraProjectId, + chainId: networkConfiguration.chainId, + ticker: networkConfiguration.nativeTokenName, + }), + ] as const; + } + return [ + rpcEndpoint.networkClientId, + createAutoManagedNetworkClient({ + type: NetworkClientType.Custom, + chainId: networkConfiguration.chainId, + rpcUrl: rpcEndpoint.url, + ticker: networkConfiguration.nativeTokenName, + }), + ] as const; + }); + }); + + return networkClientsWithIds.reduce( ( - registry, - [networkClientType, networkClientId, networkClientConfiguration], + obj: { + [NetworkClientType.Custom]: Partial; + [NetworkClientType.Infura]: Partial; + }, + [networkClientId, networkClient], ) => { - const autoManagedNetworkClient = createAutoManagedNetworkClient( - networkClientConfiguration, - ); return { - ...registry, - [networkClientType]: { - ...registry[networkClientType], - [networkClientId]: autoManagedNetworkClient, + ...obj, + [networkClient.configuration.type]: { + ...obj[networkClient.configuration.type], + [networkClientId]: networkClient, }, }; }, { - [NetworkClientType.Infura]: {}, [NetworkClientType.Custom]: {}, + [NetworkClientType.Infura]: {}, }, ) as AutoManagedNetworkClientRegistry; } - /** - * Constructs the list of network clients for built-in networks (that is, - * the subset of the networks we know Infura supports that consumers do not - * need to explicitly add). - * - * @returns The network clients. - */ - #buildIdentifiedInfuraNetworkClientConfigurations(): [ - NetworkClientType.Infura, - BuiltInNetworkClientId, - InfuraNetworkClientConfiguration, - ][] { - return knownKeysOf(InfuraNetworkType).map((network) => { - const networkClientConfiguration: InfuraNetworkClientConfiguration = { - type: NetworkClientType.Infura, - network, - infuraProjectId: this.#infuraProjectId, - chainId: BUILT_IN_NETWORKS[network].chainId, - ticker: BUILT_IN_NETWORKS[network].ticker, - }; - return [NetworkClientType.Infura, network, networkClientConfiguration]; - }); - } - - /** - * Constructs the list of network clients for custom networks (that is, those - * which consumers have added via `networkConfigurations`). - * - * @returns The network clients. - */ - #buildIdentifiedCustomNetworkClientConfigurations(): [ - NetworkClientType.Custom, - CustomNetworkClientId, - CustomNetworkClientConfiguration, - ][] { - return Object.entries(this.state.networkConfigurations).map( - ([networkConfigurationId, networkConfiguration]) => { - const networkClientId = networkConfigurationId; - const networkClientConfiguration: CustomNetworkClientConfiguration = { - type: NetworkClientType.Custom, - chainId: networkConfiguration.chainId, - rpcUrl: networkConfiguration.rpcUrl, - ticker: networkConfiguration.ticker, - }; - return [ - NetworkClientType.Custom, - networkClientId, - networkClientConfiguration, - ]; - }, - ); - } - /** * Updates the global provider and block tracker proxies (accessible via * {@link getSelectedNetworkClient}) to point to the same ones within the @@ -1414,7 +2127,7 @@ export class NetworkController extends BaseController< /* istanbul ignore if */ if (!possibleAutoManagedNetworkClient) { throw new Error( - `Infura network client not found with ID '${networkClientId}'`, + `No Infura network client found with ID ${inspect(networkClientId)}`, ); } @@ -1427,7 +2140,7 @@ export class NetworkController extends BaseController< if (!possibleAutoManagedNetworkClient) { throw new Error( - `Custom network client not found with ID '${networkClientId}'`, + `No network client found with ID ${inspect(networkClientId)}`, ); } diff --git a/packages/network-controller/src/index.ts b/packages/network-controller/src/index.ts index 9a02ded2a6a..7f0892249f4 100644 --- a/packages/network-controller/src/index.ts +++ b/packages/network-controller/src/index.ts @@ -1,4 +1,38 @@ -export * from './NetworkController'; +export type { + Block, + NetworkMetadata, + NetworkConfiguration, + BuiltInNetworkClientId, + CustomNetworkClientId, + NetworkClientId, + NetworksMetadata, + NetworkState, + BlockTrackerProxy, + ProviderProxy, + NetworkControllerStateChangeEvent, + NetworkControllerNetworkWillChangeEvent, + NetworkControllerNetworkDidChangeEvent, + NetworkControllerInfuraIsBlockedEvent, + NetworkControllerInfuraIsUnblockedEvent, + NetworkControllerEvents, + NetworkControllerGetStateAction, + NetworkControllerGetEthQueryAction, + NetworkControllerGetNetworkClientByIdAction, + NetworkControllerGetSelectedNetworkClientAction, + NetworkControllerGetEIP1559CompatibilityAction, + NetworkControllerFindNetworkClientIdByChainIdAction, + NetworkControllerSetProviderTypeAction, + NetworkControllerSetActiveNetworkAction, + NetworkControllerGetNetworkConfigurationByNetworkClientId, + NetworkControllerActions, + NetworkControllerMessenger, + NetworkControllerOptions, +} from './NetworkController'; +export { + getDefaultNetworkControllerState, + knownKeysOf, + NetworkController, +} from './NetworkController'; export * from './constants'; export type { BlockTracker, Provider } from './types'; export type { NetworkClientConfiguration } from './types'; diff --git a/packages/network-controller/tests/NetworkController.test.ts b/packages/network-controller/tests/NetworkController.test.ts index 2b3d924f343..4d51976e1c7 100644 --- a/packages/network-controller/tests/NetworkController.test.ts +++ b/packages/network-controller/tests/NetworkController.test.ts @@ -1,41 +1,54 @@ import { ControllerMessenger } from '@metamask/base-controller'; import { BUILT_IN_NETWORKS, + ChainId, InfuraNetworkType, isInfuraNetworkType, MAX_SAFE_CHAIN_ID, + NetworkNickname, + NetworksTicker, NetworkType, toHex, } from '@metamask/controller-utils'; import { rpcErrors } from '@metamask/rpc-errors'; -import { getKnownPropertyNames } from '@metamask/utils'; +import type { Hex } from '@metamask/utils'; import assert from 'assert'; import type { Patch } from 'immer'; import { when, resetAllWhenMocks } from 'jest-when'; import { inspect, isDeepStrictEqual, promisify } from 'util'; -import { v4 } from 'uuid'; +import { v4 as uuidV4 } from 'uuid'; import { FakeBlockTracker } from '../../../tests/fake-block-tracker'; import type { FakeProviderStub } from '../../../tests/fake-provider'; import { FakeProvider } from '../../../tests/fake-provider'; import { NetworkStatus } from '../src/constants'; +import * as createAutoManagedNetworkClientModule from '../src/create-auto-managed-network-client'; import type { NetworkClient } from '../src/create-network-client'; import { createNetworkClient } from '../src/create-network-client'; import type { + AutoManagedBuiltInNetworkClientRegistry, + AutoManagedCustomNetworkClientRegistry, NetworkClientId, - NetworkConfiguration, NetworkControllerActions, NetworkControllerEvents, NetworkControllerOptions, NetworkControllerStateChangeEvent, NetworkState, } from '../src/NetworkController'; -import { NetworkController } from '../src/NetworkController'; +import { NetworkController, RpcEndpointType } from '../src/NetworkController'; import type { NetworkClientConfiguration, Provider } from '../src/types'; import { NetworkClientType } from '../src/types'; import { + buildAddNetworkCustomRpcEndpointFields, + buildAddNetworkFields, buildCustomNetworkClientConfiguration, + buildCustomNetworkConfiguration, + buildCustomRpcEndpoint, buildInfuraNetworkClientConfiguration, + buildInfuraNetworkConfiguration, + buildInfuraRpcEndpoint, + buildNetworkConfiguration, + buildUpdateNetworkCustomRpcEndpointFields, } from './helpers'; jest.mock('../src/create-network-client'); @@ -61,7 +74,7 @@ type Block = { }; const createNetworkClientMock = jest.mocked(createNetworkClient); -const uuidV4Mock = jest.mocked(v4); +const uuidV4Mock = jest.mocked(uuidV4); /** * A dummy block that matches the pre-EIP-1559 format (i.e. it doesn't have the @@ -86,49 +99,6 @@ const POST_1559_BLOCK: Block = { */ const BLOCK: Block = POST_1559_BLOCK; -/** - * The networks that NetworkController recognizes as built-in Infura networks, - * along with information we expect to be true for those networks. - */ -const INFURA_NETWORKS = [ - { - networkType: NetworkType['linea-goerli'], - chainId: toHex(59140), - ticker: 'LineaETH', - blockExplorerUrl: 'https://goerli.lineascan.build', - }, - { - networkType: NetworkType['linea-sepolia'], - chainId: toHex(59141), - ticker: 'LineaETH', - blockExplorerUrl: 'https://sepolia.lineascan.build', - }, - { - networkType: NetworkType['linea-mainnet'], - chainId: toHex(59144), - ticker: 'ETH', - blockExplorerUrl: 'https://lineascan.build', - }, - { - networkType: NetworkType.mainnet, - chainId: toHex(1), - ticker: 'ETH', - blockExplorerUrl: 'https://etherscan.io', - }, - { - networkType: NetworkType.goerli, - chainId: toHex(5), - ticker: 'GoerliETH', - blockExplorerUrl: 'https://goerli.etherscan.io', - }, - { - networkType: NetworkType.sepolia, - chainId: toHex(11155111), - ticker: 'SepoliaETH', - blockExplorerUrl: 'https://sepolia.etherscan.io', - }, -]; - /** * A response object for a successful request to `eth_getBlockByNumber`. It is * assumed that the block number here is insignificant to the test. @@ -153,8 +123,16 @@ const GENERIC_JSON_RPC_ERROR = rpcErrors.internal( ); describe('NetworkController', () => { + let uuidCounter = 0; + beforeEach(() => { jest.resetAllMocks(); + + uuidV4Mock.mockImplementation(() => { + const uuid = `UUID-${uuidCounter}`; + uuidCounter += 1; + return uuid; + }); }); afterEach(() => { @@ -162,6 +140,133 @@ describe('NetworkController', () => { }); describe('constructor', () => { + it('throws given an empty networkConfigurationsByChainId collection', () => { + const messenger = buildMessenger(); + const restrictedMessenger = buildNetworkControllerMessenger(messenger); + expect( + () => + new NetworkController({ + messenger: restrictedMessenger, + state: { + networkConfigurationsByChainId: {}, + }, + infuraProjectId: 'infura-project-id', + }), + ).toThrow( + 'NetworkController state is invalid: `networkConfigurationsByChainId` cannot be empty', + ); + }); + + it('throws if the key under which a network configuration is filed does not match the chain ID of that network configuration', () => { + const messenger = buildMessenger(); + const restrictedMessenger = buildNetworkControllerMessenger(messenger); + expect( + () => + new NetworkController({ + messenger: restrictedMessenger, + state: { + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1338', + name: 'Test Network', + }), + }, + }, + infuraProjectId: 'infura-project-id', + }), + ).toThrow( + "NetworkController state has invalid `networkConfigurationsByChainId`: Network configuration 'Test Network' is filed under '0x1337' which does not match its `chainId` of '0x1338'", + ); + }); + + it('throws if a network configuration has an invalid defaultRpcEndpointUrl', () => { + const messenger = buildMessenger(); + const restrictedMessenger = buildNetworkControllerMessenger(messenger); + expect( + () => + new NetworkController({ + messenger: restrictedMessenger, + state: { + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + name: 'Test Network', + defaultRpcEndpointUrl: 'https://some.endpoint', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + url: 'https://different.endpoint', + }), + ], + }), + }, + }, + infuraProjectId: 'infura-project-id', + }), + ).toThrow( + "NetworkController state has invalid `networkConfigurationsByChainId`: Network configuration 'Test Network' has a `defaultRpcEndpointUrl` that does not match an entry in `rpcEndpoints`", + ); + }); + + it('throws if more than one RPC endpoint across network configurations has the same networkClientId', () => { + const messenger = buildMessenger(); + const restrictedMessenger = buildNetworkControllerMessenger(messenger); + expect( + () => + new NetworkController({ + messenger: restrictedMessenger, + state: { + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + name: 'Test Network 1', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.endpoint/1', + }), + ], + }), + '0x2448': buildCustomNetworkConfiguration({ + chainId: '0x2448', + name: 'Test Network 2', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.endpoint/2', + }), + ], + }), + }, + }, + infuraProjectId: 'infura-project-id', + }), + ).toThrow( + 'NetworkController state has invalid `networkConfigurationsByChainId`: Every RPC endpoint across all network configurations must have a unique `networkClientId`', + ); + }); + + it('throws if selectedNetworkClientId does not match the networkClientId of an RPC endpoint in networkConfigurationsByChainId', () => { + const messenger = buildMessenger(); + const restrictedMessenger = buildNetworkControllerMessenger(messenger); + expect( + () => + new NetworkController({ + messenger: restrictedMessenger, + state: { + selectedNetworkClientId: 'nonexistent', + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + }), + }, + }, + infuraProjectId: 'infura-project-id', + }), + ).toThrow( + "NetworkController state is invalid: `selectedNetworkClientId` 'nonexistent' does not refer to an RPC endpoint within a network configuration", + ); + }); + const invalidInfuraProjectIds = [undefined, null, {}, 1]; invalidInfuraProjectIds.forEach((invalidProjectId) => { it(`throws given an invalid Infura ID of "${inspect( @@ -173,6 +278,7 @@ describe('NetworkController', () => { () => new NetworkController({ messenger: restrictedMessenger, + state: {}, // @ts-expect-error We are intentionally passing bad input. infuraProjectId: invalidProjectId, }), @@ -184,7 +290,92 @@ describe('NetworkController', () => { await withController(({ controller }) => { expect(controller.state).toMatchInlineSnapshot(` Object { - "networkConfigurations": Object {}, + "networkConfigurationsByChainId": Object { + "0x1": Object { + "chainId": "0x1", + "defaultRpcEndpointUrl": "https://mainnet.infura.io/v3/{infuraProjectId}", + "name": "Mainnet", + "nativeTokenName": "ETH", + "rpcEndpoints": Array [ + Object { + "name": "Infura Mainnet", + "networkClientId": "mainnet", + "type": "infura", + "url": "https://mainnet.infura.io/v3/{infuraProjectId}", + }, + ], + }, + "0x5": Object { + "chainId": "0x5", + "defaultRpcEndpointUrl": "https://goerli.infura.io/v3/{infuraProjectId}", + "name": "Goerli", + "nativeTokenName": "GoerliETH", + "rpcEndpoints": Array [ + Object { + "name": "Infura Goerli", + "networkClientId": "goerli", + "type": "infura", + "url": "https://goerli.infura.io/v3/{infuraProjectId}", + }, + ], + }, + "0xaa36a7": Object { + "chainId": "0xaa36a7", + "defaultRpcEndpointUrl": "https://sepolia.infura.io/v3/{infuraProjectId}", + "name": "Sepolia", + "nativeTokenName": "SepoliaETH", + "rpcEndpoints": Array [ + Object { + "name": "Infura Sepolia", + "networkClientId": "sepolia", + "type": "infura", + "url": "https://sepolia.infura.io/v3/{infuraProjectId}", + }, + ], + }, + "0xe704": Object { + "chainId": "0xe704", + "defaultRpcEndpointUrl": "https://linea-goerli.infura.io/v3/{infuraProjectId}", + "name": "Linea Goerli", + "nativeTokenName": "LineaETH", + "rpcEndpoints": Array [ + Object { + "name": "Infura Linea Goerli", + "networkClientId": "linea-goerli", + "type": "infura", + "url": "https://linea-goerli.infura.io/v3/{infuraProjectId}", + }, + ], + }, + "0xe705": Object { + "chainId": "0xe705", + "defaultRpcEndpointUrl": "https://linea-sepolia.infura.io/v3/{infuraProjectId}", + "name": "Linea Sepolia", + "nativeTokenName": "LineaETH", + "rpcEndpoints": Array [ + Object { + "name": "Infura Linea Sepolia", + "networkClientId": "linea-sepolia", + "type": "infura", + "url": "https://linea-sepolia.infura.io/v3/{infuraProjectId}", + }, + ], + }, + "0xe708": Object { + "chainId": "0xe708", + "defaultRpcEndpointUrl": "https://linea-mainnet.infura.io/v3/{infuraProjectId}", + "name": "Linea Mainnet", + "nativeTokenName": "ETH", + "rpcEndpoints": Array [ + Object { + "name": "Infura Linea Mainnet", + "networkClientId": "linea-mainnet", + "type": "infura", + "url": "https://linea-mainnet.infura.io/v3/{infuraProjectId}", + }, + ], + }, + }, "networksMetadata": Object {}, "selectedNetworkClientId": "mainnet", } @@ -196,6 +387,24 @@ describe('NetworkController', () => { await withController( { state: { + selectedNetworkClientId: InfuraNetworkType.goerli, + networkConfigurationsByChainId: { + [ChainId.goerli]: { + chainId: ChainId.goerli, + defaultRpcEndpointUrl: + 'https://goerli.infura.io/v3/{infuraProjectId}', + name: 'Goerli', + nativeTokenName: 'GoerliETH', + rpcEndpoints: [ + { + name: 'Goerli', + networkClientId: InfuraNetworkType.goerli, + type: RpcEndpointType.Infura, + url: 'https://goerli.infura.io/v3/{infuraProjectId}', + }, + ], + }, + }, networksMetadata: { mainnet: { EIPS: { 1559: true }, @@ -207,7 +416,22 @@ describe('NetworkController', () => { ({ controller }) => { expect(controller.state).toMatchInlineSnapshot(` Object { - "networkConfigurations": Object {}, + "networkConfigurationsByChainId": Object { + "0x5": Object { + "chainId": "0x5", + "defaultRpcEndpointUrl": "https://goerli.infura.io/v3/{infuraProjectId}", + "name": "Goerli", + "nativeTokenName": "GoerliETH", + "rpcEndpoints": Array [ + Object { + "name": "Goerli", + "networkClientId": "goerli", + "type": "infura", + "url": "https://goerli.infura.io/v3/{infuraProjectId}", + }, + ], + }, + }, "networksMetadata": Object { "mainnet": Object { "EIPS": Object { @@ -216,7 +440,7 @@ describe('NetworkController', () => { "status": "unknown", }, }, - "selectedNetworkClientId": "mainnet", + "selectedNetworkClientId": "goerli", } `); }, @@ -253,45 +477,26 @@ describe('NetworkController', () => { }); describe('initializeProvider', () => { - 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 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}" network, since it is built in`, async () => { - await withController( - { - state: { - selectedNetworkClientId: networkType, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeNetworkClient = buildFakeClient(); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); + for (const infuraNetworkType of Object.values(InfuraNetworkType)) { + const infuraChainId = ChainId[infuraNetworkType]; + const infuraNativeTokenName = NetworksTicker[infuraNetworkType]; - await controller.initializeProvider(); - - expect(createNetworkClientMock).toHaveBeenCalledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - chainId: BUILT_IN_NETWORKS[networkType].chainId, - ticker: BUILT_IN_NETWORKS[networkType].ticker, - }); - expect(createNetworkClientMock).toHaveBeenCalledTimes(1); - }, - ); - }); + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + describe(`when the selected network client represents the Infura network "${infuraNetworkType}"`, () => { + it('sets the globally selected provider to the one from the corresponding network client', async () => { + const infuraProjectId = 'some-infura-project-id'; - it('captures the resulting provider of the matching network client', async () => { await withController( { state: { - selectedNetworkClientId: networkType, + selectedNetworkClientId: infuraNetworkType, + networkConfigurationsByChainId: { + [infuraChainId]: + buildInfuraNetworkConfiguration(infuraNetworkType), + }, }, - infuraProjectId: 'some-infura-project-id', + infuraProjectId, }, async ({ controller }) => { const fakeProvider = buildFakeProvider([ @@ -306,31 +511,37 @@ describe('NetworkController', () => { }, ]); const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); + mockCreateNetworkClient() + .calledWith({ + chainId: infuraChainId, + infuraProjectId, + network: infuraNetworkType, + ticker: infuraNativeTokenName, + type: NetworkClientType.Infura, + }) + .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', - method: 'test_method', - params: [], - }, - ); + const networkClient = controller.getSelectedNetworkClient(); + assert(networkClient, 'Network client not set'); + const { result } = await promisify( + networkClient.provider.sendAsync, + ).call(networkClient.provider, { + id: 1, + jsonrpc: '2.0', + method: 'test_method', + params: [], + }); expect(result).toBe('test response'); }, ); }); lookupNetworkTests({ - expectedNetworkClientConfiguration: - buildInfuraNetworkClientConfiguration(networkType), + expectedNetworkClientType: NetworkClientType.Infura, initialState: { - selectedNetworkClientId: networkType, + selectedNetworkClientId: infuraNetworkType, }, operation: async (controller: NetworkController) => { await controller.initializeProvider(); @@ -339,19 +550,23 @@ describe('NetworkController', () => { }); } - describe('when selectedNetworkClientId in state is the ID of a network configuration', () => { - it('creates a network client using the network configuration', async () => { + describe('when the selected network client represents a custom RPC endpoint', () => { + it('sets the globally selected provider to the one from the corresponding 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', - }, + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeTokenName: 'TEST', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network', + }), + ], + }), }, }, }, @@ -368,67 +583,52 @@ describe('NetworkController', () => { }, ]); const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); + mockCreateNetworkClient() + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://test.network', + ticker: 'TEST', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClient); await controller.initializeProvider(); - expect(createNetworkClientMock).toHaveBeenCalledWith({ - chainId: toHex(1337), - rpcUrl: 'https://test.network.1', - type: NetworkClientType.Custom, - ticker: 'TEST', + const networkClient = controller.getSelectedNetworkClient(); + assert(networkClient, 'Network client not set'); + const { result } = await promisify( + networkClient.provider.sendAsync, + ).call(networkClient.provider, { + id: 1, + jsonrpc: '2.0', + method: 'test_method', + params: [], }); - expect(createNetworkClientMock).toHaveBeenCalledTimes(1); + expect(result).toBe('test response'); }, ); }); - 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', - }, - }, - }, - }, - 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', - method: 'test_method', - params: [], - }, - ); - expect(result).toBe('test response'); + lookupNetworkTests({ + expectedNetworkClientType: NetworkClientType.Custom, + initialState: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeTokenName: 'TEST', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network', + }), + ], + }), }, - ); + }, + operation: async (controller: NetworkController) => { + await controller.initializeProvider(); + }, }); }); }); @@ -459,34 +659,46 @@ describe('NetworkController', () => { }); }); - for (const { networkType } of INFURA_NETWORKS) { - // TODO: Either fix this lint violation or explain why it's necessary to ignore. + for (const infuraNetworkType of Object.values(InfuraNetworkType)) { + const infuraChainId = ChainId[infuraNetworkType]; + const infuraNetworkNickname = NetworkNickname[infuraNetworkType]; + const infuraNativeTokenName = NetworksTicker[infuraNetworkType]; + + // False negative - this is a string. // 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. + describe(`when the selectedNetworkClientId is changed to represent the Infura network "${infuraNetworkType}"`, () => { + // False negative - this is a string. // 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 () => { + it(`returns a provider object that was pointed to another network before the switch and is now pointed to ${infuraNetworkNickname} afterward`, async () => { + const infuraProjectId = 'some-infura-project-id'; + await withController( { state: { 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', - }, + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeTokenName: 'TEST', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network', + }), + ], + }), + [infuraChainId]: + buildInfuraNetworkConfiguration(infuraNetworkType), }, }, - infuraProjectId: 'some-infura-project-id', + infuraProjectId, }, async ({ controller }) => { const fakeProviders = [ buildFakeProvider([ { request: { - method: 'test', + method: 'test_method', }, response: { result: 'test response 1', @@ -496,7 +708,7 @@ describe('NetworkController', () => { buildFakeProvider([ { request: { - method: 'test', + method: 'test_method', }, response: { result: 'test response 2', @@ -510,23 +722,23 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, + chainId: '0x1337', + rpcUrl: 'https://test.network', ticker: 'TEST', + type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[0]) .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', + chainId: infuraChainId, + infuraProjectId, + network: infuraNetworkType, + ticker: infuraNativeTokenName, 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'); + assert(provider, 'Provider not set'); const promisifiedSendAsync1 = promisify(provider.sendAsync).bind( provider, @@ -534,18 +746,18 @@ describe('NetworkController', () => { const response1 = await promisifiedSendAsync1({ id: '1', jsonrpc: '2.0', - method: 'test', + method: 'test_method', }); expect(response1.result).toBe('test response 1'); - await controller.setProviderType(networkType); + await controller.setActiveNetwork(infuraNetworkType); const promisifiedSendAsync2 = promisify(provider.sendAsync).bind( provider, ); const response2 = await promisifiedSendAsync2({ id: '2', jsonrpc: '2.0', - method: 'test', + method: 'test_method', }); expect(response2.result).toBe('test response 2'); }, @@ -554,29 +766,38 @@ describe('NetworkController', () => { }); } - 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 () => { + describe('when the selectedNetworkClientId is changed to represent a custom RPC endpoint', () => { + it('returns a provider object that was pointed to another network before the switch and is now pointed to the new network', async () => { + const infuraProjectId = 'some-infura-project-id'; + await withController( { state: { - selectedNetworkClientId: 'goerli', - networkConfigurations: { - testNetworkConfigurationId: { - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'ABC', - id: 'testNetworkConfigurationId', - }, + selectedNetworkClientId: InfuraNetworkType.goerli, + networkConfigurationsByChainId: { + [ChainId.goerli]: buildInfuraNetworkConfiguration( + InfuraNetworkType.goerli, + ), + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeTokenName: 'TEST', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network', + }), + ], + }), }, }, - infuraProjectId: 'some-infura-project-id', + infuraProjectId, }, async ({ controller }) => { const fakeProviders = [ buildFakeProvider([ { request: { - method: 'test', + method: 'test_method', }, response: { result: 'test response 1', @@ -586,7 +807,7 @@ describe('NetworkController', () => { buildFakeProvider([ { request: { - method: 'test', + method: 'test_method', }, response: { result: 'test response 2', @@ -600,23 +821,23 @@ describe('NetworkController', () => { ]; 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, + infuraProjectId, + network: InfuraNetworkType.goerli, + ticker: NetworksTicker[InfuraNetworkType.goerli], + type: NetworkClientType.Infura, }) .mockReturnValue(fakeNetworkClients[0]) .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', + rpcUrl: 'https://test.network', + ticker: 'TEST', type: NetworkClientType.Custom, - ticker: 'ABC', }) .mockReturnValue(fakeNetworkClients[1]); await controller.initializeProvider(); const { provider } = controller.getProviderAndBlockTracker(); - assert(provider, 'Provider is somehow unset'); + assert(provider, 'Provider not set'); const promisifiedSendAsync1 = promisify(provider.sendAsync).bind( provider, @@ -624,18 +845,18 @@ describe('NetworkController', () => { const response1 = await promisifiedSendAsync1({ id: '1', jsonrpc: '2.0', - method: 'test', + method: 'test_method', }); expect(response1.result).toBe('test response 1'); - await controller.setActiveNetwork('testNetworkConfigurationId'); + await controller.setActiveNetwork('AAAA-AAAA-AAAA-AAAA'); const promisifiedSendAsync2 = promisify(provider.sendAsync).bind( provider, ); const response2 = await promisifiedSendAsync2({ id: '2', jsonrpc: '2.0', - method: 'test', + method: 'test_method', }); expect(response2.result).toBe('test response 2'); }, @@ -690,342 +911,268 @@ describe('NetworkController', () => { }); 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 passed an Infura network client ID', () => { + describe('if the ID refers to an existing Infura network client', () => { + it('returns the network client', async () => { + const infuraProjectId = 'some-infura-project-id'; - const networkClientRegistry = controller.getNetworkClientRegistry(); - const networkClient = controller.getNetworkClientById( - NetworkType.mainnet, - ); + await withController( + { + infuraProjectId, + }, + async ({ controller }) => { + const networkClient = controller.getNetworkClientById( + NetworkType.mainnet, + ); - expect(networkClient).toBe( - networkClientRegistry[NetworkType.mainnet], - ); - }, - ); + expect(networkClient.configuration).toStrictEqual({ + chainId: ChainId[InfuraNetworkType.mainnet], + infuraProjectId, + network: InfuraNetworkType.mainnet, + ticker: NetworksTicker[InfuraNetworkType.mainnet], + type: NetworkClientType.Infura, + }); + }, + ); + }); }); - 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, - ); + describe('if the ID does not refer to an existing Infura network client', () => { + it('throws', async () => { + const infuraProjectId = 'some-infura-project-id'; - expect(networkClient.configuration.chainId).toBe('0x1'); - expect(networkClientRegistry.mainnet.configuration.chainId).toBe( - '0x1', - ); - }, - ); + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration(), + }, + }), + infuraProjectId, + }, + async ({ controller }) => { + expect(() => + controller.getNetworkClientById(NetworkType.mainnet), + ).toThrow( + 'No Infura network client was found with the ID "mainnet".', + ); + }, + ); + }); }); + }); - it('returns a valid custom NetworkClient', async () => { - await withController( - { - state: { - networkConfigurations: { - testNetworkConfigurationId: { - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - ticker: 'ABC', - id: 'testNetworkConfigurationId', - }, - }, + describe('if passed a custom network client ID', () => { + describe('if the ID refers to an existing custom network client', () => { + it('returns the network client', async () => { + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeTokenName: 'TEST', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network', + }), + ], + }), + }, + }), + infuraProjectId: 'some-infura-project-id', }, - 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); + async ({ controller }) => { + const networkClient = controller.getNetworkClientById( + 'AAAA-AAAA-AAAA-AAAA', + ); - expect(() => - controller.getNetworkClientById('non-existent-network-id'), - ).toThrow( - 'No custom network client was found with the ID "non-existent-network-id', - ); - }, - ); + expect(networkClient.configuration).toStrictEqual({ + chainId: '0x1337', + rpcUrl: 'https://test.network', + ticker: 'TEST', + type: NetworkClientType.Custom, + }); + }, + ); + }); }); - }); - - 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('if the ID does not refer to an existing custom network client', () => { + it('throws', async () => { + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x2448': buildCustomNetworkConfiguration({ + chainId: '0x2448', + }), + }, + }), + }, + async ({ controller }) => { + expect(() => controller.getNetworkClientById('0x1337')).toThrow( + 'No custom network client was found with the ID "0x1337".', + ); + }, + ); + }); }); }); }); describe('getNetworkClientRegistry', () => { - describe('if no network configurations are present in state', () => { - it('returns the built-in Infura networks by default', async () => { + describe('if no network configurations were specified at initialization', () => { + it('returns network clients for Infura RPC endpoints, keyed by network client ID', async () => { + const infuraProjectId = 'some-infura-project-id'; + await withController( - { infuraProjectId: 'some-infura-project-id' }, + { + infuraProjectId, + }, 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); - }, - ); + mockCreateNetworkClient().mockReturnValue(buildFakeClient()); - 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, + expect(controller.getNetworkClientRegistry()).toStrictEqual({ + goerli: { + blockTracker: expect.anything(), + configuration: { + chainId: '0x5', + infuraProjectId, network: InfuraNetworkType.goerli, + ticker: 'GoerliETH', + type: NetworkClientType.Infura, }, - ], - [ - 'linea-goerli', - { + provider: expect.anything(), + destroy: expect.any(Function), + }, + 'linea-goerli': { + blockTracker: expect.anything(), + configuration: { type: NetworkClientType.Infura, - infuraProjectId: 'some-infura-project-id', - chainId: - BUILT_IN_NETWORKS[NetworkType['linea-goerli']].chainId, - ticker: BUILT_IN_NETWORKS[NetworkType['linea-goerli']].ticker, + infuraProjectId, + chainId: '0xe704', + ticker: 'LineaETH', network: InfuraNetworkType['linea-goerli'], }, - ], - [ - 'linea-mainnet', - { + provider: expect.anything(), + destroy: expect.any(Function), + }, + 'linea-mainnet': { + blockTracker: expect.anything(), + configuration: { type: NetworkClientType.Infura, - infuraProjectId: 'some-infura-project-id', - chainId: - BUILT_IN_NETWORKS[NetworkType['linea-mainnet']].chainId, - ticker: - BUILT_IN_NETWORKS[NetworkType['linea-mainnet']].ticker, + infuraProjectId, + chainId: '0xe708', + ticker: 'ETH', network: InfuraNetworkType['linea-mainnet'], }, - ], - [ - 'linea-sepolia', - { + provider: expect.anything(), + destroy: expect.any(Function), + }, + 'linea-sepolia': { + blockTracker: expect.anything(), + configuration: { type: NetworkClientType.Infura, - infuraProjectId: 'some-infura-project-id', - chainId: - BUILT_IN_NETWORKS[NetworkType['linea-sepolia']].chainId, - ticker: - BUILT_IN_NETWORKS[NetworkType['linea-sepolia']].ticker, + infuraProjectId, + chainId: '0xe705', + ticker: 'LineaETH', network: InfuraNetworkType['linea-sepolia'], }, - ], - [ - 'mainnet', - { + provider: expect.anything(), + destroy: expect.any(Function), + }, + mainnet: { + blockTracker: expect.anything(), + configuration: { type: NetworkClientType.Infura, - infuraProjectId: 'some-infura-project-id', - chainId: BUILT_IN_NETWORKS[NetworkType.mainnet].chainId, - ticker: BUILT_IN_NETWORKS[NetworkType.mainnet].ticker, + infuraProjectId, + chainId: '0x1', + ticker: 'ETH', network: InfuraNetworkType.mainnet, }, - ], - [ - 'sepolia', - { + provider: expect.anything(), + destroy: expect.any(Function), + }, + sepolia: { + blockTracker: expect.anything(), + configuration: { type: NetworkClientType.Infura, - infuraProjectId: 'some-infura-project-id', - chainId: BUILT_IN_NETWORKS[NetworkType.sepolia].chainId, - ticker: BUILT_IN_NETWORKS[NetworkType.sepolia].ticker, + infuraProjectId, + chainId: '0xaa36a7', + ticker: 'SepoliaETH', network: InfuraNetworkType.sepolia, }, - ], - ]); + provider: expect.anything(), + destroy: expect.any(Function), + }, + }); }, ); }); }); - 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('if some network configurations were specified at initialization', () => { + it('returns network clients for all RPC endpoints within any defined network configurations, keyed by network client ID, and does not include Infura-supported chains by default', 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', + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeTokenName: 'TOKEN1', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network/1', + }), + ], + }), + '0x2448': buildCustomNetworkConfiguration({ + chainId: '0x2448', + nativeTokenName: 'TOKEN2', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://test.network/2', + }), + ], + }), }, - }, - }, - 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); - }, - ); + mockCreateNetworkClient().mockReturnValue(buildFakeClient()); - expect(simplifiedNetworkClients).toStrictEqual([ - [ - 'AAAA-AAAA-AAAA-AAAA', - { + expect(controller.getNetworkClientRegistry()).toStrictEqual({ + 'AAAA-AAAA-AAAA-AAAA': { + blockTracker: expect.anything(), + configuration: { + chainId: '0x1337', + rpcUrl: 'https://test.network/1', + ticker: 'TOKEN1', type: NetworkClientType.Custom, - ticker: 'TEST1', - chainId: toHex(1), - rpcUrl: 'https://test.network.1', }, - ], - [ - 'BBBB-BBBB-BBBB-BBBB', - { + provider: expect.anything(), + destroy: expect.any(Function), + }, + 'BBBB-BBBB-BBBB-BBBB': { + blockTracker: expect.anything(), + configuration: { + chainId: '0x2448', + rpcUrl: 'https://test.network/2', + ticker: 'TOKEN2', type: NetworkClientType.Custom, - ticker: 'TEST2', - chainId: toHex(2), - rpcUrl: 'https://test.network.2', - }, - ], - [ - 'goerli', - { - type: NetworkClientType.Infura, - 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', - ); - } + provider: expect.anything(), + destroy: expect.any(Function), + }, + }); }, ); }); @@ -1048,6 +1195,7 @@ describe('NetworkController', () => { }, ); }); + it('throws an error if the network is not found', async () => { await withController( { infuraProjectId: 'some-infura-project-id' }, @@ -1062,351 +1210,353 @@ describe('NetworkController', () => { }); }); - [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( - { - state: { - selectedNetworkClientId: networkType, - networkConfigurations: { - testNetworkConfigurationId: { - id: 'testNetworkConfigurationId', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'ABC', - }, - }, + for (const infuraNetworkType of Object.values(InfuraNetworkType)) { + const infuraChainId = ChainId[infuraNetworkType]; + + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + describe(`when the selected network client represents the Infura network "${infuraNetworkType}"`, () => { + 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 () => { + const infuraProjectId = 'some-infura-project-id'; + + await withController( + { + state: { + selectedNetworkClientId: infuraNetworkType, + networkConfigurationsByChainId: { + [infuraChainId]: + buildInfuraNetworkConfiguration(infuraNetworkType), + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeTokenName: 'TEST', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network', + }), + ], + }), }, - infuraProjectId: 'some-infura-project-id', }, - async ({ controller, messenger }) => { - const fakeProviders = [ - buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + infuraProjectId, + }, + async ({ controller }) => { + const fakeProviders = [ + buildFakeProvider([ + // Called during provider initialization + { + request: { + method: 'eth_getBlockByNumber', }, - // 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', - ); - }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + }, + // Called via `lookupNetwork` directly + { + request: { + method: 'eth_getBlockByNumber', }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'eth_getBlockByNumber', - }, - error: GENERIC_JSON_RPC_ERROR, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + beforeCompleting: () => { + // We are purposefully not awaiting this promise. + // eslint-disable-next-line @typescript-eslint/no-floating-promises + controller.setActiveNetwork('AAAA-AAAA-AAAA-AAAA'); }, - ]), - ]; - 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'); - - 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: { - selectedNetworkClientId: networkType, - networkConfigurations: { - testNetworkConfigurationId: { - id: 'testNetworkConfigurationId', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'ABC', + ]), + buildFakeProvider([ + // Called when switching networks + { + request: { + method: 'eth_getBlockByNumber', }, + error: GENERIC_JSON_RPC_ERROR, }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + chainId: ChainId[infuraNetworkType], + infuraProjectId, + network: infuraNetworkType, + ticker: NetworksTicker[infuraNetworkType], + type: NetworkClientType.Infura, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://test.network', + ticker: 'TEST', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); + expect( + controller.state.networksMetadata[infuraNetworkType].status, + ).toBe('available'); + + await controller.lookupNetwork(); + + expect( + controller.state.networksMetadata['AAAA-AAAA-AAAA-AAAA'] + .status, + ).toBe('unknown'); + }, + ); + }); + + it('stores the EIP-1559 support of the second network, not the first', async () => { + const infuraProjectId = 'some-infura-project-id'; + + await withController( + { + state: { + selectedNetworkClientId: infuraNetworkType, + networkConfigurationsByChainId: { + [infuraChainId]: + buildInfuraNetworkConfiguration(infuraNetworkType), + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeTokenName: 'TEST', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network', + }), + ], + }), }, - 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', - ); - }, + infuraProjectId, + }, + async ({ controller }) => { + const fakeProviders = [ + buildFakeProvider([ + // Called during provider initialization + { + request: { + method: 'eth_getBlockByNumber', }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: PRE_1559_BLOCK, - }, + response: { + result: POST_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', - ], - operation: async () => { - await controller.lookupNetwork(); }, - }); - - expect( - controller.state.networksMetadata.testNetworkConfigurationId - .EIPS[1559], - ).toBe(false); - }, - ); - }); - - it('emits infuraIsUnblocked, not infuraIsBlocked, assuming that the first network was blocked', async () => { - await withController( - { - state: { - selectedNetworkClientId: networkType, - networkConfigurations: { - testNetworkConfigurationId: { - id: 'testNetworkConfigurationId', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'ABC', + // Called via `lookupNetwork` directly + { + request: { + method: 'eth_getBlockByNumber', }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller, messenger }) => { - const fakeProviders = [ - buildFakeProvider([ - // Called during provider initialization - { - 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', - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - 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', - ); - }, + beforeCompleting: () => { + // We are purposefully not awaiting this promise. + // eslint-disable-next-line @typescript-eslint/no-floating-promises + controller.setActiveNetwork('AAAA-AAAA-AAAA-AAAA'); }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + }, + ]), + 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', - chainId: BUILT_IN_NETWORKS[networkType].chainId, - ticker: BUILT_IN_NETWORKS[networkType].ticker, - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - ticker: 'ABC', - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - const promiseForInfuraIsUnblockedEvents = - waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - }); - const promiseForNoInfuraIsBlockedEvents = - waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - count: 0, - }); - - await waitForStateChanges({ - messenger, - propertyPath: [ - 'networksMetadata', - 'testNetworkConfigurationId', - 'status', - ], - operation: async () => { - await controller.lookupNetwork(); }, - }); - - await expect( - promiseForInfuraIsUnblockedEvents, - ).toBeFulfilled(); - await expect( - promiseForNoInfuraIsBlockedEvents, - ).toBeFulfilled(); - }, - ); - }); - }); - - lookupNetworkTests({ - expectedNetworkClientConfiguration: - buildInfuraNetworkClientConfiguration(networkType), - initialState: { - selectedNetworkClientId: networkType, - }, - operation: async (controller) => { - await controller.lookupNetwork(); - }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + chainId: ChainId[infuraNetworkType], + infuraProjectId, + network: infuraNetworkType, + ticker: NetworksTicker[infuraNetworkType], + type: NetworkClientType.Infura, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://test.network', + ticker: 'TEST', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); + expect( + controller.state.networksMetadata[infuraNetworkType] + .EIPS[1559], + ).toBe(true); + + await controller.lookupNetwork(); + + expect( + controller.state.networksMetadata['AAAA-AAAA-AAAA-AAAA'] + .EIPS[1559], + ).toBe(false); + }, + ); + }); + + it('emits infuraIsUnblocked, not infuraIsBlocked, assuming that the first network was blocked', async () => { + const infuraProjectId = 'some-infura-project-id'; + + await withController( + { + state: { + selectedNetworkClientId: infuraNetworkType, + networkConfigurationsByChainId: { + [infuraChainId]: + buildInfuraNetworkConfiguration(infuraNetworkType), + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeTokenName: 'TEST', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network', + }), + ], + }), + }, + }, + infuraProjectId, + }, + 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', + }, + error: BLOCKED_INFURA_JSON_RPC_ERROR, + beforeCompleting: () => { + // We are purposefully not awaiting this promise. + // eslint-disable-next-line @typescript-eslint/no-floating-promises + controller.setActiveNetwork('AAAA-AAAA-AAAA-AAAA'); + }, + }, + ]), + buildFakeProvider([ + // Called when switching networks + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + chainId: ChainId[infuraNetworkType], + infuraProjectId, + network: infuraNetworkType, + ticker: NetworksTicker[infuraNetworkType], + type: NetworkClientType.Infura, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://test.network', + ticker: 'TEST', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); + const promiseForInfuraIsUnblockedEvents = + waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsUnblocked', + }); + const promiseForNoInfuraIsBlockedEvents = + waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsBlocked', + count: 0, + }); + + await waitForStateChanges({ + messenger, + propertyPath: [ + 'networksMetadata', + 'AAAA-AAAA-AAAA-AAAA', + 'status', + ], + operation: async () => { + await controller.lookupNetwork(); + }, + }); + + await expect(promiseForInfuraIsUnblockedEvents).toBeFulfilled(); + await expect(promiseForNoInfuraIsBlockedEvents).toBeFulfilled(); + }, + ); }); }); - }, - ); - describe('when selectedNetworkClientId in state is the ID of a network configuration', () => { + lookupNetworkTests({ + expectedNetworkClientType: NetworkClientType.Infura, + initialState: { + selectedNetworkClientId: infuraNetworkType, + }, + operation: async (controller) => { + await controller.lookupNetwork(); + }, + }); + }); + } + + describe('when the selected network client represents a custom RPC endpoint', () => { 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 () => { + const infuraProjectId = 'some-infura-project-id'; + await withController( { state: { 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', - }, + networkConfigurationsByChainId: { + [ChainId.goerli]: buildInfuraNetworkConfiguration( + InfuraNetworkType.goerli, + ), + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeTokenName: 'TEST', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network', + }), + ], + }), }, }, - infuraProjectId: 'some-infura-project-id', + infuraProjectId, }, - async ({ controller, messenger }) => { + async ({ controller }) => { const fakeProviders = [ buildFakeProvider([ // Called during provider initialization @@ -1423,7 +1573,7 @@ describe('NetworkController', () => { }, response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, beforeCompleting: () => { - // TODO: Either fix this lint violation or explain why it's necessary to ignore. + // We are purposefully not awaiting this promise. // eslint-disable-next-line @typescript-eslint/no-floating-promises controller.setProviderType(NetworkType.goerli); }, @@ -1445,17 +1595,17 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, + chainId: '0x1337', + rpcUrl: 'https://test.network', ticker: 'TEST', + type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[0]) .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - chainId: BUILT_IN_NETWORKS[NetworkType.goerli].chainId, - ticker: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].ticker, + chainId: ChainId[InfuraNetworkType.goerli], + infuraProjectId, + network: InfuraNetworkType.goerli, + ticker: NetworksTicker[InfuraNetworkType.goerli], type: NetworkClientType.Infura, }) .mockReturnValue(fakeNetworkClients[1]); @@ -1464,42 +1614,42 @@ describe('NetworkController', () => { controller.state.networksMetadata['AAAA-AAAA-AAAA-AAAA'].status, ).toBe('available'); - await waitForStateChanges({ - messenger, - propertyPath: [ - 'networksMetadata', - NetworkType.goerli, - 'status', - ], - operation: async () => { - await controller.lookupNetwork(); - }, - }); + await controller.lookupNetwork(); expect( - controller.state.networksMetadata[NetworkType.goerli].status, + controller.state.networksMetadata[InfuraNetworkType.goerli] + .status, ).toBe('unknown'); }, ); }); it('stores the EIP-1559 support of the second network, not the first', async () => { + const infuraProjectId = 'some-infura-project-id'; + await withController( { state: { 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', - }, + networkConfigurationsByChainId: { + [ChainId.goerli]: buildInfuraNetworkConfiguration( + InfuraNetworkType.goerli, + ), + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeTokenName: 'TEST', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network', + }), + ], + }), }, }, - infuraProjectId: 'some-infura-project-id', + infuraProjectId, }, - async ({ controller, messenger }) => { + async ({ controller }) => { const fakeProviders = [ buildFakeProvider([ // Called during provider initialization @@ -1520,7 +1670,7 @@ describe('NetworkController', () => { result: POST_1559_BLOCK, }, beforeCompleting: () => { - // TODO: Either fix this lint violation or explain why it's necessary to ignore. + // We are purposefully not awaiting this promise. // eslint-disable-next-line @typescript-eslint/no-floating-promises controller.setProviderType(NetworkType.goerli); }, @@ -1544,17 +1694,17 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, + chainId: '0x1337', + rpcUrl: 'https://test.network', ticker: 'TEST', + type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[0]) .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - chainId: BUILT_IN_NETWORKS[NetworkType.goerli].chainId, - ticker: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].ticker, + chainId: ChainId[InfuraNetworkType.goerli], + infuraProjectId, + network: InfuraNetworkType.goerli, + ticker: NetworksTicker[InfuraNetworkType.goerli], type: NetworkClientType.Infura, }) .mockReturnValue(fakeNetworkClients[1]); @@ -1564,13 +1714,7 @@ describe('NetworkController', () => { .EIPS[1559], ).toBe(true); - await waitForStateChanges({ - messenger, - propertyPath: ['networksMetadata', NetworkType.goerli, 'EIPS'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); + await controller.lookupNetwork(); expect( controller.state.networksMetadata[NetworkType.goerli] @@ -1585,20 +1729,29 @@ describe('NetworkController', () => { }); it('emits infuraIsBlocked, not infuraIsUnblocked, if the second network was blocked and the first network was not', async () => { + const infuraProjectId = 'some-infura-project-id'; + await withController( { state: { 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', - }, + networkConfigurationsByChainId: { + [ChainId.goerli]: buildInfuraNetworkConfiguration( + InfuraNetworkType.goerli, + ), + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeTokenName: 'TEST', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network', + }), + ], + }), }, }, - infuraProjectId: 'some-infura-project-id', + infuraProjectId, }, async ({ controller, messenger }) => { const fakeProviders = [ @@ -1617,7 +1770,7 @@ describe('NetworkController', () => { }, response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, beforeCompleting: () => { - // TODO: Either fix this lint violation or explain why it's necessary to ignore. + // We are purposefully not awaiting this promise. // eslint-disable-next-line @typescript-eslint/no-floating-promises controller.setProviderType(NetworkType.goerli); }, @@ -1639,17 +1792,17 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, + chainId: '0x1337', + rpcUrl: 'https://test.network', ticker: 'TEST', + type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[0]) .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - chainId: BUILT_IN_NETWORKS[NetworkType.goerli].chainId, - ticker: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].ticker, + chainId: ChainId[InfuraNetworkType.goerli], + infuraProjectId, + network: InfuraNetworkType.goerli, + ticker: NetworksTicker[InfuraNetworkType.goerli], type: NetworkClientType.Infura, }) .mockReturnValue(fakeNetworkClients[1]); @@ -1665,17 +1818,7 @@ describe('NetworkController', () => { eventType: 'NetworkController:infuraIsBlocked', }); - await waitForStateChanges({ - messenger, - propertyPath: [ - 'networksMetadata', - NetworkType.goerli, - 'status', - ], - operation: async () => { - await controller.lookupNetwork(); - }, - }); + await controller.lookupNetwork(); await expect(promiseForNoInfuraIsUnblockedEvents).toBeFulfilled(); await expect(promiseForInfuraIsBlockedEvents).toBeFulfilled(); @@ -1685,17 +1828,20 @@ describe('NetworkController', () => { }); lookupNetworkTests({ - expectedNetworkClientConfiguration: - buildCustomNetworkClientConfiguration(), + expectedNetworkClientType: NetworkClientType.Custom, 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', - }, + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeTokenName: 'TEST', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network', + }), + ], + }), }, }, operation: async (controller) => { @@ -1706,50 +1852,35 @@ describe('NetworkController', () => { }); describe('setProviderType', () => { - for (const { networkType } of INFURA_NETWORKS) { - // TODO: Either fix this lint violation or explain why it's necessary to ignore. + for (const infuraNetworkType of Object.values(InfuraNetworkType)) { + // False negative - this is a string. // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - describe(`given the Infura network "${networkType}"`, () => { + describe(`given the Infura network "${infuraNetworkType}"`, () => { refreshNetworkTests({ expectedNetworkClientConfiguration: - buildInfuraNetworkClientConfiguration(networkType), + buildInfuraNetworkClientConfiguration(infuraNetworkType), operation: async (controller) => { - await controller.setProviderType(networkType); + await controller.setProviderType(infuraNetworkType); }, }); }); - // TODO: Either fix this lint violation or explain why it's necessary to ignore. + // False negative - this is a string. // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - it(`sets selectedNetworkClientId in state to the Infura network "${networkType}"`, async () => { - await withController( - { - state: { - 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', - }, - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + it(`sets selectedNetworkClientId in state to "${infuraNetworkType}"`, async () => { + await withController(async ({ controller }) => { + mockCreateNetworkClient().mockReturnValue(buildFakeClient()); - await controller.setProviderType(networkType); + await controller.setProviderType(infuraNetworkType); - expect(controller.state.selectedNetworkClientId).toBe(networkType); - }, - ); + expect(controller.state.selectedNetworkClientId).toBe( + infuraNetworkType, + ); + }); }); } - describe('given the ID of a network configuration', () => { + describe('given "rpc"', () => { it('throws because there is no way to switch to a custom RPC endpoint using this method', async () => { await withController( { @@ -1855,90 +1986,121 @@ describe('NetworkController', () => { }); describe('setActiveNetwork', () => { - refreshNetworkTests({ - expectedNetworkClientConfiguration: buildCustomNetworkClientConfiguration( - { - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - }, - ), - initialState: { - networkConfigurations: { - testNetworkConfigurationId: { - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - nickname: 'something existing', - id: 'testNetworkConfigurationId', - rpcPrefs: undefined, - }, - }, - }, - operation: async (controller) => { - await controller.setActiveNetwork('testNetworkConfigurationId'); - }, + describe('if the given ID does not refer to an existing network client', () => { + it('throws', async () => { + await withController(async ({ controller }) => { + await expect(() => + controller.setActiveNetwork('invalid-network-client-id'), + ).rejects.toThrow( + new Error( + "No network client found with ID 'invalid-network-client-id'", + ), + ); + }); + }); }); - 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: { - 'AAAA-AAAA-AAAA-AAAA': { - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - id: 'AAAA-AAAA-AAAA-AAAA', - }, - }, + for (const infuraNetworkType of Object.values(InfuraNetworkType)) { + const infuraChainId = ChainId[infuraNetworkType]; + + // False negative - 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 "${infuraNetworkType}"`, () => { + refreshNetworkTests({ + expectedNetworkClientConfiguration: + buildInfuraNetworkClientConfiguration(infuraNetworkType), + initialState: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + [infuraChainId]: + buildInfuraNetworkConfiguration(infuraNetworkType), + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + }), + ], + }), }, }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + operation: async (controller) => { + await controller.setActiveNetwork(infuraNetworkType); + }, + }); - await expect(() => - controller.setActiveNetwork('invalidNetworkClientId'), - ).rejects.toThrow( - new Error( - "Custom network client not found with ID 'invalidNetworkClientId'", - ), + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + it(`sets selectedNetworkClientId in state to "${infuraNetworkType}"`, async () => { + await withController({}, async ({ controller }) => { + mockCreateNetworkClient().mockReturnValue(buildFakeClient()); + + await controller.setActiveNetwork(infuraNetworkType); + + expect(controller.state.selectedNetworkClientId).toStrictEqual( + infuraNetworkType, ); + }); + }); + }); + } + + describe('if the ID refers to a custom network client', () => { + refreshNetworkTests({ + expectedNetworkClientConfiguration: + buildCustomNetworkClientConfiguration({ + rpcUrl: 'https://test.network', + chainId: '0x1337', + ticker: 'TEST', + }), + initialState: { + selectedNetworkClientId: InfuraNetworkType.mainnet, + networkConfigurationsByChainId: { + [ChainId.mainnet]: buildInfuraNetworkConfiguration( + InfuraNetworkType.mainnet, + ), + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeTokenName: 'TEST', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network', + }), + ], + }), }, - ); + }, + operation: async (controller) => { + await controller.setActiveNetwork('AAAA-AAAA-AAAA-AAAA'); + }, }); - }); - 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( { state: { - networkConfigurations: { - [testNetworkClientId]: { - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - id: testNetworkClientId, - }, + selectedNetworkClientId: InfuraNetworkType.mainnet, + networkConfigurationsByChainId: { + [ChainId.mainnet]: buildInfuraNetworkConfiguration( + InfuraNetworkType.mainnet, + ), + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeTokenName: 'TEST', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network', + }), + ], + }), }, }, }, 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); + mockCreateNetworkClient().mockReturnValue(buildFakeClient()); await controller.setActiveNetwork(testNetworkClientId); @@ -1950,74 +2112,38 @@ describe('NetworkController', () => { }); }); - 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); - }, - }); - - // 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); - 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', - }, - }, + selectedNetworkClientId: InfuraNetworkType.mainnet, + networkConfigurationsByChainId: { + [ChainId.mainnet]: buildInfuraNetworkConfiguration( + InfuraNetworkType.mainnet, + ), + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeTokenName: 'TEST', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network', + }), + ], + }), }, }, }, 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); + mockCreateNetworkClient().mockReturnValue(buildFakeClient()); await messenger.call( 'NetworkController:setActiveNetwork', - testNetworkClientId, + 'AAAA-AAAA-AAAA-AAAA', ); - expect(controller.state.selectedNetworkClientId).toStrictEqual( - testNetworkClientId, + expect(controller.state.selectedNetworkClientId).toBe( + 'AAAA-AAAA-AAAA-AAAA', ); }, ); @@ -2396,42 +2522,44 @@ describe('NetworkController', () => { }); describe('resetConnection', () => { - [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, - }, - operation: async (controller) => { - await controller.resetConnection(); - }, - }); + for (const infuraNetworkType of Object.values(InfuraNetworkType)) { + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + describe(`when the selected network client represents the Infura network "${infuraNetworkType}"`, () => { + refreshNetworkTests({ + expectedNetworkClientConfiguration: + buildInfuraNetworkClientConfiguration(infuraNetworkType), + initialState: { + selectedNetworkClientId: infuraNetworkType, + }, + operation: async (controller) => { + await controller.resetConnection(); + }, }); - }, - ); + }); + } - describe('when selectedNetworkClientId in state is the ID of a network configuration', () => { + describe('when the selected network client represents a custom RPC endpoint', () => { refreshNetworkTests({ expectedNetworkClientConfiguration: buildCustomNetworkClientConfiguration({ - rpcUrl: 'https://test.network.1', - chainId: toHex(1337), + rpcUrl: 'https://test.network', + chainId: '0x1337', ticker: 'TEST', }), initialState: { 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', - }, + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeTokenName: 'TEST', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network', + }), + ], + }), }, }, operation: async (controller) => { @@ -2483,6 +2611,123 @@ describe('NetworkController', () => { }); }); + for (const [name, getNetworkConfigurationByChainId] of [ + [ + 'getNetworkConfigurationByChainId', + ({ + controller, + chainId, + }: { + controller: NetworkController; + chainId: Hex; + }) => controller.getNetworkConfigurationByChainId(chainId), + ], + [ + 'NetworkController:getNetworkConfigurationByChainId', + ({ + messenger, + chainId, + }: { + messenger: ControllerMessenger< + NetworkControllerActions, + NetworkControllerEvents + >; + chainId: Hex; + }) => + messenger.call( + 'NetworkController:getNetworkConfigurationByChainId', + chainId, + ), + ], + ] as const) { + // This is a string! + // eslint-disable-next-line jest/valid-title + describe(name, () => { + for (const infuraNetworkType of Object.values(InfuraNetworkType)) { + const infuraChainId = ChainId[infuraNetworkType]; + + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + describe(`given the ID of the Infura-supported chain "${infuraNetworkType}" that a network configuration is filed under`, () => { + it('returns the network configuration', async () => { + const registeredNetworkConfiguration = + buildInfuraNetworkConfiguration(infuraNetworkType); + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId( + { + networkConfigurationsByChainId: { + [infuraChainId]: registeredNetworkConfiguration, + }, + }, + ), + }, + ({ controller, messenger }) => { + const returnedNetworkConfiguration = + getNetworkConfigurationByChainId({ + controller, + messenger, + chainId: infuraChainId, + }); + + expect(returnedNetworkConfiguration).toBe( + registeredNetworkConfiguration, + ); + }, + ); + }); + }); + } + + describe('given the ID of a non-Infura-supported chain that a network configuration is filed under', () => { + it('returns the network configuration', async () => { + const registeredNetworkConfiguration = + buildCustomNetworkConfiguration({ + chainId: '0x1337', + }); + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': registeredNetworkConfiguration, + }, + }), + }, + ({ controller, messenger }) => { + const returnedNetworkConfiguration = + getNetworkConfigurationByChainId({ + controller, + messenger, + chainId: '0x1337', + }); + + expect(returnedNetworkConfiguration).toBe( + registeredNetworkConfiguration, + ); + }, + ); + }); + }); + + describe('given the ID of a chain that no network configuration is filed under', () => { + it('returns undefined', async () => { + await withController(({ controller, messenger }) => { + const returnedNetworkConfiguration = + getNetworkConfigurationByChainId({ + controller, + messenger, + chainId: '0x9999999999999', + }); + + expect(returnedNetworkConfiguration).toBeUndefined(); + }); + }); + }); + }); + } + for (const [name, getNetworkConfigurationByNetworkClientId] of [ [ 'getNetworkConfigurationByNetworkClientId', @@ -2515,1355 +2760,4687 @@ describe('NetworkController', () => { ] as const) { // This is a string! // eslint-disable-next-line jest/valid-title - describe(String(name), () => { - const infuraProjectId = 'some-infura-project-id'; - const expectedInfuraNetworkConfigurationsByType: Record< - InfuraNetworkType, - NetworkConfiguration - > = { - [InfuraNetworkType.goerli]: { - rpcUrl: 'https://goerli.infura.io/v3/some-infura-project-id', - chainId: '0x5' as const, - ticker: 'GoerliETH', - rpcPrefs: { - blockExplorerUrl: 'https://goerli.etherscan.io', - }, - }, - [InfuraNetworkType.sepolia]: { - rpcUrl: 'https://sepolia.infura.io/v3/some-infura-project-id', - chainId: '0xaa36a7' as const, - ticker: 'SepoliaETH', - rpcPrefs: { - blockExplorerUrl: 'https://sepolia.etherscan.io', - }, - }, - [InfuraNetworkType.mainnet]: { - rpcUrl: 'https://mainnet.infura.io/v3/some-infura-project-id', - chainId: '0x1' as const, - ticker: 'ETH', - rpcPrefs: { - blockExplorerUrl: 'https://etherscan.io', - }, - }, - [InfuraNetworkType['linea-goerli']]: { - rpcUrl: 'https://linea-goerli.infura.io/v3/some-infura-project-id', - chainId: '0xe704' as const, - ticker: 'LineaETH', - rpcPrefs: { - blockExplorerUrl: 'https://goerli.lineascan.build', - }, - }, - [InfuraNetworkType['linea-sepolia']]: { - rpcUrl: 'https://linea-sepolia.infura.io/v3/some-infura-project-id', - chainId: '0xe705' as const, - ticker: 'LineaETH', - rpcPrefs: { - blockExplorerUrl: 'https://sepolia.lineascan.build', - }, - }, - [InfuraNetworkType['linea-mainnet']]: { - rpcUrl: 'https://linea-mainnet.infura.io/v3/some-infura-project-id', - chainId: '0xe708' as const, - ticker: 'ETH', - rpcPrefs: { - blockExplorerUrl: 'https://lineascan.build', - }, - }, - }; + describe(name, () => { + for (const infuraNetworkType of Object.values(InfuraNetworkType)) { + const infuraChainId = ChainId[infuraNetworkType]; + + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + describe(`given the ID of a network client that corresponds to an RPC endpoint for the Infura network "${infuraNetworkType}" in a network configuration`, () => { + it('returns the network configuration', async () => { + const registeredNetworkConfiguration = + buildInfuraNetworkConfiguration(infuraNetworkType); + await withController( + { + state: { + selectedNetworkClientId: infuraNetworkType, + networkConfigurationsByChainId: { + [infuraChainId]: registeredNetworkConfiguration, + }, + }, + }, + ({ controller, messenger }) => { + const returnedNetworkConfiguration = + getNetworkConfigurationByNetworkClientId({ + controller, + messenger, + networkClientId: infuraNetworkType, + }); - it.each(getKnownPropertyNames(InfuraNetworkType))( - 'constructs a network configuration for the %s network', - async (infuraNetworkType) => { + expect(returnedNetworkConfiguration).toBe( + registeredNetworkConfiguration, + ); + }, + ); + }); + }); + } + + describe('given the ID of a network client that corresponds to a custom RPC endpoint in a network configuration', () => { + it('returns the network configuration', async () => { + const registeredNetworkConfiguration = + buildCustomNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + }), + ], + }); await withController( - { infuraProjectId }, + { + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + '0x1337': registeredNetworkConfiguration, + }, + }, + }, ({ controller, messenger }) => { - const networkConfiguration = + const returnedNetworkConfiguration = getNetworkConfigurationByNetworkClientId({ controller, messenger, - networkClientId: infuraNetworkType, + networkClientId: 'AAAA-AAAA-AAAA-AAAA', }); - expect(networkConfiguration).toStrictEqual( - expectedInfuraNetworkConfigurationsByType[infuraNetworkType], + expect(returnedNetworkConfiguration).toBe( + registeredNetworkConfiguration, ); }, ); - }, - ); + }); + }); - it('returns the network configuration in state that matches the given ID, if there is one', async () => { - await withController( - { - infuraProjectId, - state: { - networkConfigurations: { - 'AAAA-AAAA-AAAA-AAAA': { - rpcUrl: 'https://test.network', - chainId: toHex(111), - ticker: 'TICKER', - id: 'AAAA-AAAA-AAAA-AAAA', - }, - }, - }, - }, - ({ controller, messenger }) => { - const networkConfiguration = + describe('given the ID of a network client that does not correspond to any RPC endpoint in a network configuration', () => { + it('returns undefined', async () => { + await withController(({ controller, messenger }) => { + const returnedNetworkConfiguration = getNetworkConfigurationByNetworkClientId({ controller, messenger, - networkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkClientId: 'nonexistent', }); - expect(networkConfiguration).toStrictEqual({ - rpcUrl: 'https://test.network', - chainId: toHex(111), - ticker: 'TICKER', - id: 'AAAA-AAAA-AAAA-AAAA', - }); - }, + expect(returnedNetworkConfiguration).toBeUndefined(); + }); + }); + }); + }); + } + + describe('addNetwork', () => { + it('throws if the chainId field is a string, but not a 0x-prefixed hex number', async () => { + await withController(({ controller }) => { + expect(() => + controller.addNetwork( + buildAddNetworkFields({ + // @ts-expect-error Intentionally passing bad input + chainId: '12345', + }), + ), + ).toThrow( + new Error( + `Cannot add network: Invalid \`chainId\` '12345' (must start with "0x" and not exceed the maximum)`, + ), ); }); + }); - it('returns undefined if the given ID does not match a network configuration in state', async () => { - await withController( - { infuraProjectId }, - ({ controller, messenger }) => { - const networkConfiguration = - getNetworkConfigurationByNetworkClientId({ - controller, - messenger, - networkClientId: 'nonexistent', - }); + it('throws if the chainId field is greater than the maximum allowed chain ID', async () => { + await withController(({ controller }) => { + expect(() => + controller.addNetwork( + buildAddNetworkFields({ + // False negative - this is a number. + // eslint-disable-next-line @typescript-eslint/restrict-plus-operands + chainId: toHex(MAX_SAFE_CHAIN_ID + 1), + }), + ), + ).toThrow( + new Error( + `Cannot add network: Invalid \`chainId\` '0xfffffffffffed' (must start with "0x" and not exceed the maximum)`, + ), + ); + }); + }); - expect(networkConfiguration).toBeUndefined(); - }, + it('throws if the given blockExplorerUrl field is an invalid URL', async () => { + await withController(({ controller }) => { + expect(() => + controller.addNetwork( + buildAddNetworkFields({ + blockExplorerUrl: 'clearly-not-a-url', + }), + ), + ).toThrow( + new Error( + "Cannot add network: `blockExplorerUrl` 'clearly-not-a-url' is an invalid URL", + ), ); }); }); - } - describe('upsertNetworkConfiguration', () => { - describe('when the rpcUrl of the given network configuration does not match an existing network configuration', () => { - it('adds the network configuration to state without updating or removing any existing network configurations', async () => { + it('throws if the rpcEndpoints field is an empty array', async () => { + await withController(({ controller }) => { + expect(() => + controller.addNetwork( + buildAddNetworkFields({ + rpcEndpoints: [], + }), + ), + ).toThrow( + new Error( + 'Cannot add network: `rpcEndpoints` must be a non-empty array', + ), + ); + }); + }); + + it('throws if one of the rpcEndpoints has an invalid url property', async () => { + await withController(({ controller }) => { + expect(() => + controller.addNetwork( + buildAddNetworkFields({ + rpcEndpoints: [ + buildAddNetworkCustomRpcEndpointFields({ + url: 'clearly-not-a-url', + }), + ], + }), + ), + ).toThrow( + new Error( + "Cannot add network: An entry in `rpcEndpoints` has invalid URL 'clearly-not-a-url'", + ), + ); + }); + }); + + it('throws if the URLs of two or more RPC endpoints have similar schemes (comparing case-insensitively)', async () => { + await withController(({ controller }) => { + expect(() => + controller.addNetwork( + buildAddNetworkFields({ + rpcEndpoints: [ + buildAddNetworkCustomRpcEndpointFields({ + url: 'https://foo.com/bar', + }), + buildAddNetworkCustomRpcEndpointFields({ + url: 'HTTPS://foo.com/bar', + }), + ], + }), + ), + ).toThrow( + new Error( + 'Cannot add network: Each entry in rpcEndpoints must have a unique URL', + ), + ); + }); + }); + + it('throws if the URLs of two or more RPC endpoints have similar hostnames (comparing case-insensitively)', async () => { + await withController(({ controller }) => { + expect(() => + controller.addNetwork( + buildAddNetworkFields({ + rpcEndpoints: [ + buildAddNetworkCustomRpcEndpointFields({ + url: 'https://foo.com/bar', + }), + buildAddNetworkCustomRpcEndpointFields({ + url: 'https://fOo.CoM/bar', + }), + ], + }), + ), + ).toThrow( + new Error( + 'Cannot add network: Each entry in rpcEndpoints must have a unique URL', + ), + ); + }); + }); + + it('does not throw if the URLs of two or more RPC endpoints have similar paths (comparing case-insensitively)', async () => { + await withController(({ controller }) => { + expect(() => + controller.addNetwork( + buildAddNetworkFields({ + rpcEndpoints: [ + buildAddNetworkCustomRpcEndpointFields({ + url: 'https://foo.com/bar', + }), + buildAddNetworkCustomRpcEndpointFields({ + url: 'https://foo.com/BAR', + }), + ], + }), + ), + ).not.toThrow(); + }); + }); + + for (const infuraNetworkType of Object.values(InfuraNetworkType)) { + const infuraNetworkNickname = NetworkNickname[infuraNetworkType]; + const infuraChainId = ChainId[infuraNetworkType]; + + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + it(`throws if rpcEndpoints contains an Infura RPC endpoint which is already present in the network configuration for the Infura-supported chain ${infuraChainId}`, async () => { + const infuraRpcEndpoint = buildInfuraRpcEndpoint(infuraNetworkType); + await withController( { - state: { - networkConfigurations: { - 'AAAA-AAAA-AAAA-AAAA': { - rpcUrl: 'https://test.network.1', - chainId: toHex(111), - ticker: 'TICKER1', - id: 'AAAA-AAAA-AAAA-AAAA', + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + [infuraChainId]: buildInfuraNetworkConfiguration( + infuraNetworkType, + { + rpcEndpoints: [infuraRpcEndpoint], + }, + ), }, - }, - }, + }), }, - async ({ controller }) => { - uuidV4Mock.mockReturnValue('BBBB-BBBB-BBBB-BBBB'); - - await controller.upsertNetworkConfiguration( - { - rpcUrl: 'https://test.network.2', - chainId: toHex(222), - ticker: 'TICKER2', - nickname: 'test network 2', - rpcPrefs: { - blockExplorerUrl: 'https://testchainscan.io', - }, - }, - { - referrer: 'https://test-dapp.com', - source: 'dapp', - }, + ({ controller }) => { + expect(() => + controller.addNetwork( + buildAddNetworkFields({ + chainId: '0x1337', + rpcEndpoints: [infuraRpcEndpoint], + }), + ), + ).toThrow( + // This is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `Cannot add network that points to same RPC endpoint as existing network for chain ${infuraChainId} ('${infuraNetworkNickname}')`, ); - - expect(controller.state.networkConfigurations).toStrictEqual({ - 'AAAA-AAAA-AAAA-AAAA': { - rpcUrl: 'https://test.network.1', - chainId: toHex(111), - ticker: 'TICKER1', - id: 'AAAA-AAAA-AAAA-AAAA', - }, - 'BBBB-BBBB-BBBB-BBBB': { - rpcUrl: 'https://test.network.2', - chainId: toHex(222), - ticker: 'TICKER2', - nickname: 'test network 2', - rpcPrefs: { - blockExplorerUrl: 'https://testchainscan.io', - }, - id: 'BBBB-BBBB-BBBB-BBBB', - }, - }); }, ); }); + } - it('removes properties not specific to the NetworkConfiguration interface before persisting it to state', async function () { - await withController(async ({ controller }) => { - uuidV4Mock.mockReturnValue('AAAA-AAAA-AAAA-AAAA'); - - await controller.upsertNetworkConfiguration( - { - rpcUrl: 'https://test.network', - chainId: toHex(111), - ticker: 'TICKER', - nickname: 'test network', - rpcPrefs: { - blockExplorerUrl: 'https://testchainscan.io', - }, - // @ts-expect-error We are intentionally passing bad input. - invalidKey: 'some value', - }, - { - referrer: 'https://test-dapp.com', - source: 'dapp', + it('throws if rpcEndpoints contains a custom RPC endpoint which is already present in another network configuration (comparing URLs case-insensitively)', async () => { + await withController( + { + state: buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x2448': buildNetworkConfiguration({ + chainId: '0x2448', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + url: 'http://test.endpoint/bar', + }), + ], + }), }, + }), + }, + ({ controller }) => { + expect(() => + controller.addNetwork( + buildAddNetworkFields({ + chainId: '0x1337', + rpcEndpoints: [ + buildAddNetworkCustomRpcEndpointFields({ + url: 'http://test.endpoint/foo', + }), + buildAddNetworkCustomRpcEndpointFields({ + url: 'HTTP://TEST.ENDPOINT/bar', + }), + ], + }), + ), + ).toThrow( + "Cannot add network that points to same RPC endpoint as existing network for chain 0x2448 ('Some Network')", ); + }, + ); + }); - expect(controller.state.networkConfigurations).toStrictEqual({ - 'AAAA-AAAA-AAAA-AAAA': { - rpcUrl: 'https://test.network', - chainId: toHex(111), - ticker: 'TICKER', - nickname: 'test network', - rpcPrefs: { - blockExplorerUrl: 'https://testchainscan.io', - }, - id: 'AAAA-AAAA-AAAA-AAAA', - }, - }); - }); + it('throws if two or more RPC endpoints are exactly the same object', async () => { + await withController(({ controller }) => { + const rpcEndpoint = buildAddNetworkCustomRpcEndpointFields(); + expect(() => + controller.addNetwork( + buildAddNetworkFields({ + chainId: '0x1337', + rpcEndpoints: [rpcEndpoint, rpcEndpoint], + }), + ), + ).toThrow( + 'Cannot add network: Each entry in rpcEndpoints must be unique', + ); }); + }); - it('creates a new network client for the network configuration and adds it to the registry', async () => { - await withController( - { infuraProjectId: 'some-infura-project-id' }, - async ({ controller }) => { - uuidV4Mock.mockReturnValue('AAAA-AAAA-AAAA-AAAA'); - const newCustomNetworkClient = buildFakeClient(); - mockCreateNetworkClientWithDefaultsForBuiltInNetworkClients({ - infuraProjectId: 'some-infura-project-id', - }) - .calledWith({ - chainId: toHex(111), - rpcUrl: 'https://test.network', - type: NetworkClientType.Custom, - ticker: 'TICKER', - }) - .mockReturnValue(newCustomNetworkClient); - - await controller.upsertNetworkConfiguration( - { - rpcUrl: 'https://test.network', - chainId: toHex(111), - ticker: 'TICKER', - }, - { - referrer: 'https://test-dapp.com', - source: 'dapp', - }, - ); - - const networkClients = controller.getNetworkClientRegistry(); - expect(Object.keys(networkClients)).toHaveLength(7); - expect(networkClients).toMatchObject({ - 'AAAA-AAAA-AAAA-AAAA': expect.objectContaining({ - configuration: { - chainId: toHex(111), - rpcUrl: 'https://test.network', - type: NetworkClientType.Custom, - ticker: 'TICKER', - }, + it('throws if there are two or more different Infura RPC endpoints', async () => { + await withController( + { + state: buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', }), - }); - }, + }, + }), + }, + ({ controller }) => { + const mainnetRpcEndpoint = buildInfuraRpcEndpoint( + InfuraNetworkType.mainnet, + ); + const goerliRpcEndpoint = buildInfuraRpcEndpoint( + InfuraNetworkType.goerli, + ); + expect(() => + controller.addNetwork( + buildAddNetworkFields({ + chainId: ChainId.mainnet, + rpcEndpoints: [mainnetRpcEndpoint, goerliRpcEndpoint], + }), + ), + ).toThrow( + 'Cannot add network: There cannot be more than one Infura RPC endpoint', + ); + }, + ); + }); + + it('throws if defaultRpcEndpointUrl does not refer to an entry in rpcEndpoints', async () => { + await withController(({ controller }) => { + expect(() => + controller.addNetwork( + buildAddNetworkFields({ + defaultRpcEndpointUrl: 'https://non-existent.com', + rpcEndpoints: [ + buildUpdateNetworkCustomRpcEndpointFields({ + url: 'https://foo.com', + }), + buildCustomRpcEndpoint({ + url: 'https://bar.com', + }), + ], + }), + ), + ).toThrow( + new Error( + "Cannot add network: `defaultRpcEndpointUrl` 'https://non-existent.com' must match an entry in `rpcEndpoints` ([ 'https://foo.com', 'https://bar.com' ])", + ), ); }); + }); - it('updates state only after creating the new network client', async () => { - await withController( - { infuraProjectId: 'some-infura-project-id' }, - async ({ controller, messenger }) => { - uuidV4Mock.mockReturnValue('AAAA-AAAA-AAAA-AAAA'); - const newCustomNetworkClient = buildFakeClient(); - mockCreateNetworkClientWithDefaultsForBuiltInNetworkClients({ - infuraProjectId: 'some-infura-project-id', - }) - .calledWith({ - chainId: toHex(111), - rpcUrl: 'https://test.network', - type: NetworkClientType.Custom, - ticker: 'TICKER', - }) - .mockReturnValue(newCustomNetworkClient); + for (const infuraNetworkType of Object.values(InfuraNetworkType)) { + const infuraNetworkNickname = NetworkNickname[infuraNetworkType]; + const infuraChainId = ChainId[infuraNetworkType]; - await waitForStateChanges({ - messenger, - count: 1, - operation: async () => { - await controller.upsertNetworkConfiguration( - { - rpcUrl: 'https://test.network', - chainId: toHex(111), - ticker: 'TICKER', - }, - { - referrer: 'https://test-dapp.com', - source: 'dapp', - }, - ); - }, - beforeResolving: () => { - const newNetworkClient = controller.getNetworkClientById( - 'AAAA-AAAA-AAAA-AAAA', - ); - expect(newNetworkClient).toBeDefined(); - }, - }); + // This is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + it(`throws if a network configuration for the Infura network "${infuraNetworkNickname}" is already registered under the given chain ID`, async () => { + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + [infuraChainId]: + buildInfuraNetworkConfiguration(infuraNetworkType), + }, + }), + }, + ({ controller }) => { + expect(() => + controller.addNetwork( + buildAddNetworkFields({ + chainId: infuraChainId, + }), + ), + ).toThrow( + // This is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `Cannot add network for chain ${infuraChainId} as another network for that chain already exists ('${infuraNetworkNickname}')`, + ); }, ); }); + } - describe('if the setActive option is not given', () => { - it('does not update selectedNetworkClientId to refer to the new network configuration by default', async () => { - await withController(async ({ controller }) => { - const originalSelectedNetworkClientId = - controller.state.selectedNetworkClientId; - - uuidV4Mock.mockReturnValue('AAAA-AAAA-AAAA-AAAA'); + it('throws if a custom network is already registered under the given chain ID', async () => { + await withController( + { + state: buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + name: 'Some Network', + }), + }, + }), + }, + ({ controller }) => { + expect(() => + controller.addNetwork( + buildAddNetworkFields({ + chainId: '0x1337', + }), + ), + ).toThrow( + `Cannot add network for chain 0x1337 as another network for that chain already exists ('Some Network')`, + ); + }, + ); + }); - await controller.upsertNetworkConfiguration( - { - rpcUrl: 'https://test.network', - chainId: toHex(111), - ticker: 'TICKER', - }, - { - referrer: 'https://test-dapp.com', - source: 'dapp', - }, - ); + for (const infuraNetworkType of Object.values(InfuraNetworkType)) { + const infuraChainId = ChainId[infuraNetworkType]; + const infuraNetworkNickname = NetworkNickname[infuraNetworkType]; + const infuraNativeTokenName = NetworksTicker[infuraNetworkType]; - expect(controller.state.selectedNetworkClientId).toStrictEqual( - originalSelectedNetworkClientId, - ); - }); - }); + // This is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + describe(`when the given chain ID is the Infura-supported chain ${infuraChainId}`, () => { + it('creates a new network client for not only each custom RPC endpoint, but also the Infura RPC endpoint', async () => { + uuidV4Mock + .mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB') + .mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC'); + const createAutoManagedNetworkClientSpy = jest.spyOn( + createAutoManagedNetworkClientModule, + 'createAutoManagedNetworkClient', + ); + const infuraProjectId = 'some-infura-project-id'; - 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 }) => { - uuidV4Mock.mockReturnValue('AAAA-AAAA-AAAA-AAAA'); - const builtInNetworkProvider = buildFakeProvider([ - { - request: { - method: 'test_method', - params: [], - }, - response: { - result: 'test response from built-in network', + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.endpoint/1', + }), + ], + }), }, - }, - ]); - const builtInNetworkClient = buildFakeClient( - builtInNetworkProvider, - ); - const newCustomNetworkProvider = buildFakeProvider([ - { - request: { - method: 'test_method', - params: [], + }), + infuraProjectId, + }, + ({ controller }) => { + const defaultRpcEndpoint = + buildInfuraRpcEndpoint(infuraNetworkType); + + controller.addNetwork({ + chainId: infuraChainId, + defaultRpcEndpointUrl: 'https://test.endpoint/2', + name: infuraNetworkType, + nativeTokenName: infuraNativeTokenName, + rpcEndpoints: [ + defaultRpcEndpoint, + { + name: 'Test Network 1', + type: RpcEndpointType.Custom, + url: 'https://test.endpoint/2', }, - response: { - result: 'test response from custom network', + { + name: 'Test Network 2', + type: RpcEndpointType.Custom, + url: 'https://test.endpoint/3', }, - }, - ]); - const newCustomNetworkClient = buildFakeClient( - newCustomNetworkProvider, - ); - mockCreateNetworkClientWithDefaultsForBuiltInNetworkClients({ - builtInNetworkClient, - infuraProjectId: 'some-infura-project-id', - }) - .calledWith({ - chainId: toHex(111), - rpcUrl: 'https://test.network', - type: NetworkClientType.Custom, - ticker: 'TEST', - }) - .mockReturnValue(newCustomNetworkClient); - // Will use mainnet by default - await controller.initializeProvider(); + ], + }); - await controller.upsertNetworkConfiguration( + // Skipping the 1st call because it's for the initial state + expect(createAutoManagedNetworkClientSpy).toHaveBeenNthCalledWith( + 2, { - rpcUrl: 'https://test.network', - chainId: toHex(111), - ticker: 'TICKER', + infuraProjectId, + chainId: infuraChainId, + network: infuraNetworkType, + ticker: infuraNativeTokenName, + type: NetworkClientType.Infura, }, + ); + expect(createAutoManagedNetworkClientSpy).toHaveBeenNthCalledWith( + 3, { - referrer: 'https://test-dapp.com', - source: 'dapp', + chainId: infuraChainId, + rpcUrl: 'https://test.endpoint/2', + ticker: infuraNativeTokenName, + type: NetworkClientType.Custom, }, ); - - const { provider } = controller.getProviderAndBlockTracker(); - assert(provider, 'Provider is not set'); - const { result } = await promisify(provider.sendAsync).call( - provider, + expect(createAutoManagedNetworkClientSpy).toHaveBeenNthCalledWith( + 4, { - id: 1, - jsonrpc: '2.0', - method: 'test_method', - params: [], + chainId: infuraChainId, + rpcUrl: 'https://test.endpoint/3', + ticker: infuraNativeTokenName, + type: NetworkClientType.Custom, }, ); - expect(result).toBe('test response from built-in network'); + expect( + getNetworkConfigurationsByNetworkClientId( + controller.getNetworkClientRegistry(), + ), + ).toMatchObject({ + [infuraNetworkType]: { + chainId: infuraChainId, + network: infuraNetworkType, + type: NetworkClientType.Infura, + }, + 'BBBB-BBBB-BBBB-BBBB': { + chainId: infuraChainId, + rpcUrl: 'https://test.endpoint/2', + ticker: infuraNativeTokenName, + type: NetworkClientType.Custom, + }, + 'CCCC-CCCC-CCCC-CCCC': { + chainId: infuraChainId, + rpcUrl: 'https://test.endpoint/3', + ticker: infuraNativeTokenName, + type: NetworkClientType.Custom, + }, + }); }, ); }); - }); - - describe('if the setActive option is false', () => { - it('does not update selectedNetworkClientId to refer to the new network configuration', async () => { - await withController(async ({ controller }) => { - const originalSelectedNetworkClientId = - controller.state.selectedNetworkClientId; - uuidV4Mock.mockReturnValue('AAAA-AAAA-AAAA-AAAA'); + it('adds the network configuration to state under the chain ID', async () => { + uuidV4Mock.mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB'); - await controller.upsertNetworkConfiguration( - { - rpcUrl: 'https://test.network', - chainId: toHex(111), - ticker: 'TICKER', - }, - { - setActive: false, - referrer: 'https://test-dapp.com', - source: 'dapp', - }, - ); + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.endpoint/1', + }), + ], + }), + }, + }), + }, + ({ controller }) => { + controller.addNetwork({ + chainId: infuraChainId, + defaultRpcEndpointUrl: 'https://test.endpoint/2', + name: 'Some Network', + nativeTokenName: 'TOKEN', + rpcEndpoints: [ + { + name: 'Test Network', + type: RpcEndpointType.Custom, + url: 'https://test.endpoint/2', + }, + { + name: infuraNetworkNickname, + networkClientId: infuraNetworkType, + type: RpcEndpointType.Infura as const, + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + url: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`, + }, + ], + }); - expect(controller.state.selectedNetworkClientId).toStrictEqual( - originalSelectedNetworkClientId, - ); - }); + expect( + controller.state.networkConfigurationsByChainId, + ).toHaveProperty(infuraChainId); + expect( + controller.state.networkConfigurationsByChainId[infuraChainId], + ).toStrictEqual({ + chainId: infuraChainId, + defaultRpcEndpointUrl: 'https://test.endpoint/2', + name: 'Some Network', + nativeTokenName: 'TOKEN', + rpcEndpoints: [ + { + name: 'Test Network', + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + type: RpcEndpointType.Custom, + url: 'https://test.endpoint/2', + }, + { + name: infuraNetworkNickname, + networkClientId: infuraNetworkType, + type: RpcEndpointType.Infura as const, + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + url: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`, + }, + ], + }); + }, + ); }); - it('does not re-point the provider and block tracker proxies to the new network', async () => { + it('emits the NetworkController:networkAdded event', async () => { await withController( - { infuraProjectId: 'some-infura-project-id' }, - async ({ controller }) => { - uuidV4Mock.mockReturnValue('AAAA-AAAA-AAAA-AAAA'); - const builtInNetworkProvider = buildFakeProvider([ - { - request: { - method: 'test_method', - params: [], - }, - response: { - result: 'test response from built-in network', + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.endpoint/1', + }), + ], + }), }, - }, - ]); - const builtInNetworkClient = buildFakeClient( - builtInNetworkProvider, + }), + }, + ({ controller, messenger }) => { + const networkAddedEventListener = jest.fn(); + messenger.subscribe( + 'NetworkController:networkAdded', + networkAddedEventListener, ); - const newCustomNetworkProvider = buildFakeProvider([ - { - request: { - method: 'test_method', - params: [], + + controller.addNetwork({ + chainId: infuraChainId, + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + defaultRpcEndpointUrl: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`, + name: 'Some Network', + nativeTokenName: 'TOKEN', + rpcEndpoints: [ + { + name: infuraNetworkNickname, + networkClientId: infuraNetworkType, + type: RpcEndpointType.Infura as const, + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + url: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`, }, - response: { - result: 'test response from custom network', + ], + }); + + expect(networkAddedEventListener).toHaveBeenCalledWith({ + chainId: infuraChainId, + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + defaultRpcEndpointUrl: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`, + name: 'Some Network', + nativeTokenName: 'TOKEN', + rpcEndpoints: [ + { + name: infuraNetworkNickname, + networkClientId: infuraNetworkType, + type: RpcEndpointType.Infura as const, + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + url: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`, }, - }, - ]); - const newCustomNetworkClient = buildFakeClient( - newCustomNetworkProvider, - ); - mockCreateNetworkClientWithDefaultsForBuiltInNetworkClients({ - builtInNetworkClient, - infuraProjectId: 'some-infura-project-id', - }) - .calledWith({ - chainId: toHex(111), - rpcUrl: 'https://test.network', + ], + }); + }, + ); + }); + + it('returns the newly added network configuration', async () => { + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.endpoint/1', + }), + ], + }), + }, + }), + }, + ({ controller }) => { + const newNetworkConfiguration = controller.addNetwork({ + chainId: infuraChainId, + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + defaultRpcEndpointUrl: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`, + name: 'Some Network', + nativeTokenName: 'TOKEN', + rpcEndpoints: [ + { + name: infuraNetworkNickname, + networkClientId: infuraNetworkType, + type: RpcEndpointType.Infura as const, + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + url: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`, + }, + ], + }); + + expect(newNetworkConfiguration).toStrictEqual({ + chainId: infuraChainId, + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + defaultRpcEndpointUrl: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`, + name: 'Some Network', + nativeTokenName: 'TOKEN', + rpcEndpoints: [ + { + name: infuraNetworkNickname, + networkClientId: infuraNetworkType, + type: RpcEndpointType.Infura as const, + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + url: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`, + }, + ], + }); + }, + ); + }); + }); + } + + describe('when the given chain ID is not an Infura-supported chain', () => { + it('throws (albeit for a different reason) if rpcEndpoints contains an Infura RPC endpoint that represents a different chain that the one being added', async () => { + uuidV4Mock + .mockReturnValueOnce('AAAA-AAAA-AAAA-AAAA') + .mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB'); + const defaultRpcEndpoint = buildInfuraRpcEndpoint( + InfuraNetworkType.mainnet, + ); + + await withController( + { + state: { + selectedNetworkClientId: InfuraNetworkType.goerli, + networkConfigurationsByChainId: { + [ChainId.goerli]: buildInfuraNetworkConfiguration( + InfuraNetworkType.goerli, + ), + }, + }, + }, + ({ controller }) => { + expect(() => + controller.addNetwork({ + chainId: '0x1337', + defaultRpcEndpointUrl: 'https://test.endpoint/1', + name: 'Some Network', + nativeTokenName: 'TOKEN', + rpcEndpoints: [ + defaultRpcEndpoint, + { + name: 'Test Network 2', + type: RpcEndpointType.Custom, + url: 'https://test.endpoint/2', + }, + ], + }), + ).toThrow( + new Error( + "Cannot add network with chain ID 0x1337 and Infura RPC endpoint for 'Mainnet' which represents 0x1, as the two conflict", + ), + ); + }, + ); + }); + + it('creates a new network client for each given RPC endpoint', async () => { + uuidV4Mock + .mockReturnValueOnce('AAAA-AAAA-AAAA-AAAA') + .mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB'); + + await withController(({ controller }) => { + controller.addNetwork({ + chainId: '0x1337', + defaultRpcEndpointUrl: 'https://test.endpoint/1', + name: 'Some Network', + nativeTokenName: 'TOKEN', + rpcEndpoints: [ + { + name: 'Test Network 1', + type: RpcEndpointType.Custom, + url: 'https://test.endpoint/1', + }, + { + name: 'Test Network 2', + type: RpcEndpointType.Custom, + url: 'https://test.endpoint/2', + }, + ], + }); + + const networkClient1 = controller.getNetworkClientById( + // 'mm:ncli:custom:https:443:test.endpoint/1', + 'AAAA-AAAA-AAAA-AAAA', + ); + expect(networkClient1.configuration).toStrictEqual({ + chainId: '0x1337', + rpcUrl: 'https://test.endpoint/1', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }); + const networkClient2 = controller.getNetworkClientById( + // 'mm:ncli:custom:https:443:test.endpoint/2', + 'BBBB-BBBB-BBBB-BBBB', + ); + expect(networkClient2.configuration).toStrictEqual({ + chainId: '0x1337', + rpcUrl: 'https://test.endpoint/2', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }); + }); + }); + + it('adds the network configuration to state under the chain ID', async () => { + uuidV4Mock + .mockReturnValueOnce('AAAA-AAAA-AAAA-AAAA') + .mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB'); + + await withController(({ controller }) => { + controller.addNetwork({ + chainId: '0x1337', + defaultRpcEndpointUrl: 'https://test.endpoint/1', + name: 'Some Network', + nativeTokenName: 'TOKEN', + rpcEndpoints: [ + { + name: 'Test Network 1', + type: RpcEndpointType.Custom, + url: 'https://test.endpoint/1', + }, + { + name: 'Test Network 2', + type: RpcEndpointType.Custom, + url: 'https://test.endpoint/2', + }, + ], + }); + + expect( + controller.state.networkConfigurationsByChainId['0x1337'], + ).toStrictEqual({ + chainId: '0x1337', + defaultRpcEndpointUrl: 'https://test.endpoint/1', + name: 'Some Network', + nativeTokenName: 'TOKEN', + rpcEndpoints: [ + { + name: 'Test Network 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + type: RpcEndpointType.Custom, + url: 'https://test.endpoint/1', + }, + { + name: 'Test Network 2', + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + type: RpcEndpointType.Custom, + url: 'https://test.endpoint/2', + }, + ], + }); + }); + }); + + it('emits the NetworkController:networkAdded event', async () => { + uuidV4Mock.mockReturnValueOnce('AAAA-AAAA-AAAA-AAAA'); + + await withController(({ controller, messenger }) => { + const networkAddedEventListener = jest.fn(); + messenger.subscribe( + 'NetworkController:networkAdded', + networkAddedEventListener, + ); + + controller.addNetwork({ + chainId: '0x1337', + defaultRpcEndpointUrl: 'https://test.endpoint', + name: 'Some Network', + nativeTokenName: 'TOKEN', + rpcEndpoints: [ + { + name: 'Test Network', + type: RpcEndpointType.Custom, + url: 'https://test.endpoint', + }, + ], + }); + + expect(networkAddedEventListener).toHaveBeenCalledWith({ + chainId: '0x1337', + defaultRpcEndpointUrl: 'https://test.endpoint', + name: 'Some Network', + nativeTokenName: 'TOKEN', + rpcEndpoints: [ + { + name: 'Test Network', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + type: RpcEndpointType.Custom, + url: 'https://test.endpoint', + }, + ], + }); + }); + }); + + it('returns the newly added network configuration', async () => { + uuidV4Mock.mockReturnValueOnce('AAAA-AAAA-AAAA-AAAA'); + + await withController(({ controller, messenger }) => { + const networkAddedEventListener = jest.fn(); + messenger.subscribe( + 'NetworkController:networkAdded', + networkAddedEventListener, + ); + + const newNetworkConfiguration = controller.addNetwork({ + chainId: '0x1337', + defaultRpcEndpointUrl: 'https://test.endpoint', + name: 'Some Network', + nativeTokenName: 'TOKEN', + rpcEndpoints: [ + { + name: 'Test Network', + type: RpcEndpointType.Custom, + url: 'https://test.endpoint', + }, + ], + }); + + expect(newNetworkConfiguration).toStrictEqual({ + chainId: '0x1337', + defaultRpcEndpointUrl: 'https://test.endpoint', + name: 'Some Network', + nativeTokenName: 'TOKEN', + rpcEndpoints: [ + { + name: 'Test Network', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + type: RpcEndpointType.Custom, + url: 'https://test.endpoint', + }, + ], + }); + }); + }); + }); + }); + + describe('updateNetwork', () => { + it('throws if the given chain ID does not refer to an existing network configuration', async () => { + await withController(({ controller }) => { + expect(() => + controller.updateNetwork( + '0x1337', + buildCustomNetworkConfiguration({ + chainId: '0x1337', + }), + ), + ).toThrow( + new Error("Cannot find network configuration for chain '0x1337'"), + ); + }); + }); + + it('throws if the new chainId field is a string, but not a 0x-prefixed hex number', async () => { + const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ + chainId: '0x1337', + }); + + await withController( + { + state: buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }), + }, + ({ controller }) => { + expect(() => + controller.updateNetwork( + '0x1337', + buildCustomNetworkConfiguration({ + // @ts-expect-error Intentionally passing bad input + chainId: '12345', + }), + ), + ).toThrow( + new Error( + `Cannot update network: New \`chainId\` '12345' is invalid (must start with "0x" and not exceed the maximum)`, + ), + ); + }, + ); + }); + + it('throws if the new chainId field is greater than the maximum allowed chain ID', async () => { + const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ + chainId: '0x1337', + }); + + await withController( + { + state: buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }), + }, + ({ controller }) => { + expect(() => + controller.updateNetwork( + '0x1337', + buildCustomNetworkConfiguration({ + // False negative - this is a number. + // eslint-disable-next-line @typescript-eslint/restrict-plus-operands + chainId: toHex(MAX_SAFE_CHAIN_ID + 1), + }), + ), + ).toThrow( + new Error( + `Cannot update network: New \`chainId\` '0xfffffffffffed' is invalid (must start with "0x" and not exceed the maximum)`, + ), + ); + }, + ); + }); + + it('throws if the new blockExplorerUrl field is an invalid URL', async () => { + const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ + chainId: '0x1337', + }); + + await withController( + { + state: buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }), + }, + ({ controller }) => { + expect(() => + controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + blockExplorerUrl: 'clearly-not-a-url', + }), + ).toThrow( + new Error( + "Cannot update network: `blockExplorerUrl` 'clearly-not-a-url' is an invalid URL", + ), + ); + }, + ); + }); + + it('throws if the new rpcEndpoints field is an empty array', async () => { + const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ + chainId: '0x1337', + }); + + await withController( + { + state: buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }), + }, + ({ controller }) => { + expect(() => + controller.updateNetwork( + '0x1337', + buildNetworkConfiguration({ + rpcEndpoints: [], + }), + ), + ).toThrow( + new Error( + 'Cannot update network: `rpcEndpoints` must be a non-empty array', + ), + ); + }, + ); + }); + + it('throws if one of the new rpcEndpoints has an invalid url property', async () => { + const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ + chainId: '0x1337', + }); + + await withController( + { + state: buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }), + }, + ({ controller }) => { + expect(() => + controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + buildUpdateNetworkCustomRpcEndpointFields({ + url: 'clearly-not-a-url', + }), + ], + }), + ).toThrow( + new Error( + "Cannot update network: An entry in `rpcEndpoints` has invalid URL 'clearly-not-a-url'", + ), + ); + }, + ); + }); + + it('throws if one of the new RPC endpoints has a networkClientId that does not refer to a registered network client', async () => { + const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ + chainId: '0x1337', + }); + + await withController( + { + state: buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }), + }, + ({ controller }) => { + expect(() => + controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + buildUpdateNetworkCustomRpcEndpointFields({ + url: 'https://foo.com', + networkClientId: 'not-a-real-network-client-id', + }), + ], + }), + ).toThrow( + new Error( + "Cannot update network: RPC endpoint 'https://foo.com' refers to network client 'not-a-real-network-client-id' that does not exist", + ), + ); + }, + ); + }); + + it('throws if the URLs of two or more RPC endpoints have similar schemes (comparing case-insensitively)', async () => { + const networkConfigurationToUpdate = buildNetworkConfiguration(); + + await withController( + { + state: buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }), + }, + ({ controller }) => { + expect(() => + controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + buildUpdateNetworkCustomRpcEndpointFields({ + url: 'https://foo.com/bar', + }), + buildUpdateNetworkCustomRpcEndpointFields({ + url: 'HTTPS://foo.com/bar', + }), + ], + }), + ).toThrow( + new Error( + 'Cannot update network: Each entry in rpcEndpoints must have a unique URL', + ), + ); + }, + ); + }); + + it('throws if the URLs of two or more RPC endpoints have similar hostnames (comparing case-insensitively)', async () => { + const networkConfigurationToUpdate = buildNetworkConfiguration(); + + await withController( + { + state: buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }), + }, + ({ controller }) => { + expect(() => + controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + buildUpdateNetworkCustomRpcEndpointFields({ + url: 'https://foo.com/bar', + }), + buildUpdateNetworkCustomRpcEndpointFields({ + url: 'https://fOo.CoM/bar', + }), + ], + }), + ).toThrow( + new Error( + 'Cannot update network: Each entry in rpcEndpoints must have a unique URL', + ), + ); + }, + ); + }); + + it('does not throw if the URLs of two or more RPC endpoints have similar paths (comparing case-insensitively)', async () => { + const networkConfigurationToUpdate = buildNetworkConfiguration(); + + await withController( + { + state: buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }), + }, + ({ controller }) => { + expect(() => + controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + defaultRpcEndpointUrl: 'https://foo.com/bar', + rpcEndpoints: [ + buildUpdateNetworkCustomRpcEndpointFields({ + url: 'https://foo.com/bar', + }), + buildUpdateNetworkCustomRpcEndpointFields({ + url: 'https://foo.com/BAR', + }), + ], + }), + ).not.toThrow(); + }, + ); + }); + + for (const infuraNetworkType of Object.values(InfuraNetworkType)) { + const infuraNetworkNickname = NetworkNickname[infuraNetworkType]; + const infuraChainId = ChainId[infuraNetworkType]; + + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + it(`throws if an Infura RPC endpoint is being added which is already present in the network configuration for the Infura-supported chain ${infuraChainId}`, async () => { + const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ + chainId: '0x1337', + }); + const infuraRpcEndpoint = buildInfuraRpcEndpoint(infuraNetworkType); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + [infuraChainId]: buildInfuraNetworkConfiguration( + infuraNetworkType, + { + rpcEndpoints: [infuraRpcEndpoint], + }, + ), + }, + }), + }, + ({ controller }) => { + expect(() => + controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + rpcEndpoints: [infuraRpcEndpoint], + }), + ).toThrow( + // This is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `Cannot update network to point to same RPC endpoint as existing network for chain ${infuraChainId} ('${infuraNetworkNickname}')`, + ); + }, + ); + }); + } + + it('throws if a custom RPC endpoint is being added which is already present in another network configuration (comparing URLs case-insensitively)', async () => { + const networkConfigurationToUpdate = buildNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + url: 'http://test.endpoint/foo', + }), + ], + }); + + await withController( + { + state: buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + '0x2448': buildNetworkConfiguration({ + chainId: '0x2448', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + url: 'http://test.endpoint/bar', + }), + ], + }), + }, + }), + }, + ({ controller }) => { + expect(() => + controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + buildUpdateNetworkCustomRpcEndpointFields({ + url: 'http://test.endpoint/foo', + }), + buildUpdateNetworkCustomRpcEndpointFields({ + url: 'HTTP://TEST.ENDPOINT/bar', + }), + ], + }), + ).toThrow( + new Error( + "Cannot update network to point to same RPC endpoint as existing network for chain 0x2448 ('Some Network')", + ), + ); + }, + ); + }); + + it('throws if two or more RPC endpoints are exactly the same object', async () => { + const networkConfigurationToUpdate = buildNetworkConfiguration(); + + await withController( + { + state: buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }), + }, + ({ controller }) => { + const rpcEndpoint = buildUpdateNetworkCustomRpcEndpointFields(); + expect(() => + controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + defaultRpcEndpointUrl: rpcEndpoint.url, + rpcEndpoints: [rpcEndpoint, rpcEndpoint], + }), + ).toThrow( + new Error( + 'Cannot update network: Each entry in rpcEndpoints must be unique', + ), + ); + }, + ); + }); + + it('throws if two or more RPC endpoints have the same networkClientId', async () => { + const rpcEndpoint = buildCustomRpcEndpoint({ + url: 'https://test.endpoint', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + }); + const networkConfigurationToUpdate = buildNetworkConfiguration({ + rpcEndpoints: [rpcEndpoint], + }); + + await withController( + { + state: buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }), + }, + ({ controller }) => { + expect(() => + controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + rpcEndpoint, + buildUpdateNetworkCustomRpcEndpointFields({ + url: 'https://test.endpoint/2', + networkClientId: rpcEndpoint.networkClientId, + }), + ], + }), + ).toThrow( + new Error( + 'Cannot update network: Each entry in rpcEndpoints must have a unique networkClientId', + ), + ); + }, + ); + }); + + it('throws (albeit for a different reason) if there are two or more different Infura RPC endpoints', async () => { + const [mainnetRpcEndpoint, goerliRpcEndpoint] = [ + buildInfuraRpcEndpoint(InfuraNetworkType.mainnet), + buildInfuraRpcEndpoint(InfuraNetworkType.goerli), + ]; + const networkConfigurationToUpdate = buildNetworkConfiguration({ + name: 'Mainnet', + chainId: ChainId.mainnet, + rpcEndpoints: [mainnetRpcEndpoint], + }); + + await withController( + { + state: buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + [ChainId.mainnet]: networkConfigurationToUpdate, + [ChainId.goerli]: buildNetworkConfiguration({ + name: 'Goerli', + chainId: ChainId.goerli, + rpcEndpoints: [goerliRpcEndpoint], + }), + }, + }), + }, + ({ controller }) => { + expect(() => + controller.updateNetwork(ChainId.mainnet, { + ...networkConfigurationToUpdate, + rpcEndpoints: [mainnetRpcEndpoint, goerliRpcEndpoint], + }), + ).toThrow( + new Error( + "Cannot update network to point to same RPC endpoint as existing network for chain 0x5 ('Goerli')", + ), + ); + }, + ); + }); + + it('throws if the new defaultRpcEndpointUrl does not refer to an entry in rpcEndpoints', async () => { + const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + url: 'https://foo.com', + }), + buildCustomRpcEndpoint({ + url: 'https://bar.com', + }), + ], + }); + + await withController( + { + state: buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }), + }, + ({ controller }) => { + expect(() => + controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + defaultRpcEndpointUrl: 'https://non-existent.com', + }), + ).toThrow( + new Error( + "Cannot update network: `defaultRpcEndpointUrl` 'https://non-existent.com' must match an entry in `rpcEndpoints` ([ 'https://foo.com', 'https://bar.com' ])", + ), + ); + }, + ); + }); + + for (const infuraNetworkType of Object.values(InfuraNetworkType)) { + const infuraChainId = ChainId[infuraNetworkType]; + const infuraNativeTokenName = NetworksTicker[infuraNetworkType]; + + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + describe(`if the existing chain ID is the Infura-supported chain ${infuraChainId} and is not being changed`, () => { + describe('when new custom RPC endpoints are being added', () => { + it('creates and registers new network clients for each RPC endpoint', async () => { + uuidV4Mock + .mockReturnValueOnce('AAAA-AAAA-AAAA-AAAA') + .mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB'); + const createAutoManagedNetworkClientSpy = jest.spyOn( + createAutoManagedNetworkClientModule, + 'createAutoManagedNetworkClient', + ); + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + rpcEndpoints: [buildInfuraRpcEndpoint(infuraNetworkType)], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId( + { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + ), + }, + ({ controller }) => { + const [rpcEndpoint1, rpcEndpoint2] = [ + buildUpdateNetworkCustomRpcEndpointFields({ + name: 'Endpoint 1', + url: 'https://rpc.endpoint/1', + }), + buildUpdateNetworkCustomRpcEndpointFields({ + name: 'Endpoint 2', + url: 'https://rpc.endpoint/2', + }), + ]; + controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + defaultRpcEndpointUrl: rpcEndpoint1.url, + rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], + }); + + // Skipping the 1st call because it's for the Infura network + expect( + createAutoManagedNetworkClientSpy, + ).toHaveBeenNthCalledWith(2, { + chainId: infuraChainId, + rpcUrl: 'https://rpc.endpoint/1', + ticker: infuraNativeTokenName, + type: NetworkClientType.Custom, + }); + expect( + createAutoManagedNetworkClientSpy, + ).toHaveBeenNthCalledWith(3, { + chainId: infuraChainId, + rpcUrl: 'https://rpc.endpoint/2', + ticker: infuraNativeTokenName, + type: NetworkClientType.Custom, + }); + + expect( + getNetworkConfigurationsByNetworkClientId( + controller.getNetworkClientRegistry(), + ), + ).toMatchObject({ + 'AAAA-AAAA-AAAA-AAAA': { + chainId: infuraChainId, + rpcUrl: 'https://rpc.endpoint/1', + ticker: infuraNativeTokenName, + type: NetworkClientType.Custom, + }, + 'BBBB-BBBB-BBBB-BBBB': { + chainId: infuraChainId, + rpcUrl: 'https://rpc.endpoint/2', + ticker: infuraNativeTokenName, + type: NetworkClientType.Custom, + }, + }); + }, + ); + }); + + it('assigns the ID of the created network client to each RPC endpoint in state', async () => { + uuidV4Mock + .mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB') + .mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC'); + const rpcEndpoint1 = buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint/1', + }); + const networkConfigurationToUpdate = buildNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [rpcEndpoint1], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId( + { + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }, + ), + }, + ({ controller }) => { + controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + defaultRpcEndpointUrl: rpcEndpoint1.url, + rpcEndpoints: [ + rpcEndpoint1, + buildUpdateNetworkCustomRpcEndpointFields({ + name: 'Endpoint 2', + url: 'https://rpc.endpoint/2', + }), + buildUpdateNetworkCustomRpcEndpointFields({ + name: 'Endpoint 3', + url: 'https://rpc.endpoint/3', + }), + ], + }); + + expect( + controller.state.networkConfigurationsByChainId['0x1337'], + ).toStrictEqual({ + ...networkConfigurationToUpdate, + rpcEndpoints: [ + rpcEndpoint1, + { + name: 'Endpoint 2', + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + type: RpcEndpointType.Custom, + url: 'https://rpc.endpoint/2', + }, + { + name: 'Endpoint 3', + networkClientId: 'CCCC-CCCC-CCCC-CCCC', + type: RpcEndpointType.Custom, + url: 'https://rpc.endpoint/3', + }, + ], + }); + }, + ); + }); + + it('returns the updated network configuration', async () => { + uuidV4Mock + .mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB') + .mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC'); + const rpcEndpoint1 = buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint/1', + }); + const networkConfigurationToUpdate = buildNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [rpcEndpoint1], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId( + { + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }, + ), + }, + ({ controller }) => { + const updatedNetworkConfiguration = controller.updateNetwork( + '0x1337', + { + ...networkConfigurationToUpdate, + defaultRpcEndpointUrl: rpcEndpoint1.url, + rpcEndpoints: [ + rpcEndpoint1, + buildUpdateNetworkCustomRpcEndpointFields({ + name: 'Endpoint 2', + url: 'https://rpc.endpoint/2', + }), + buildUpdateNetworkCustomRpcEndpointFields({ + name: 'Endpoint 3', + url: 'https://rpc.endpoint/3', + }), + ], + }, + ); + + expect(updatedNetworkConfiguration).toStrictEqual({ + ...networkConfigurationToUpdate, + rpcEndpoints: [ + rpcEndpoint1, + { + name: 'Endpoint 2', + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + type: RpcEndpointType.Custom, + url: 'https://rpc.endpoint/2', + }, + { + name: 'Endpoint 3', + networkClientId: 'CCCC-CCCC-CCCC-CCCC', + type: RpcEndpointType.Custom, + url: 'https://rpc.endpoint/3', + }, + ], + }); + }, + ); + }); + }); + }); + + describe('when some custom RPC endpoints are being removed', () => { + it('destroys and unregisters existing network clients for the RPC endpoints', async () => { + const [rpcEndpoint1, rpcEndpoint2] = [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint/1', + }), + buildCustomRpcEndpoint({ + name: 'Endpoint 2', + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://rpc.endpoint/2', + }), + ]; + const networkConfigurationToUpdate = buildInfuraNetworkConfiguration( + infuraNetworkType, + { + rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], + }, + ); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }), + }, + ({ controller }) => { + const existingNetworkClient = controller.getNetworkClientById( + 'AAAA-AAAA-AAAA-AAAA', + ); + const destroySpy = jest.spyOn(existingNetworkClient, 'destroy'); + + controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + defaultRpcEndpointUrl: rpcEndpoint2.url, + rpcEndpoints: [rpcEndpoint2], + }); + + expect(destroySpy).toHaveBeenCalled(); + const networkClientRegistry = + controller.getNetworkClientRegistry(); + expect(networkClientRegistry).not.toHaveProperty( + 'AAAA-AAAA-AAAA-AAAA', + ); + }, + ); + }); + + it('updates the network configuration in state', async () => { + const [rpcEndpoint1, rpcEndpoint2] = [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint/1', + }), + buildCustomRpcEndpoint({ + name: 'Endpoint 2', + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://rpc.endpoint/2', + }), + ]; + const networkConfigurationToUpdate = buildInfuraNetworkConfiguration( + infuraNetworkType, + { + rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], + }, + ); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }), + }, + ({ controller }) => { + controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + defaultRpcEndpointUrl: rpcEndpoint2.url, + rpcEndpoints: [rpcEndpoint2], + }); + + expect( + controller.state.networkConfigurationsByChainId[infuraChainId], + ).toStrictEqual({ + ...networkConfigurationToUpdate, + defaultRpcEndpointUrl: rpcEndpoint2.url, + rpcEndpoints: [rpcEndpoint2], + }); + }, + ); + }); + + it('returns the updated network configuration', async () => { + const [rpcEndpoint1, rpcEndpoint2] = [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint/1', + }), + buildCustomRpcEndpoint({ + name: 'Endpoint 2', + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://rpc.endpoint/2', + }), + ]; + const networkConfigurationToUpdate = buildInfuraNetworkConfiguration( + infuraNetworkType, + { + rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], + }, + ); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }), + }, + ({ controller }) => { + const updatedNetworkConfiguration = controller.updateNetwork( + infuraChainId, + { + ...networkConfigurationToUpdate, + defaultRpcEndpointUrl: rpcEndpoint2.url, + rpcEndpoints: [rpcEndpoint2], + }, + ); + + expect(updatedNetworkConfiguration).toStrictEqual({ + ...networkConfigurationToUpdate, + defaultRpcEndpointUrl: rpcEndpoint2.url, + rpcEndpoints: [rpcEndpoint2], + }); + }, + ); + }); + }); + + describe('when the networkClientId of some custom RPC endpoints are being cleared', () => { + it('destroys and unregisters existing network clients for the RPC endpoints', async () => { + const [rpcEndpoint1, rpcEndpoint2] = [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint/1', + }), + buildCustomRpcEndpoint({ + name: 'Endpoint 2', + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://rpc.endpoint/2', + }), + ]; + const networkConfigurationToUpdate = buildInfuraNetworkConfiguration( + infuraNetworkType, + { + rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], + }, + ); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }), + }, + ({ controller }) => { + const existingNetworkClient = controller.getNetworkClientById( + 'BBBB-BBBB-BBBB-BBBB', + ); + const destroySpy = jest.spyOn(existingNetworkClient, 'destroy'); + + controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + rpcEndpoint1, + { ...rpcEndpoint2, networkClientId: undefined }, + ], + }); + + expect(destroySpy).toHaveBeenCalled(); + const networkClientRegistry = + controller.getNetworkClientRegistry(); + expect(networkClientRegistry).not.toHaveProperty( + 'BBBB-BBBB-BBBB-BBBB', + ); + }, + ); + }); + + it('creates and registers new network clients for the RPC endpoints', async () => { + uuidV4Mock.mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC'); + const createAutoManagedNetworkClientSpy = jest.spyOn( + createAutoManagedNetworkClientModule, + 'createAutoManagedNetworkClient', + ); + const [rpcEndpoint1, rpcEndpoint2] = [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.endpoint/1', + }), + buildCustomRpcEndpoint({ + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://test.endpoint/2', + }), + ]; + const networkConfigurationToUpdate = buildInfuraNetworkConfiguration( + infuraNetworkType, + { + rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], + }, + ); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }), + }, + ({ controller }) => { + controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + rpcEndpoint1, + { ...rpcEndpoint2, networkClientId: undefined }, + ], + }); + + expect(createAutoManagedNetworkClientSpy).toHaveBeenCalledWith({ + chainId: infuraChainId, + rpcUrl: 'https://test.endpoint/2', + ticker: infuraNativeTokenName, + type: NetworkClientType.Custom, + }); + + const networkConfigurationsByNetworkClientId = + getNetworkConfigurationsByNetworkClientId( + controller.getNetworkClientRegistry(), + ); + expect(networkConfigurationsByNetworkClientId).not.toHaveProperty( + 'BBBB-BBBB-BBBB-BBBB', + ); + expect(networkConfigurationsByNetworkClientId).toMatchObject({ + 'CCCC-CCCC-CCCC-CCCC': { + chainId: infuraChainId, + rpcUrl: 'https://test.endpoint/2', + ticker: infuraNativeTokenName, + type: NetworkClientType.Custom, + }, + }); + }, + ); + }); + + it('assigns the IDs of the new network clients to the RPC endpoints in state', async () => { + uuidV4Mock.mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC'); + const [rpcEndpoint1, rpcEndpoint2] = [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.endpoint/1', + }), + buildCustomRpcEndpoint({ + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://test.endpoint/2', + }), + ]; + const networkConfigurationToUpdate = buildInfuraNetworkConfiguration( + infuraNetworkType, + { + rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], + }, + ); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }), + }, + ({ controller }) => { + controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + rpcEndpoint1, + { ...rpcEndpoint2, networkClientId: undefined }, + ], + }); + + expect( + controller.state.networkConfigurationsByChainId[infuraChainId], + ).toStrictEqual({ + ...networkConfigurationToUpdate, + rpcEndpoints: [ + rpcEndpoint1, + { + ...rpcEndpoint2, + networkClientId: 'CCCC-CCCC-CCCC-CCCC', + }, + ], + }); + }, + ); + }); + + it('returns the updated network configuration', async () => { + uuidV4Mock.mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC'); + const [rpcEndpoint1, rpcEndpoint2] = [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.endpoint/1', + }), + buildCustomRpcEndpoint({ + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://test.endpoint/2', + }), + ]; + const networkConfigurationToUpdate = buildInfuraNetworkConfiguration( + infuraNetworkType, + { + rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], + }, + ); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }), + }, + ({ controller }) => { + const updatedNetworkConfiguration = controller.updateNetwork( + infuraChainId, + { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + rpcEndpoint1, + { ...rpcEndpoint2, networkClientId: undefined }, + ], + }, + ); + + expect(updatedNetworkConfiguration).toStrictEqual({ + ...networkConfigurationToUpdate, + rpcEndpoints: [ + rpcEndpoint1, + { + ...rpcEndpoint2, + networkClientId: 'CCCC-CCCC-CCCC-CCCC', + }, + ], + }); + }, + ); + }); + }); + + describe('when no RPC endpoints are being changed', () => { + it('does not touch the network client registry', async () => { + const networkConfigurationToUpdate = buildInfuraNetworkConfiguration( + infuraNetworkType, + { + name: 'Some Name', + rpcEndpoints: [buildInfuraRpcEndpoint(infuraNetworkType)], + }, + ); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }), + }, + ({ controller }) => { + mockCreateNetworkClient().mockReturnValue(buildFakeClient()); + const networkClientRegistry = + controller.getNetworkClientRegistry(); + + controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + name: 'Some Other Name', + }); + + expect(controller.getNetworkClientRegistry()).toStrictEqual( + networkClientRegistry, + ); + }, + ); + }); + }); + } + + describe('if the existing chain ID is not an Infura-supported chain and is not being changed', () => { + it('throws (albeit for a different reason) if an Infura RPC endpoint is being added that represents a different chain than the one being updated', async () => { + const defaultRpcEndpoint = buildInfuraRpcEndpoint( + InfuraNetworkType.mainnet, + ); + const networkConfigurationToUpdate = buildNetworkConfiguration({ + chainId: '0x1337', + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + [ChainId.mainnet]: buildInfuraNetworkConfiguration( + InfuraNetworkType.mainnet, + ), + }, + }), + }, + ({ controller }) => { + expect(() => + controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + ...networkConfigurationToUpdate.rpcEndpoints, + defaultRpcEndpoint, + ], + }), + ).toThrow( + "Cannot update network to point to same RPC endpoint as existing network for chain 0x1 ('Mainnet')", + ); + }, + ); + }); + + describe('when new custom RPC endpoints are being added', () => { + it('creates and registers new network clients for each RPC endpoint', async () => { + uuidV4Mock + .mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB') + .mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC'); + const createAutoManagedNetworkClientSpy = jest.spyOn( + createAutoManagedNetworkClientModule, + 'createAutoManagedNetworkClient', + ); + const rpcEndpoint1 = buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint/1', + }); + const networkConfigurationToUpdate = buildNetworkConfiguration({ + chainId: '0x1337', + nativeTokenName: 'TOKEN', + rpcEndpoints: [rpcEndpoint1], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }), + }, + ({ controller }) => { + controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + defaultRpcEndpointUrl: rpcEndpoint1.url, + rpcEndpoints: [ + rpcEndpoint1, + buildUpdateNetworkCustomRpcEndpointFields({ + name: 'Endpoint 2', + url: 'https://rpc.endpoint/2', + }), + buildUpdateNetworkCustomRpcEndpointFields({ + name: 'Endpoint 3', + url: 'https://rpc.endpoint/3', + }), + ], + }); + + expect(createAutoManagedNetworkClientSpy).toHaveBeenCalledWith({ + chainId: '0x1337', + rpcUrl: 'https://rpc.endpoint/2', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }); + expect(createAutoManagedNetworkClientSpy).toHaveBeenCalledWith({ + chainId: '0x1337', + rpcUrl: 'https://rpc.endpoint/3', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }); + + expect( + getNetworkConfigurationsByNetworkClientId( + controller.getNetworkClientRegistry(), + ), + ).toMatchObject({ + 'AAAA-AAAA-AAAA-AAAA': { + chainId: '0x1337', + rpcUrl: 'https://rpc.endpoint/1', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }, + 'BBBB-BBBB-BBBB-BBBB': { + chainId: '0x1337', + rpcUrl: 'https://rpc.endpoint/2', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }, + 'CCCC-CCCC-CCCC-CCCC': { + chainId: '0x1337', + rpcUrl: 'https://rpc.endpoint/3', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }, + }); + }, + ); + }); + + it('assigns the ID of the created network client to each RPC endpoint in state', async () => { + uuidV4Mock + .mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB') + .mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC'); + const rpcEndpoint1 = buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint/1', + }); + const networkConfigurationToUpdate = buildNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [rpcEndpoint1], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }), + }, + ({ controller }) => { + controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + defaultRpcEndpointUrl: rpcEndpoint1.url, + rpcEndpoints: [ + rpcEndpoint1, + buildUpdateNetworkCustomRpcEndpointFields({ + name: 'Endpoint 2', + url: 'https://rpc.endpoint/2', + }), + buildUpdateNetworkCustomRpcEndpointFields({ + name: 'Endpoint 3', + url: 'https://rpc.endpoint/3', + }), + ], + }); + + expect( + controller.state.networkConfigurationsByChainId['0x1337'], + ).toStrictEqual({ + ...networkConfigurationToUpdate, + rpcEndpoints: [ + rpcEndpoint1, + { + name: 'Endpoint 2', + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + type: RpcEndpointType.Custom, + url: 'https://rpc.endpoint/2', + }, + { + name: 'Endpoint 3', + networkClientId: 'CCCC-CCCC-CCCC-CCCC', + type: RpcEndpointType.Custom, + url: 'https://rpc.endpoint/3', + }, + ], + }); + }, + ); + }); + + it('returns the updated network configuration', async () => { + uuidV4Mock + .mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB') + .mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC'); + const rpcEndpoint1 = buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint/1', + }); + const networkConfigurationToUpdate = buildNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [rpcEndpoint1], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }), + }, + ({ controller }) => { + const updatedNetworkConfiguration = controller.updateNetwork( + '0x1337', + { + ...networkConfigurationToUpdate, + defaultRpcEndpointUrl: rpcEndpoint1.url, + rpcEndpoints: [ + rpcEndpoint1, + buildUpdateNetworkCustomRpcEndpointFields({ + name: 'Endpoint 2', + url: 'https://rpc.endpoint/2', + }), + buildUpdateNetworkCustomRpcEndpointFields({ + name: 'Endpoint 3', + url: 'https://rpc.endpoint/3', + }), + ], + }, + ); + + expect(updatedNetworkConfiguration).toStrictEqual({ + ...networkConfigurationToUpdate, + rpcEndpoints: [ + rpcEndpoint1, + { + name: 'Endpoint 2', + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + type: RpcEndpointType.Custom, + url: 'https://rpc.endpoint/2', + }, + { + name: 'Endpoint 3', + networkClientId: 'CCCC-CCCC-CCCC-CCCC', + type: RpcEndpointType.Custom, + url: 'https://rpc.endpoint/3', + }, + ], + }); + }, + ); + }); + }); + + describe('when some custom RPC endpoints are being removed', () => { + it('destroys and unregisters existing network clients for the RPC endpoints', async () => { + const [rpcEndpoint1, rpcEndpoint2] = [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint/1', + }), + buildCustomRpcEndpoint({ + name: 'Endpoint 2', + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://rpc.endpoint/2', + }), + ]; + const networkConfigurationToUpdate = buildNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }), + }, + ({ controller }) => { + const existingNetworkClient = controller.getNetworkClientById( + 'AAAA-AAAA-AAAA-AAAA', + ); + const destroySpy = jest.spyOn(existingNetworkClient, 'destroy'); + + controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + defaultRpcEndpointUrl: rpcEndpoint2.url, + rpcEndpoints: [rpcEndpoint2], + }); + + expect(destroySpy).toHaveBeenCalled(); + const networkClientRegistry = + controller.getNetworkClientRegistry(); + expect(networkClientRegistry).not.toHaveProperty( + 'AAAA-AAAA-AAAA-AAAA', + ); + }, + ); + }); + + it('updates the network configuration in state', async () => { + const [rpcEndpoint1, rpcEndpoint2] = [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint/1', + }), + buildCustomRpcEndpoint({ + name: 'Endpoint 2', + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://rpc.endpoint/2', + }), + ]; + const networkConfigurationToUpdate = buildNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }), + }, + ({ controller }) => { + controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + defaultRpcEndpointUrl: rpcEndpoint2.url, + rpcEndpoints: [rpcEndpoint2], + }); + + expect( + controller.state.networkConfigurationsByChainId['0x1337'], + ).toStrictEqual({ + ...networkConfigurationToUpdate, + defaultRpcEndpointUrl: rpcEndpoint2.url, + rpcEndpoints: [rpcEndpoint2], + }); + }, + ); + }); + + it('returns the updated network configuration', async () => { + const [rpcEndpoint1, rpcEndpoint2] = [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint/1', + }), + buildCustomRpcEndpoint({ + name: 'Endpoint 2', + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://rpc.endpoint/2', + }), + ]; + const networkConfigurationToUpdate = buildNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }), + }, + ({ controller }) => { + const updatedNetworkConfiguration = controller.updateNetwork( + '0x1337', + { + ...networkConfigurationToUpdate, + defaultRpcEndpointUrl: rpcEndpoint2.url, + rpcEndpoints: [rpcEndpoint2], + }, + ); + + expect(updatedNetworkConfiguration).toStrictEqual({ + ...networkConfigurationToUpdate, + defaultRpcEndpointUrl: rpcEndpoint2.url, + rpcEndpoints: [rpcEndpoint2], + }); + }, + ); + }); + }); + + describe('when the networkClientId of some custom RPC endpoints are being cleared', () => { + it('destroys and unregisters existing network clients for the RPC endpoints', async () => { + const [rpcEndpoint1, rpcEndpoint2] = [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint/1', + }), + buildCustomRpcEndpoint({ + name: 'Endpoint 2', + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://rpc.endpoint/2', + }), + ]; + const networkConfigurationToUpdate = buildNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }), + }, + ({ controller }) => { + const existingNetworkClient = controller.getNetworkClientById( + 'BBBB-BBBB-BBBB-BBBB', + ); + const destroySpy = jest.spyOn(existingNetworkClient, 'destroy'); + + controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + defaultRpcEndpointUrl: rpcEndpoint2.url, + rpcEndpoints: [ + rpcEndpoint1, + { ...rpcEndpoint2, networkClientId: undefined }, + ], + }); + + expect(destroySpy).toHaveBeenCalled(); + const networkClientRegistry = + controller.getNetworkClientRegistry(); + expect(networkClientRegistry).not.toHaveProperty( + 'BBBB-BBBB-BBBB-BBBB', + ); + }, + ); + }); + + it('creates and registers new network clients for the RPC endpoints', async () => { + uuidV4Mock.mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC'); + const createAutoManagedNetworkClientSpy = jest.spyOn( + createAutoManagedNetworkClientModule, + 'createAutoManagedNetworkClient', + ); + const [rpcEndpoint1, rpcEndpoint2] = [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.endpoint/1', + }), + buildCustomRpcEndpoint({ + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://test.endpoint/2', + }), + ]; + const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ + chainId: '0x1337', + defaultRpcEndpointUrl: 'https://test.endpoint/1', + nativeTokenName: 'TOKEN', + rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }), + }, + ({ controller }) => { + controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + rpcEndpoint1, + { ...rpcEndpoint2, networkClientId: undefined }, + ], + }); + + expect(createAutoManagedNetworkClientSpy).toHaveBeenCalledWith({ + chainId: '0x1337', + rpcUrl: 'https://test.endpoint/2', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }); + + expect( + getNetworkConfigurationsByNetworkClientId( + controller.getNetworkClientRegistry(), + ), + ).toMatchObject({ + 'AAAA-AAAA-AAAA-AAAA': { + chainId: '0x1337', + rpcUrl: 'https://test.endpoint/1', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }, + 'CCCC-CCCC-CCCC-CCCC': { + chainId: '0x1337', + rpcUrl: 'https://test.endpoint/2', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }, + }); + }, + ); + }); + + it('assigns the IDs of the new network clients to the RPC endpoints in state', async () => { + uuidV4Mock.mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC'); + const [rpcEndpoint1, rpcEndpoint2] = [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.endpoint/1', + }), + buildCustomRpcEndpoint({ + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://test.endpoint/2', + }), + ]; + const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ + chainId: '0x1337', + defaultRpcEndpointUrl: 'https://test.endpoint/1', + rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }), + }, + ({ controller }) => { + controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + rpcEndpoint1, + { ...rpcEndpoint2, networkClientId: undefined }, + ], + }); + + expect( + controller.state.networkConfigurationsByChainId['0x1337'], + ).toStrictEqual({ + ...networkConfigurationToUpdate, + rpcEndpoints: [ + rpcEndpoint1, + { + ...rpcEndpoint2, + networkClientId: 'CCCC-CCCC-CCCC-CCCC', + }, + ], + }); + }, + ); + }); + + it('returns the updated network configuration', async () => { + uuidV4Mock.mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC'); + const [rpcEndpoint1, rpcEndpoint2] = [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.endpoint/1', + }), + buildCustomRpcEndpoint({ + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://test.endpoint/2', + }), + ]; + const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ + chainId: '0x1337', + defaultRpcEndpointUrl: 'https://test.endpoint/1', + rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }), + }, + ({ controller }) => { + const updatedNetworkConfiguration = controller.updateNetwork( + '0x1337', + { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + rpcEndpoint1, + { ...rpcEndpoint2, networkClientId: undefined }, + ], + }, + ); + + expect(updatedNetworkConfiguration).toStrictEqual({ + ...networkConfigurationToUpdate, + rpcEndpoints: [ + rpcEndpoint1, + { + ...rpcEndpoint2, + networkClientId: 'CCCC-CCCC-CCCC-CCCC', + }, + ], + }); + }, + ); + }); + }); + + describe('when no RPC endpoints are being changed', () => { + it('does not touch the network client registry', async () => { + const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.endpoint/1', + }), + ], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }), + }, + ({ controller }) => { + mockCreateNetworkClient().mockReturnValue(buildFakeClient()); + const networkClientRegistry = + controller.getNetworkClientRegistry(); + + controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + name: 'Some Other Name', + }); + + expect(controller.getNetworkClientRegistry()).toStrictEqual( + networkClientRegistry, + ); + }, + ); + }); + }); + }); + + const possibleInfuraNetworkTypes = Object.values(InfuraNetworkType); + possibleInfuraNetworkTypes.forEach( + (infuraNetworkType, infuraNetworkTypeIndex) => { + const infuraNetworkNickname = NetworkNickname[infuraNetworkType]; + const infuraChainId = ChainId[infuraNetworkType]; + const anotherInfuraNetworkType = + possibleInfuraNetworkTypes[ + (infuraNetworkTypeIndex + 1) % possibleInfuraNetworkTypes.length + ]; + const anotherInfuraChainId = ChainId[anotherInfuraNetworkType]; + const anotherInfuraNetworkNickname = + NetworkNickname[anotherInfuraNetworkType]; + + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + describe(`if the chain ID is being changed from a non-Infura-supported chain to the Infura-supported chain ${infuraChainId}`, () => { + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + it(`throws if a network configuration for the Infura network "${infuraNetworkNickname}" is already registered under the new chain ID`, async () => { + const networkConfigurationToUpdate = + buildCustomNetworkConfiguration({ chainId: '0x1337' }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId( + { + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + [infuraChainId]: + buildInfuraNetworkConfiguration(infuraNetworkType), + }, + }, + ), + }, + ({ controller }) => { + expect(() => + controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + chainId: infuraChainId, + }), + ).toThrow( + // This is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `Cannot move network from chain 0x1337 to ${infuraChainId} as another network for that chain already exists ('${infuraNetworkNickname}')`, + ); + }, + ); + }); + + it('throws (albeit for a different reason) if an Infura RPC endpoint is being added that represents a different chain than the one being changed to', async () => { + const networkConfigurationToUpdate = + buildCustomNetworkConfiguration({ chainId: '0x1337' }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId( + { + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + [anotherInfuraChainId]: buildInfuraNetworkConfiguration( + anotherInfuraNetworkType, + ), + }, + }, + ), + }, + ({ controller }) => { + expect(() => + controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + chainId: infuraChainId, + rpcEndpoints: [ + ...networkConfigurationToUpdate.rpcEndpoints, + buildInfuraRpcEndpoint(anotherInfuraNetworkType), + ], + }), + ).toThrow( + new Error( + // This is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `Cannot update network to point to same RPC endpoint as existing network for chain ${anotherInfuraChainId} ('${anotherInfuraNetworkNickname}')`, + ), + ); + }, + ); + }); + + it('re-files the existing network configuration from under the old chain ID to under the new one, regenerating network client IDs for each RPC endpoint', async () => { + uuidV4Mock + .mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC') + .mockReturnValueOnce('DDDD-DDDD-DDDD-DDDD'); + + const [rpcEndpoint1, rpcEndpoint2] = [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.endpoint/1', + }), + buildCustomRpcEndpoint({ + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://test.endpoint/2', + }), + ]; + const networkConfigurationToUpdate = buildNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId( + { + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }, + ), + }, + ({ controller }) => { + controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + chainId: infuraChainId, + }); + + expect( + controller.state.networkConfigurationsByChainId, + ).not.toHaveProperty('0x1337'); + expect( + controller.state.networkConfigurationsByChainId, + ).toHaveProperty(infuraChainId); + expect( + controller.state.networkConfigurationsByChainId[ + infuraChainId + ], + ).toStrictEqual({ + ...networkConfigurationToUpdate, + chainId: infuraChainId, + rpcEndpoints: [ + { + ...rpcEndpoint1, + networkClientId: 'CCCC-CCCC-CCCC-CCCC', + }, + { + ...rpcEndpoint2, + networkClientId: 'DDDD-DDDD-DDDD-DDDD', + }, + ], + }); + }, + ); + }); + + it('destroys and unregisters every network client for each of the RPC endpoints (even if none of the endpoint URLs were changed)', async () => { + const networkConfigurationToUpdate = buildNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + name: 'Test Network 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.endpoint/1', + }), + buildCustomRpcEndpoint({ + name: 'Test Network 2', + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://test.endpoint/2', + }), + ], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId( + { + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }, + ), + }, + ({ controller }) => { + const existingNetworkClient1 = controller.getNetworkClientById( + 'AAAA-AAAA-AAAA-AAAA', + ); + const destroySpy1 = jest.spyOn( + existingNetworkClient1, + 'destroy', + ); + const existingNetworkClient2 = controller.getNetworkClientById( + 'BBBB-BBBB-BBBB-BBBB', + ); + const destroySpy2 = jest.spyOn( + existingNetworkClient2, + 'destroy', + ); + + controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + chainId: infuraChainId, + }); + + expect(destroySpy1).toHaveBeenCalled(); + expect(destroySpy2).toHaveBeenCalled(); + const networkClientRegistry = + controller.getNetworkClientRegistry(); + expect(networkClientRegistry).not.toHaveProperty( + 'AAAA-AAAA-AAAA-AAAA', + ); + expect(networkClientRegistry).not.toHaveProperty( + 'BBBB-BBBB-BBBB-BBBB', + ); + }, + ); + }); + + it('creates and registers new network clients for each of the given RPC endpoints', async () => { + uuidV4Mock + .mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC') + .mockReturnValueOnce('DDDD-DDDD-DDDD-DDDD'); + + const createAutoManagedNetworkClientSpy = jest.spyOn( + createAutoManagedNetworkClientModule, + 'createAutoManagedNetworkClient', + ); + + const networkConfigurationToUpdate = buildNetworkConfiguration({ + chainId: '0x1337', + nativeTokenName: 'TOKEN', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + name: 'Test Network 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.endpoint/1', + }), + buildCustomRpcEndpoint({ + name: 'Test Network 2', + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://test.endpoint/2', + }), + ], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId( + { + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }, + ), + }, + ({ controller }) => { + controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + chainId: infuraChainId, + }); + + expect(createAutoManagedNetworkClientSpy).toHaveBeenCalledWith({ + chainId: infuraChainId, + rpcUrl: 'https://test.endpoint/1', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }); + expect(createAutoManagedNetworkClientSpy).toHaveBeenCalledWith({ + chainId: infuraChainId, + rpcUrl: 'https://test.endpoint/2', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }); + + expect( + getNetworkConfigurationsByNetworkClientId( + controller.getNetworkClientRegistry(), + ), + ).toMatchObject({ + 'CCCC-CCCC-CCCC-CCCC': { + chainId: infuraChainId, + rpcUrl: 'https://test.endpoint/1', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }, + 'DDDD-DDDD-DDDD-DDDD': { + chainId: infuraChainId, + rpcUrl: 'https://test.endpoint/2', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }, + }); + }, + ); + }); + + it('returns the updated network configuration', async () => { + uuidV4Mock + .mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC') + .mockReturnValueOnce('DDDD-DDDD-DDDD-DDDD'); + + const [rpcEndpoint1, rpcEndpoint2] = [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.endpoint/1', + }), + buildCustomRpcEndpoint({ + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://test.endpoint/2', + }), + ]; + const networkConfigurationToUpdate = buildNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId( + { + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }, + ), + }, + ({ controller }) => { + const updatedNetworkConfiguration = controller.updateNetwork( + '0x1337', + { + ...networkConfigurationToUpdate, + chainId: infuraChainId, + }, + ); + + expect(updatedNetworkConfiguration).toStrictEqual({ + ...networkConfigurationToUpdate, + chainId: infuraChainId, + rpcEndpoints: [ + { + ...rpcEndpoint1, + networkClientId: 'CCCC-CCCC-CCCC-CCCC', + }, + { + ...rpcEndpoint2, + networkClientId: 'DDDD-DDDD-DDDD-DDDD', + }, + ], + }); + }, + ); + }); + }); + + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + describe(`if the chain ID is being changed from the Infura-supported chain ${infuraChainId} to a non-Infura-supported chain`, () => { + it('throws if a network configuration for a custom network is already registered under the new chain ID', async () => { + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId( + { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + '0x1337': buildNetworkConfiguration({ + name: 'Some Network', + chainId: '0x1337', + }), + }, + }, + ), + }, + ({ controller }) => { + expect(() => + controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + chainId: '0x1337', + }), + ).toThrow( + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `Cannot move network from chain ${infuraChainId} to 0x1337 as another network for that chain already exists ('Some Network')`, + ); + }, + ); + }); + + it('throws if the existing Infura RPC endpoint is not removed in the process of changing the chain ID', async () => { + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId( + { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + ), + }, + ({ controller }) => { + expect(() => + controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + chainId: '0x1337', + }), + ).toThrow( + new Error( + // This is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `Cannot update network with chain ID 0x1337 and Infura RPC endpoint for '${infuraNetworkNickname}' which represents ${infuraChainId}, as the two conflict`, + ), + ); + }, + ); + }); + + it('re-files the existing network configuration from under the old chain ID to under the new one, regenerating network client IDs for each custom RPC endpoint', async () => { + uuidV4Mock + .mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC') + .mockReturnValueOnce('DDDD-DDDD-DDDD-DDDD'); + + const [defaultRpcEndpoint, customRpcEndpoint1, customRpcEndpoint2] = + [ + buildInfuraRpcEndpoint(infuraNetworkType), + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.endpoint/1', + }), + buildCustomRpcEndpoint({ + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://test.endpoint/2', + }), + ]; + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + rpcEndpoints: [ + defaultRpcEndpoint, + customRpcEndpoint1, + customRpcEndpoint2, + ], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId( + { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + ), + }, + ({ controller }) => { + controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + chainId: '0x1337', + defaultRpcEndpointUrl: customRpcEndpoint1.url, + rpcEndpoints: [customRpcEndpoint1, customRpcEndpoint2], + }); + + expect( + controller.state.networkConfigurationsByChainId, + ).not.toHaveProperty(infuraChainId); + expect( + controller.state.networkConfigurationsByChainId, + ).toHaveProperty('0x1337'); + expect( + controller.state.networkConfigurationsByChainId['0x1337'], + ).toStrictEqual({ + ...networkConfigurationToUpdate, + chainId: '0x1337', + defaultRpcEndpointUrl: customRpcEndpoint1.url, + rpcEndpoints: [ + { + ...customRpcEndpoint1, + networkClientId: 'CCCC-CCCC-CCCC-CCCC', + }, + { + ...customRpcEndpoint2, + networkClientId: 'DDDD-DDDD-DDDD-DDDD', + }, + ], + }); + }, + ); + }); + + it('destroys and unregisters every network client for each of the custom RPC endpoints (even if none of the endpoint URLs were changed)', async () => { + const [defaultRpcEndpoint, customRpcEndpoint1, customRpcEndpoint2] = + [ + buildInfuraRpcEndpoint(infuraNetworkType), + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.endpoint/1', + }), + buildCustomRpcEndpoint({ + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://test.endpoint/2', + }), + ]; + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + rpcEndpoints: [ + defaultRpcEndpoint, + customRpcEndpoint1, + customRpcEndpoint2, + ], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId( + { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + ), + }, + ({ controller }) => { + const existingNetworkClient1 = controller.getNetworkClientById( + 'AAAA-AAAA-AAAA-AAAA', + ); + const destroySpy1 = jest.spyOn( + existingNetworkClient1, + 'destroy', + ); + const existingNetworkClient2 = controller.getNetworkClientById( + 'BBBB-BBBB-BBBB-BBBB', + ); + const destroySpy2 = jest.spyOn( + existingNetworkClient2, + 'destroy', + ); + + controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + chainId: '0x1337', + defaultRpcEndpointUrl: customRpcEndpoint1.url, + rpcEndpoints: [customRpcEndpoint1, customRpcEndpoint2], + }); + + expect(destroySpy1).toHaveBeenCalled(); + expect(destroySpy2).toHaveBeenCalled(); + const networkClientRegistry = + controller.getNetworkClientRegistry(); + expect(networkClientRegistry).not.toHaveProperty( + 'AAAA-AAAA-AAAA-AAAA', + ); + expect(networkClientRegistry).not.toHaveProperty( + 'BBBB-BBBB-BBBB-BBBB', + ); + }, + ); + }); + + it('creates and registers new network clients for each of the given custom RPC endpoints', async () => { + uuidV4Mock + .mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC') + .mockReturnValueOnce('DDDD-DDDD-DDDD-DDDD'); + + const createAutoManagedNetworkClientSpy = jest.spyOn( + createAutoManagedNetworkClientModule, + 'createAutoManagedNetworkClient', + ); + + const [defaultRpcEndpoint, customRpcEndpoint1, customRpcEndpoint2] = + [ + buildInfuraRpcEndpoint(infuraNetworkType), + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.endpoint/1', + }), + buildCustomRpcEndpoint({ + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://test.endpoint/2', + }), + ]; + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + nativeTokenName: 'ETH', + rpcEndpoints: [ + defaultRpcEndpoint, + customRpcEndpoint1, + customRpcEndpoint2, + ], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId( + { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + ), + }, + ({ controller }) => { + controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + chainId: '0x1337', + defaultRpcEndpointUrl: customRpcEndpoint1.url, + rpcEndpoints: [customRpcEndpoint1, customRpcEndpoint2], + }); + + expect(createAutoManagedNetworkClientSpy).toHaveBeenCalledWith({ + chainId: '0x1337', + rpcUrl: 'https://test.endpoint/1', + ticker: 'ETH', type: NetworkClientType.Custom, - ticker: 'TEST', - }) - .mockReturnValue(newCustomNetworkClient); - // Will use mainnet by default - await controller.initializeProvider(); + }); + expect(createAutoManagedNetworkClientSpy).toHaveBeenCalledWith({ + chainId: '0x1337', + rpcUrl: 'https://test.endpoint/2', + ticker: 'ETH', + type: NetworkClientType.Custom, + }); - await controller.upsertNetworkConfiguration( - { - rpcUrl: 'https://test.network', - chainId: toHex(111), - ticker: 'TICKER', + expect( + getNetworkConfigurationsByNetworkClientId( + controller.getNetworkClientRegistry(), + ), + ).toMatchObject({ + 'CCCC-CCCC-CCCC-CCCC': { + chainId: '0x1337', + rpcUrl: 'https://test.endpoint/1', + ticker: 'ETH', + type: NetworkClientType.Custom, + }, + 'DDDD-DDDD-DDDD-DDDD': { + chainId: '0x1337', + rpcUrl: 'https://test.endpoint/2', + ticker: 'ETH', + type: NetworkClientType.Custom, + }, + }); + }, + ); + }); + + it('returns the updated network configuration', async () => { + uuidV4Mock + .mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC') + .mockReturnValueOnce('DDDD-DDDD-DDDD-DDDD'); + + const [defaultRpcEndpoint, customRpcEndpoint1, customRpcEndpoint2] = + [ + buildInfuraRpcEndpoint(infuraNetworkType), + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.endpoint/1', + }), + buildCustomRpcEndpoint({ + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://test.endpoint/2', + }), + ]; + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + rpcEndpoints: [ + defaultRpcEndpoint, + customRpcEndpoint1, + customRpcEndpoint2, + ], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId( + { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + ), + }, + ({ controller }) => { + const updatedNetworkConfiguration = controller.updateNetwork( + infuraChainId, + { + ...networkConfigurationToUpdate, + chainId: '0x1337', + defaultRpcEndpointUrl: customRpcEndpoint1.url, + rpcEndpoints: [customRpcEndpoint1, customRpcEndpoint2], + }, + ); + + expect(updatedNetworkConfiguration).toStrictEqual({ + ...networkConfigurationToUpdate, + chainId: '0x1337', + defaultRpcEndpointUrl: customRpcEndpoint1.url, + rpcEndpoints: [ + { + ...customRpcEndpoint1, + networkClientId: 'CCCC-CCCC-CCCC-CCCC', + }, + { + ...customRpcEndpoint2, + networkClientId: 'DDDD-DDDD-DDDD-DDDD', + }, + ], + }); + }, + ); + }); + }); + + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + describe(`if the chain ID is being changed from the Infura-supported chain ${infuraChainId} to a different Infura-supported chain ${anotherInfuraChainId}`, () => { + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + it(`throws if a network configuration for the Infura network "${infuraNetworkNickname}" is already registered under the new chain ID`, async () => { + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId( + { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + [anotherInfuraChainId]: buildInfuraNetworkConfiguration( + anotherInfuraNetworkType, + ), + }, + }, + ), + }, + ({ controller }) => { + expect(() => + controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + chainId: anotherInfuraChainId, + }), + ).toThrow( + // This is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `Cannot move network from chain ${infuraChainId} to ${anotherInfuraChainId} as another network for that chain already exists ('${anotherInfuraNetworkNickname}')`, + ); + }, + ); + }); + + it('throws if the existing Infura RPC endpoint is not updated in the process of changing the chain ID', async () => { + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId( + { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + ), + }, + ({ controller }) => { + expect(() => + controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + chainId: anotherInfuraChainId, + }), + ).toThrow( + new Error( + // This is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `Cannot update network with chain ID ${anotherInfuraChainId} and Infura RPC endpoint for '${infuraNetworkNickname}' which represents ${infuraChainId}, as the two conflict`, + ), + ); + }, + ); + }); + + it('re-files the existing network configuration from under the old chain ID to under the new one, regenerating network client IDs for each custom RPC endpoint', async () => { + uuidV4Mock + .mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC') + .mockReturnValueOnce('DDDD-DDDD-DDDD-DDDD'); + + const [defaultRpcEndpoint, customRpcEndpoint1, customRpcEndpoint2] = + [ + buildInfuraRpcEndpoint(infuraNetworkType), + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.endpoint/1', + }), + buildCustomRpcEndpoint({ + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://test.endpoint/2', + }), + ]; + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + rpcEndpoints: [ + defaultRpcEndpoint, + customRpcEndpoint1, + customRpcEndpoint2, + ], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId( + { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + ), + }, + ({ controller }) => { + controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + chainId: anotherInfuraChainId, + defaultRpcEndpointUrl: customRpcEndpoint1.url, + rpcEndpoints: [customRpcEndpoint1, customRpcEndpoint2], + }); + + expect( + controller.state.networkConfigurationsByChainId, + ).not.toHaveProperty(infuraChainId); + expect( + controller.state.networkConfigurationsByChainId, + ).toHaveProperty(anotherInfuraChainId); + expect( + controller.state.networkConfigurationsByChainId[ + anotherInfuraChainId + ], + ).toStrictEqual({ + ...networkConfigurationToUpdate, + chainId: anotherInfuraChainId, + defaultRpcEndpointUrl: customRpcEndpoint1.url, + rpcEndpoints: [ + { + ...customRpcEndpoint1, + networkClientId: 'CCCC-CCCC-CCCC-CCCC', + }, + { + ...customRpcEndpoint2, + networkClientId: 'DDDD-DDDD-DDDD-DDDD', + }, + ], + }); + }, + ); + }); + + it('destroys and unregisters every network client for each of the custom RPC endpoints (even if none of the endpoint URLs were changed)', async () => { + const [defaultRpcEndpoint, customRpcEndpoint1, customRpcEndpoint2] = + [ + buildInfuraRpcEndpoint(infuraNetworkType), + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.endpoint/1', + }), + buildCustomRpcEndpoint({ + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://test.endpoint/2', + }), + ]; + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + rpcEndpoints: [ + defaultRpcEndpoint, + customRpcEndpoint1, + customRpcEndpoint2, + ], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId( + { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + ), + }, + ({ controller }) => { + const existingNetworkClient1 = controller.getNetworkClientById( + 'AAAA-AAAA-AAAA-AAAA', + ); + const destroySpy1 = jest.spyOn( + existingNetworkClient1, + 'destroy', + ); + const existingNetworkClient2 = controller.getNetworkClientById( + 'BBBB-BBBB-BBBB-BBBB', + ); + const destroySpy2 = jest.spyOn( + existingNetworkClient2, + 'destroy', + ); + + controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + chainId: anotherInfuraChainId, + defaultRpcEndpointUrl: customRpcEndpoint1.url, + rpcEndpoints: [customRpcEndpoint1, customRpcEndpoint2], + }); + + expect(destroySpy1).toHaveBeenCalled(); + expect(destroySpy2).toHaveBeenCalled(); + const networkClientRegistry = + controller.getNetworkClientRegistry(); + expect(networkClientRegistry).not.toHaveProperty( + 'AAAA-AAAA-AAAA-AAAA', + ); + expect(networkClientRegistry).not.toHaveProperty( + 'BBBB-BBBB-BBBB-BBBB', + ); + }, + ); + }); + + it('creates and registers new network clients for each of the given custom RPC endpoints', async () => { + uuidV4Mock + .mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC') + .mockReturnValueOnce('DDDD-DDDD-DDDD-DDDD'); + + const createAutoManagedNetworkClientSpy = jest.spyOn( + createAutoManagedNetworkClientModule, + 'createAutoManagedNetworkClient', + ); + + const [defaultRpcEndpoint, customRpcEndpoint1, customRpcEndpoint2] = + [ + buildInfuraRpcEndpoint(infuraNetworkType), + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.endpoint/1', + }), + buildCustomRpcEndpoint({ + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://test.endpoint/2', + }), + ]; + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + nativeTokenName: 'ETH', + rpcEndpoints: [ + defaultRpcEndpoint, + customRpcEndpoint1, + customRpcEndpoint2, + ], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId( + { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + ), + }, + ({ controller }) => { + controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + chainId: anotherInfuraChainId, + defaultRpcEndpointUrl: customRpcEndpoint1.url, + rpcEndpoints: [customRpcEndpoint1, customRpcEndpoint2], + }); + + expect(createAutoManagedNetworkClientSpy).toHaveBeenCalledWith({ + chainId: anotherInfuraChainId, + rpcUrl: 'https://test.endpoint/1', + ticker: 'ETH', + type: NetworkClientType.Custom, + }); + expect(createAutoManagedNetworkClientSpy).toHaveBeenCalledWith({ + chainId: anotherInfuraChainId, + rpcUrl: 'https://test.endpoint/2', + ticker: 'ETH', + type: NetworkClientType.Custom, + }); + + expect( + getNetworkConfigurationsByNetworkClientId( + controller.getNetworkClientRegistry(), + ), + ).toMatchObject({ + 'CCCC-CCCC-CCCC-CCCC': { + chainId: anotherInfuraChainId, + rpcUrl: 'https://test.endpoint/1', + ticker: 'ETH', + type: NetworkClientType.Custom, + }, + 'DDDD-DDDD-DDDD-DDDD': { + chainId: anotherInfuraChainId, + rpcUrl: 'https://test.endpoint/2', + ticker: 'ETH', + type: NetworkClientType.Custom, + }, + }); + }, + ); + }); + + it('returns the updated network configuration', async () => { + uuidV4Mock + .mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC') + .mockReturnValueOnce('DDDD-DDDD-DDDD-DDDD'); + + const [defaultRpcEndpoint, customRpcEndpoint1, customRpcEndpoint2] = + [ + buildInfuraRpcEndpoint(infuraNetworkType), + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.endpoint/1', + }), + buildCustomRpcEndpoint({ + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://test.endpoint/2', + }), + ]; + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + rpcEndpoints: [ + defaultRpcEndpoint, + customRpcEndpoint1, + customRpcEndpoint2, + ], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId( + { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + ), + }, + ({ controller }) => { + const updatedNetworkConfiguration = controller.updateNetwork( + infuraChainId, + { + ...networkConfigurationToUpdate, + chainId: anotherInfuraChainId, + defaultRpcEndpointUrl: customRpcEndpoint1.url, + rpcEndpoints: [customRpcEndpoint1, customRpcEndpoint2], + }, + ); + + expect(updatedNetworkConfiguration).toStrictEqual({ + ...networkConfigurationToUpdate, + chainId: anotherInfuraChainId, + defaultRpcEndpointUrl: customRpcEndpoint1.url, + rpcEndpoints: [ + { + ...customRpcEndpoint1, + networkClientId: 'CCCC-CCCC-CCCC-CCCC', + }, + { + ...customRpcEndpoint2, + networkClientId: 'DDDD-DDDD-DDDD-DDDD', + }, + ], + }); + }, + ); + }); + }); + }, + ); + + describe('if the chain ID is being changed from one non-Infura-supported chain to another', () => { + it('throws if a network configuration for a custom network is already registered under the new chain ID', async () => { + const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ + chainId: '0x1337', + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + '0x2448': buildNetworkConfiguration({ chainId: '0x2448' }), }, - { - setActive: false, - referrer: 'https://test-dapp.com', - source: 'dapp', + }), + }, + ({ controller }) => { + expect(() => + controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + chainId: '0x2448', + }), + ).toThrow( + "Cannot move network from chain 0x1337 to 0x2448 as another network for that chain already exists ('Some Network')", + ); + }, + ); + }); + + it('throws (albeit for a different reason) if an Infura RPC endpoint is being added that represents a different chain than the one being changed to', async () => { + const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ + chainId: '0x1337', + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + [ChainId.goerli]: buildInfuraNetworkConfiguration( + InfuraNetworkType.goerli, + ), }, - ); + }), + }, + ({ controller }) => { + const newRpcEndpoint = buildInfuraRpcEndpoint( + InfuraNetworkType.goerli, + ); + expect(() => + controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + chainId: '0x2448', + rpcEndpoints: [newRpcEndpoint], + }), + ).toThrow( + new Error( + "Cannot update network to point to same RPC endpoint as existing network for chain 0x5 ('Goerli')", + ), + ); + }, + ); + }); - const { provider } = controller.getProviderAndBlockTracker(); - assert(provider, 'Provider is not set'); - const { result } = await promisify(provider.sendAsync).call( - provider, + it('re-files the existing network configuration from under the old chain ID to under the new one, regenerating network client IDs for each RPC endpoint', async () => { + uuidV4Mock + .mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC') + .mockReturnValueOnce('DDDD-DDDD-DDDD-DDDD'); + + const [rpcEndpoint1, rpcEndpoint2] = [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.endpoint/1', + }), + buildCustomRpcEndpoint({ + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://test.endpoint/2', + }), + ]; + const networkConfigurationToUpdate = buildNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }), + }, + ({ controller }) => { + controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + chainId: '0x2448', + }); + + expect( + controller.state.networkConfigurationsByChainId, + ).not.toHaveProperty('0x1337'); + expect( + controller.state.networkConfigurationsByChainId, + ).toHaveProperty('0x2448'); + expect( + controller.state.networkConfigurationsByChainId['0x2448'], + ).toStrictEqual({ + ...networkConfigurationToUpdate, + chainId: '0x2448', + rpcEndpoints: [ { - id: 1, - jsonrpc: '2.0', - method: 'test_method', - params: [], + ...rpcEndpoint1, + networkClientId: 'CCCC-CCCC-CCCC-CCCC', }, - ); - expect(result).toBe('test response from built-in network'); - }, - ); - }); + { + ...rpcEndpoint2, + networkClientId: 'DDDD-DDDD-DDDD-DDDD', + }, + ], + }); + }, + ); }); - describe('if the setActive option is true', () => { - it('updates selectedNetworkClientId to refer to the new network configuration', async () => { - await withController(async ({ controller }) => { - uuidV4Mock.mockReturnValue('AAAA-AAAA-AAAA-AAAA'); - const newCustomNetworkClient = buildFakeClient(); - mockCreateNetworkClientWithDefaultsForBuiltInNetworkClients() - .calledWith({ - chainId: toHex(111), - rpcUrl: 'https://test.network', - type: NetworkClientType.Custom, - ticker: 'TICKER', - }) - .mockReturnValue(newCustomNetworkClient); + it('destroys and unregisters every network client for each of the RPC endpoints (even if none of the endpoint URLs were changed)', async () => { + const networkConfigurationToUpdate = buildNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + name: 'Test Network 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.endpoint/1', + }), + buildCustomRpcEndpoint({ + name: 'Test Network 2', + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://test.endpoint/2', + }), + ], + }); - await controller.upsertNetworkConfiguration( - { - rpcUrl: 'https://test.network', - chainId: toHex(111), - ticker: 'TICKER', - nickname: 'test network', - rpcPrefs: { - blockExplorerUrl: 'https://some.chainscan.io', + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, }, - }, - { - setActive: true, - referrer: 'https://test-dapp.com', - source: 'dapp', - }, - ); - - expect(controller.state.selectedNetworkClientId).toBe( + }), + }, + ({ controller }) => { + const existingNetworkClient1 = controller.getNetworkClientById( 'AAAA-AAAA-AAAA-AAAA', ); - }); - }); + const destroySpy1 = jest.spyOn(existingNetworkClient1, 'destroy'); + const existingNetworkClient2 = controller.getNetworkClientById( + 'BBBB-BBBB-BBBB-BBBB', + ); + const destroySpy2 = jest.spyOn(existingNetworkClient2, 'destroy'); - refreshNetworkTests({ - expectedNetworkClientConfiguration: - buildCustomNetworkClientConfiguration({ - rpcUrl: 'https://some.other.network', - chainId: toHex(222), - ticker: 'TICKER2', - }), - initialState: { - networkConfigurations: { - 'AAAA-AAAA-AAAA-AAAA': { - rpcUrl: 'https://test.network', - chainId: toHex(111), - ticker: 'TICKER1', - id: 'AAAA-AAAA-AAAA-AAAA', - }, - }, - }, - operation: async (controller) => { - uuidV4Mock.mockReturnValue('BBBB-BBBB-BBBB-BBBB'); + controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + chainId: '0x2448', + }); - await controller.upsertNetworkConfiguration( - { - rpcUrl: 'https://some.other.network', - chainId: toHex(222), - ticker: 'TICKER2', - }, - { - setActive: true, - referrer: 'https://test-dapp.com', - source: 'dapp', - }, + expect(destroySpy1).toHaveBeenCalled(); + expect(destroySpy2).toHaveBeenCalled(); + const networkClientRegistry = controller.getNetworkClientRegistry(); + expect(networkClientRegistry).not.toHaveProperty( + 'AAAA-AAAA-AAAA-AAAA', + ); + expect(networkClientRegistry).not.toHaveProperty( + 'BBBB-BBBB-BBBB-BBBB', ); }, - }); + ); }); - it('calls trackMetaMetricsEvent with details about the new network', async () => { - const trackMetaMetricsEventSpy = jest.fn(); + it('creates and registers new network clients for each of the given RPC endpoints', async () => { + uuidV4Mock + .mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC') + .mockReturnValueOnce('DDDD-DDDD-DDDD-DDDD'); + + const createAutoManagedNetworkClientSpy = jest.spyOn( + createAutoManagedNetworkClientModule, + 'createAutoManagedNetworkClient', + ); + + const networkConfigurationToUpdate = buildNetworkConfiguration({ + chainId: '0x1337', + nativeTokenName: 'TOKEN', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + name: 'Test Network 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.endpoint/1', + }), + buildCustomRpcEndpoint({ + name: 'Test Network 2', + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://test.endpoint/2', + }), + ], + }); await withController( { - trackMetaMetricsEvent: trackMetaMetricsEventSpy, + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }), }, - async ({ controller }) => { - uuidV4Mock.mockReturnValue('AAAA-AAAA-AAAA-AAAA'); + ({ controller }) => { + controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + chainId: '0x2448', + }); - await controller.upsertNetworkConfiguration( - { - rpcUrl: 'https://test.network', - chainId: toHex(111), - ticker: 'TICKER', - }, - { - referrer: 'https://test-dapp.com', - source: 'dapp', - }, - ); + expect(createAutoManagedNetworkClientSpy).toHaveBeenCalledWith({ + chainId: '0x2448', + rpcUrl: 'https://test.endpoint/1', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }); + expect(createAutoManagedNetworkClientSpy).toHaveBeenCalledWith({ + chainId: '0x2448', + rpcUrl: 'https://test.endpoint/2', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }); - expect(trackMetaMetricsEventSpy).toHaveBeenCalledWith({ - event: 'Custom Network Added', - category: 'Network', - referrer: { - url: 'https://test-dapp.com', + expect( + getNetworkConfigurationsByNetworkClientId( + controller.getNetworkClientRegistry(), + ), + ).toMatchObject({ + 'CCCC-CCCC-CCCC-CCCC': { + chainId: '0x2448', + rpcUrl: 'https://test.endpoint/1', + ticker: 'TOKEN', + type: NetworkClientType.Custom, }, - properties: { - // TODO: Either fix this lint violation or explain why it's necessary to ignore. - // eslint-disable-next-line @typescript-eslint/naming-convention - chain_id: toHex(111), - symbol: 'TICKER', - source: 'dapp', + 'DDDD-DDDD-DDDD-DDDD': { + chainId: '0x2448', + rpcUrl: 'https://test.endpoint/2', + ticker: 'TOKEN', + type: NetworkClientType.Custom, }, }); }, ); }); - }); - describe.each([ - ['case-sensitively', 'https://test.network', 'https://test.network'], - ['case-insensitively', 'https://test.network', 'https://TEST.NETWORK'], - ])( - 'when the rpcUrl of the given network configuration matches an existing network configuration in state (%s)', - (_qualifier, oldRpcUrl, newRpcUrl) => { - it('completely overwrites the existing network configuration in state, but does not update or remove any other network configurations', async () => { - await withController( - { - state: { - networkConfigurations: { - 'AAAA-AAAA-AAAA-AAAA': { - rpcUrl: 'https://test.network.1', - chainId: toHex(111), - ticker: 'TICKER1', - id: 'AAAA-AAAA-AAAA-AAAA', - }, - 'BBBB-BBBB-BBBB-BBBB': { - rpcUrl: oldRpcUrl, - chainId: toHex(222), - ticker: 'TICKER2', - id: 'BBBB-BBBB-BBBB-BBBB', - }, - }, - }, - }, - async ({ controller }) => { - await controller.upsertNetworkConfiguration( - { - rpcUrl: newRpcUrl, - chainId: toHex(999), - ticker: 'NEW_TICKER', - nickname: 'test network 2', - rpcPrefs: { - blockExplorerUrl: 'https://testchainscan.io', - }, - }, - { - referrer: 'https://test-dapp.com', - source: 'dapp', - }, - ); + it('returns the updated network configuration', async () => { + uuidV4Mock + .mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC') + .mockReturnValueOnce('DDDD-DDDD-DDDD-DDDD'); - expect(controller.state.networkConfigurations).toStrictEqual({ - 'AAAA-AAAA-AAAA-AAAA': { - rpcUrl: 'https://test.network.1', - chainId: toHex(111), - ticker: 'TICKER1', - id: 'AAAA-AAAA-AAAA-AAAA', - }, - 'BBBB-BBBB-BBBB-BBBB': { - rpcUrl: newRpcUrl, - chainId: toHex(999), - ticker: 'NEW_TICKER', - nickname: 'test network 2', - rpcPrefs: { - blockExplorerUrl: 'https://testchainscan.io', - }, - id: 'BBBB-BBBB-BBBB-BBBB', - }, - }); - }, - ); + const [rpcEndpoint1, rpcEndpoint2] = [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.endpoint/1', + }), + buildCustomRpcEndpoint({ + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://test.endpoint/2', + }), + ]; + const networkConfigurationToUpdate = buildNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], }); - it('removes properties not specific to the NetworkConfiguration interface before persisting it to state', async function () { - await withController( - { - state: { - networkConfigurations: { - 'AAAA-AAAA-AAAA-AAAA': { - rpcUrl: oldRpcUrl, - chainId: toHex(111), - ticker: 'TICKER', - id: 'AAAA-AAAA-AAAA-AAAA', - }, + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, }, + }), + }, + ({ controller }) => { + const updatedNetworkConfiguration = controller.updateNetwork( + '0x1337', + { + ...networkConfigurationToUpdate, + chainId: '0x2448', }, - }, - async ({ controller }) => { - await controller.upsertNetworkConfiguration( + ); + + expect(updatedNetworkConfiguration).toStrictEqual({ + ...networkConfigurationToUpdate, + chainId: '0x2448', + rpcEndpoints: [ { - rpcUrl: newRpcUrl, - chainId: toHex(999), - ticker: 'NEW_TICKER', - nickname: 'test network', - rpcPrefs: { - blockExplorerUrl: 'https://testchainscan.io', - }, - // @ts-expect-error We are intentionally passing bad input. - invalidKey: 'some value', + ...rpcEndpoint1, + networkClientId: 'CCCC-CCCC-CCCC-CCCC', }, { - referrer: 'https://test-dapp.com', - source: 'dapp', + ...rpcEndpoint2, + networkClientId: 'DDDD-DDDD-DDDD-DDDD', }, - ); + ], + }); + }, + ); + }); + }); - expect(controller.state.networkConfigurations).toStrictEqual({ - 'AAAA-AAAA-AAAA-AAAA': { - rpcUrl: newRpcUrl, - chainId: toHex(999), - ticker: 'NEW_TICKER', - nickname: 'test network', - rpcPrefs: { - blockExplorerUrl: 'https://testchainscan.io', - }, - id: 'AAAA-AAAA-AAAA-AAAA', - }, - }); - }, - ); - }); + describe('if nothing is being changed', () => { + for (const infuraNetworkType of Object.values(InfuraNetworkType)) { + const infuraChainId = ChainId[infuraNetworkType]; + + // This is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + describe(`if the given chain ID is the Infura-supported chain ${infuraChainId}`, () => { + it('makes no updates to state', async () => { + const existingNetworkConfiguration = + buildInfuraNetworkConfiguration(infuraNetworkType); - describe('if at least the chain ID is being updated', () => { - it('destroys and removes the existing network client for the old network configuration', async () => { await withController( { - state: { - networkConfigurations: { - 'AAAA-AAAA-AAAA-AAAA': { - rpcUrl: oldRpcUrl, - chainId: toHex(111), - ticker: 'TICKER', - id: 'AAAA-AAAA-AAAA-AAAA', + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId( + { + networkConfigurationsByChainId: { + [infuraChainId]: existingNetworkConfiguration, + }, }, - }, - }, - infuraProjectId: 'some-infura-project-id', + ), }, - async ({ controller }) => { - const newCustomNetworkClient = buildFakeClient(); - mockCreateNetworkClientWithDefaultsForBuiltInNetworkClients({ - infuraProjectId: 'some-infura-project-id', - }) - .calledWith({ - chainId: toHex(111), - rpcUrl: 'https://test.network', - type: NetworkClientType.Custom, - ticker: 'TEST', - }) - .mockReturnValue(newCustomNetworkClient); - 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'); - - await controller.upsertNetworkConfiguration( - { - rpcUrl: newRpcUrl, - chainId: toHex(999), - ticker: 'TICKER', - }, - { - referrer: 'https://test-dapp.com', - source: 'dapp', - }, + ({ controller }) => { + controller.updateNetwork( + infuraChainId, + existingNetworkConfiguration, ); - const networkClients = controller.getNetworkClientRegistry(); - expect(networkClientToDestroy.destroy).toHaveBeenCalled(); - expect(Object.keys(networkClients)).toHaveLength(7); - expect(networkClients).not.toMatchObject({ - [oldRpcUrl]: expect.objectContaining({ - configuration: { - chainId: toHex(111), - rpcUrl: oldRpcUrl, - type: NetworkClientType.Custom, - ticker: 'TEST', - }, - }), - }); + expect( + controller.state.networkConfigurationsByChainId[ + infuraChainId + ], + ).toStrictEqual(existingNetworkConfiguration); }, ); }); - it('creates a new network client for the network configuration and adds it to the registry', async () => { + it('does not destroy any existing clients for the network', async () => { + const existingNetworkConfiguration = + buildInfuraNetworkConfiguration(infuraNetworkType); + await withController( { - state: { - networkConfigurations: { - 'AAAA-AAAA-AAAA-AAAA': { - rpcUrl: oldRpcUrl, - chainId: toHex(111), - ticker: 'TICKER', - id: 'AAAA-AAAA-AAAA-AAAA', + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId( + { + networkConfigurationsByChainId: { + [infuraChainId]: existingNetworkConfiguration, + }, }, - }, - }, - infuraProjectId: 'some-infura-project-id', + ), }, - async ({ controller }) => { - const newCustomNetworkClient = buildFakeClient(); - mockCreateNetworkClientWithDefaultsForBuiltInNetworkClients({ - infuraProjectId: 'some-infura-project-id', - }) - .calledWith({ - chainId: toHex(999), - rpcUrl: newRpcUrl, - type: NetworkClientType.Custom, - ticker: 'TICKER', - }) - .mockReturnValue(newCustomNetworkClient); - - await controller.upsertNetworkConfiguration( - { - rpcUrl: newRpcUrl, - chainId: toHex(999), - ticker: 'TICKER', - }, - { - referrer: 'https://test-dapp.com', - source: 'dapp', - }, + ({ controller }) => { + const existingNetworkClient = + controller.getNetworkClientById(infuraNetworkType); + const destroySpy = jest.spyOn(existingNetworkClient, 'destroy'); + + controller.updateNetwork( + infuraChainId, + existingNetworkConfiguration, ); - const networkClients = controller.getNetworkClientRegistry(); - expect(Object.keys(networkClients)).toHaveLength(7); - expect(networkClients).toMatchObject({ - 'AAAA-AAAA-AAAA-AAAA': expect.objectContaining({ - configuration: { - chainId: toHex(999), - rpcUrl: newRpcUrl, - type: NetworkClientType.Custom, - ticker: 'TICKER', - }, - }), - }); + expect(destroySpy).not.toHaveBeenCalled(); }, ); }); - }); - describe('if the chain ID is not being updated', () => { - it('does not update the network client registry', async () => { + it('does not create any new clients for the network', async () => { + const existingNetworkConfiguration = + buildInfuraNetworkConfiguration(infuraNetworkType); + + const createAutoManagedNetworkClientSpy = jest.spyOn( + createAutoManagedNetworkClientModule, + 'createAutoManagedNetworkClient', + ); + await withController( { - state: { - networkConfigurations: { - 'AAAA-AAAA-AAAA-AAAA': { - rpcUrl: oldRpcUrl, - chainId: toHex(111), - ticker: 'TICKER', - id: 'AAAA-AAAA-AAAA-AAAA', + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId( + { + networkConfigurationsByChainId: { + [infuraChainId]: existingNetworkConfiguration, + }, }, - }, - }, - infuraProjectId: 'some-infura-project-id', + ), }, - async ({ controller }) => { - const newCustomNetworkClient = buildFakeClient(); - mockCreateNetworkClientWithDefaultsForBuiltInNetworkClients({ - infuraProjectId: 'some-infura-project-id', - }) - .calledWith({ - chainId: toHex(111), - rpcUrl: 'https://test.network', - type: NetworkClientType.Custom, - ticker: 'TEST', - }) - .mockReturnValue(newCustomNetworkClient); - const networkClientsBefore = - controller.getNetworkClientRegistry(); - - await controller.upsertNetworkConfiguration( - { - rpcUrl: newRpcUrl, - chainId: toHex(111), - ticker: 'NEW_TICKER', - }, - { - referrer: 'https://test-dapp.com', - source: 'dapp', - }, + ({ controller }) => { + controller.updateNetwork( + infuraChainId, + existingNetworkConfiguration, ); - const networkClientsAfter = - controller.getNetworkClientRegistry(); - expect(networkClientsBefore).toStrictEqual(networkClientsAfter); - }, - ); - }); - }); - - it('does not call trackMetaMetricsEvent', async () => { - const trackMetaMetricsEventSpy = jest.fn(); - - await withController( - { - state: { - networkConfigurations: { - 'AAAA-AAAA-AAAA-AAAA': { - rpcUrl: oldRpcUrl, - chainId: toHex(111), - ticker: 'TICKER', - id: 'AAAA-AAAA-AAAA-AAAA', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - trackMetaMetricsEvent: trackMetaMetricsEventSpy, - }, - async ({ controller }) => { - await controller.upsertNetworkConfiguration( - { - rpcUrl: newRpcUrl, - chainId: toHex(111), - ticker: 'NEW_TICKER', - }, - { - referrer: 'https://test-dapp.com', - source: 'dapp', - }, - ); - - expect(trackMetaMetricsEventSpy).not.toHaveBeenCalled(); - }, - ); - }); - }, - ); - - it('throws if the given chain ID is not a 0x-prefixed hex number', async () => { - await withController(async ({ controller }) => { - await expect( - controller.upsertNetworkConfiguration( - { - rpcUrl: 'https://test.network', - // @ts-expect-error We are intentionally passing bad input. - chainId: '1', - ticker: 'TICKER', - }, - { - referrer: 'https://test-dapp.com', - source: 'dapp', - }, - ), - ).rejects.toThrow( - new Error('Value must be a hexadecimal string, starting with "0x".'), - ); - }); - }); - - it('throws if the given chain ID is greater than the maximum allowed ID', async () => { - await withController(async ({ controller }) => { - await expect( - controller.upsertNetworkConfiguration( - { - rpcUrl: 'https://test.network', - // TODO: Either fix this lint violation or explain why it's necessary to ignore. - // eslint-disable-next-line @typescript-eslint/restrict-plus-operands - chainId: toHex(MAX_SAFE_CHAIN_ID + 1), - ticker: 'TICKER', - }, - { - referrer: 'https://test-dapp.com', - source: 'dapp', - }, - ), - ).rejects.toThrow( - new Error( - 'Invalid chain ID "0xfffffffffffed": numerical value greater than max safe value.', - ), - ); - }); - }); + // Once when the controller is initialized, but no more + expect(createAutoManagedNetworkClientSpy).toHaveBeenCalledTimes( + 1, + ); + }, + ); + }); + }); + } - it('throws if a falsy rpcUrl is given', async () => { - await withController(async ({ controller }) => { - await expect(() => - controller.upsertNetworkConfiguration( - { - // @ts-expect-error We are intentionally passing bad input. - rpcUrl: false, - chainId: toHex(111), - ticker: 'TICKER', - }, - { - referrer: 'https://test-dapp.com', - source: 'dapp', - }, - ), - ).rejects.toThrow( - new Error( - 'An rpcUrl is required to add or update network configuration', - ), - ); - }); - }); + describe('if the given chain ID is not an Infura-supported chain', () => { + it('makes no updates to state', async () => { + const existingNetworkConfiguration = buildCustomNetworkConfiguration({ + chainId: '0x1337', + }); - it('throws if no rpcUrl is given', async () => { - await withController(async ({ controller }) => { - await expect( - controller.upsertNetworkConfiguration( - // @ts-expect-error We are intentionally passing bad input. - { - chainId: toHex(111), - ticker: 'TICKER', - }, + await withController( { - referrer: 'https://test-dapp.com', - source: 'dapp', + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': existingNetworkConfiguration, + }, + }), }, - ), - ).rejects.toThrow( - new Error( - 'An rpcUrl is required to add or update network configuration', - ), - ); - }); - }); + ({ controller }) => { + controller.updateNetwork('0x1337', existingNetworkConfiguration); - it('throws if the rpcUrl given is not a valid URL', async () => { - await withController(async ({ controller }) => { - await expect( - controller.upsertNetworkConfiguration( - { - rpcUrl: 'test', - chainId: toHex(111), - ticker: 'TICKER', - }, - { - referrer: 'https://test-dapp.com', - source: 'dapp', + expect( + controller.state.networkConfigurationsByChainId['0x1337'], + ).toStrictEqual(existingNetworkConfiguration); }, - ), - ).rejects.toThrow(new Error('rpcUrl must be a valid URL')); - }); - }); + ); + }); - it('throws if a falsy referrer is given', async () => { - await withController(async ({ controller }) => { - await expect( - controller.upsertNetworkConfiguration( + it('does not destroy any existing clients for the network', async () => { + const existingNetworkConfiguration = buildCustomNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + }), + ], + }); + + await withController( { - rpcUrl: 'https://test.network', - chainId: toHex(111), - ticker: 'TICKER', + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': existingNetworkConfiguration, + }, + }), }, - { - // @ts-expect-error We are intentionally passing bad input. - referrer: false, - source: 'dapp', + ({ controller }) => { + const existingNetworkClient = controller.getNetworkClientById( + 'AAAA-AAAA-AAAA-AAAA', + ); + const destroySpy = jest.spyOn(existingNetworkClient, 'destroy'); + + controller.updateNetwork('0x1337', existingNetworkConfiguration); + + expect(destroySpy).not.toHaveBeenCalled(); }, - ), - ).rejects.toThrow( - new Error( - 'referrer and source are required arguments for adding or updating a network configuration', - ), - ); - }); - }); + ); + }); - it('throws if no referrer is given', async () => { - await withController(async ({ controller }) => { - await expect( - controller.upsertNetworkConfiguration( + it('does not create any new clients for the network', async () => { + const existingNetworkConfiguration = buildCustomNetworkConfiguration({ + chainId: '0x1337', + }); + + const createAutoManagedNetworkClientSpy = jest.spyOn( + createAutoManagedNetworkClientModule, + 'createAutoManagedNetworkClient', + ); + + await withController( { - rpcUrl: 'https://test.network', - chainId: toHex(111), - ticker: 'TICKER', + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': existingNetworkConfiguration, + }, + }), }, - // @ts-expect-error We are intentionally passing bad input. - { - source: 'dapp', + ({ controller }) => { + controller.updateNetwork('0x1337', existingNetworkConfiguration); + + // Once when the controller is initialized, but no more + expect(createAutoManagedNetworkClientSpy).toHaveBeenCalledTimes( + 1, + ); }, - ), - ).rejects.toThrow( - new Error( - 'referrer and source are required arguments for adding or updating a network configuration', - ), - ); + ); + }); }); }); + }); - it('throws if a falsy source is given', async () => { - await withController(async ({ controller }) => { - await expect( - controller.upsertNetworkConfiguration( - { - rpcUrl: 'https://test.network', - chainId: toHex(111), - ticker: 'TICKER', - }, - { - referrer: 'https://test-dapp.com', - // @ts-expect-error We are intentionally passing bad input. - source: false, - }, - ), - ).rejects.toThrow( - new Error( - 'referrer and source are required arguments for adding or updating a network configuration', - ), + describe('removeNetwork', () => { + it('throws if the given chain ID does not refer to an existing network configuration', async () => { + await withController(({ controller }) => { + expect(() => controller.removeNetwork('0x1337')).toThrow( + new Error("Cannot find network configuration for chain '0x1337'"), ); }); }); - it('throws if no source is given', async () => { - await withController(async ({ controller }) => { - await expect( - controller.upsertNetworkConfiguration( - { - rpcUrl: 'https://test.network', - chainId: toHex(111), - ticker: 'TICKER', - }, - // @ts-expect-error We are intentionally passing bad input. - { - referrer: 'https://test-dapp.com', + it('throws if selectedNetworkClientId matches the networkClientId of any RPC endpoint in the existing network configuration', async () => { + await withController( + { + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + }), + ], + }), }, - ), - ).rejects.toThrow( - new Error( - 'referrer and source are required arguments for adding or updating a network configuration', - ), - ); - }); + }, + }, + ({ controller }) => { + expect(() => controller.removeNetwork('0x1337')).toThrow( + 'Cannot remove the currently selected network', + ); + }, + ); }); - it('throws if a falsy ticker is given', async () => { - await withController(async ({ controller }) => { - await expect( - controller.upsertNetworkConfiguration( + for (const infuraNetworkType of Object.values(InfuraNetworkType)) { + const infuraChainId = ChainId[infuraNetworkType]; + + // This is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + describe(`when the given chain ID is the Infura-supported chain ${infuraChainId}`, () => { + it('removes the existing network configuration from state', async () => { + await withController( { - rpcUrl: 'https://test.network', - chainId: toHex(111), - // @ts-expect-error We are intentionally passing bad input. - ticker: false, + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + [infuraChainId]: + buildInfuraNetworkConfiguration(infuraNetworkType), + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + }), + ], + }), + }, + }, }, - { - referrer: 'https://test-dapp.com', - source: 'dapp', + ({ controller }) => { + expect( + controller.state.networkConfigurationsByChainId, + ).toHaveProperty(infuraChainId); + + controller.removeNetwork(infuraChainId); + + expect( + controller.state.networkConfigurationsByChainId, + ).not.toHaveProperty(infuraChainId); }, - ), - ).rejects.toThrow( - new Error( - 'A ticker is required to add or update networkConfiguration', - ), - ); - }); - }); + ); + }); - it('throws if no ticker is given', async () => { - await withController(async ({ controller }) => { - await expect( - controller.upsertNetworkConfiguration( - // @ts-expect-error We are intentionally passing bad input. + it('destroys and unregisters the network clients for each of the RPC endpoints defined in the network configuration (even the Infura endpoint)', async () => { + const defaultRpcEndpoint = buildInfuraRpcEndpoint(infuraNetworkType); + + await withController( { - rpcUrl: 'https://test.network', - chainId: toHex(111), + state: { + selectedNetworkClientId: 'BBBB-BBBB-BBBB-BBBB', + networkConfigurationsByChainId: { + [infuraChainId]: buildInfuraNetworkConfiguration( + infuraNetworkType, + { + rpcEndpoints: [ + defaultRpcEndpoint, + buildCustomRpcEndpoint({ + name: 'Test Network', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.endpoint', + }), + ], + }, + ), + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + }), + ], + }), + }, + }, }, - { - referrer: 'https://test-dapp.com', - source: 'dapp', + ({ controller }) => { + const existingNetworkClient1 = + controller.getNetworkClientById(infuraNetworkType); + const destroySpy1 = jest.spyOn(existingNetworkClient1, 'destroy'); + const existingNetworkClient2 = controller.getNetworkClientById( + 'AAAA-AAAA-AAAA-AAAA', + ); + const destroySpy2 = jest.spyOn(existingNetworkClient2, 'destroy'); + + controller.removeNetwork(infuraChainId); + + expect(destroySpy1).toHaveBeenCalled(); + expect(destroySpy2).toHaveBeenCalled(); + const networkClientRegistry = + controller.getNetworkClientRegistry(); + expect(networkClientRegistry).not.toHaveProperty( + infuraNetworkType, + ); + expect(networkClientRegistry).not.toHaveProperty( + 'AAAA-AAAA-AAAA-AAAA', + ); }, - ), - ).rejects.toThrow( - new Error( - 'A ticker is required to add or update networkConfiguration', - ), - ); + ); + }); }); - }); - }); + } - describe('removeNetworkConfiguration', () => { - describe('given an ID that identifies a network configuration in state', () => { - it('removes the network configuration from state', async () => { + describe('when the given chain ID is not an Infura-supported chain', () => { + it('removes the existing network configuration', async () => { await withController( { state: { - networkConfigurations: { - 'AAAA-AAAA-AAAA-AAAA': { - rpcUrl: 'https://test.network', - ticker: 'TICKER', - chainId: toHex(111), - id: 'AAAA-AAAA-AAAA-AAAA', - }, + selectedNetworkClientId: InfuraNetworkType.goerli, + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration(), + [ChainId.goerli]: buildInfuraNetworkConfiguration( + InfuraNetworkType.goerli, + ), }, }, }, - async ({ controller }) => { - controller.removeNetworkConfiguration('AAAA-AAAA-AAAA-AAAA'); + ({ controller }) => { + expect( + controller.state.networkConfigurationsByChainId, + ).toHaveProperty('0x1337'); + + controller.removeNetwork('0x1337'); - expect(controller.state.networkConfigurations).toStrictEqual({}); + expect( + controller.state.networkConfigurationsByChainId, + ).not.toHaveProperty('0x1337'); }, ); }); - it('destroys and removes the network client in the network client registry that corresponds to the given ID', async () => { + it('destroys the network clients for each of the RPC endpoints defined in the network configuration', async () => { await withController( { state: { - networkConfigurations: { - 'AAAA-AAAA-AAAA-AAAA': { - rpcUrl: 'https://test.network', - ticker: 'TICKER', - chainId: toHex(111), - id: 'AAAA-AAAA-AAAA-AAAA', - }, + selectedNetworkClientId: InfuraNetworkType.goerli, + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + rpcEndpoints: [ + buildCustomRpcEndpoint({ + name: 'Test Network 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.endpoint/1', + }), + buildCustomRpcEndpoint({ + name: 'Test Network 2', + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://test.endpoint/2', + }), + ], + }), + [ChainId.goerli]: buildInfuraNetworkConfiguration( + InfuraNetworkType.goerli, + ), }, }, }, - 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 }) => { + const existingNetworkClient1 = controller.getNetworkClientById( + 'AAAA-AAAA-AAAA-AAAA', + ); + const destroySpy1 = jest.spyOn(existingNetworkClient1, 'destroy'); + const existingNetworkClient2 = controller.getNetworkClientById( + 'BBBB-BBBB-BBBB-BBBB', + ); + const destroySpy2 = jest.spyOn(existingNetworkClient2, 'destroy'); - controller.removeNetworkConfiguration('AAAA-AAAA-AAAA-AAAA'); + controller.removeNetwork('0x1337'); - 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', - }, - }), - }); + expect(destroySpy1).toHaveBeenCalled(); + expect(destroySpy2).toHaveBeenCalled(); + const networkClientRegistry = controller.getNetworkClientRegistry(); + expect(networkClientRegistry).not.toHaveProperty( + 'AAAA-AAAA-AAAA-AAAA', + ); + expect(networkClientRegistry).not.toHaveProperty( + 'BBBB-BBBB-BBBB-BBBB', + ); }, ); }); }); - - 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('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, - }, - operation: async (controller) => { - await controller.rollbackToPreviousProvider(); - }, - }); + for (const infuraNetworkType of Object.values(InfuraNetworkType)) { + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + describe(`when the selected network client represents the Infura network "${infuraNetworkType}"`, () => { + refreshNetworkTests({ + expectedNetworkClientConfiguration: + buildInfuraNetworkClientConfiguration(infuraNetworkType), + initialState: { + selectedNetworkClientId: infuraNetworkType, + }, + operation: async (controller) => { + await controller.rollbackToPreviousProvider(); + }, }); - }, - ); + }); + } - describe('when selectedNetworkClientId in state is the ID of a network configuration', () => { + describe('when the selected network client represents a custom RPC endpoint', () => { refreshNetworkTests({ expectedNetworkClientConfiguration: buildCustomNetworkClientConfiguration({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), + rpcUrl: 'https://test.network', + chainId: '0x1337', 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', - }, + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeTokenName: 'TEST', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network', + }), + ], + }), }, }, operation: async (controller) => { @@ -3873,26 +7450,29 @@ describe('NetworkController', () => { }); }); - for (const { networkType } of INFURA_NETWORKS) { - // TODO: Either fix this lint violation or explain why it's necessary to ignore. + for (const infuraNetworkType of Object.values(InfuraNetworkType)) { + const infuraChainId = ChainId[infuraNetworkType]; + const infuraNativeTokenName = NetworksTicker[infuraNetworkType]; + + // False negative - this is a string. // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - describe(`when called following a network switch away from the Infura network "${networkType}"`, () => { + describe(`when called following a switch away from the Infura network "${infuraNetworkType}"`, () => { it('emits networkWillChange with state payload', async () => { await withController( { state: { - 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', - }, - }, + selectedNetworkClientId: infuraNetworkType, + networkConfigurationsByChainId: { + [infuraChainId]: + buildInfuraNetworkConfiguration(infuraNetworkType), + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + }), + ], + }), }, }, }, @@ -3900,7 +7480,7 @@ describe('NetworkController', () => { const fakeProvider = buildFakeProvider(); const fakeNetworkClient = buildFakeClient(fakeProvider); mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.setActiveNetwork('testNetworkConfiguration'); + await controller.setActiveNetwork('AAAA-AAAA-AAAA-AAAA'); const networkWillChange = waitForPublishedEvents({ messenger, @@ -3909,7 +7489,6 @@ describe('NetworkController', () => { 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(); }, @@ -3924,18 +7503,18 @@ describe('NetworkController', () => { await withController( { state: { - 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', - }, - }, + selectedNetworkClientId: infuraNetworkType, + networkConfigurationsByChainId: { + [infuraChainId]: + buildInfuraNetworkConfiguration(infuraNetworkType), + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + }), + ], + }), }, }, }, @@ -3943,7 +7522,7 @@ describe('NetworkController', () => { const fakeProvider = buildFakeProvider(); const fakeNetworkClient = buildFakeClient(fakeProvider); mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.setActiveNetwork('testNetworkConfiguration'); + await controller.setActiveNetwork('AAAA-AAAA-AAAA-AAAA'); const networkDidChange = waitForPublishedEvents({ messenger, @@ -3964,24 +7543,28 @@ describe('NetworkController', () => { }); it('sets selectedNetworkClientId in state to the previous version', async () => { + const infuraProjectId = 'some-infura-project-id'; + await withController( { state: { - 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', - }, - }, + selectedNetworkClientId: infuraNetworkType, + networkConfigurationsByChainId: { + [infuraChainId]: + buildInfuraNetworkConfiguration(infuraNetworkType), + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeTokenName: 'TEST', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network', + }), + ], + }), }, }, - infuraProjectId: 'some-infura-project-id', + infuraProjectId, }, async ({ controller }) => { const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; @@ -3991,49 +7574,57 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, + chainId: '0x1337', + rpcUrl: 'https://test.network', ticker: 'TEST', + type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[0]) .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - chainId: BUILT_IN_NETWORKS[networkType].chainId, - ticker: BUILT_IN_NETWORKS[networkType].ticker, + chainId: infuraChainId, + infuraProjectId, + network: infuraNetworkType, + ticker: infuraNativeTokenName, type: NetworkClientType.Infura, }) .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); + await controller.setActiveNetwork('AAAA-AAAA-AAAA-AAAA'); expect(controller.state.selectedNetworkClientId).toBe( - 'testNetworkConfiguration', + 'AAAA-AAAA-AAAA-AAAA', ); await controller.rollbackToPreviousProvider(); expect(controller.state.selectedNetworkClientId).toBe( - networkType, + infuraNetworkType, ); }, ); }); it('resets the network status to "unknown" before updating the provider', async () => { + const infuraProjectId = 'some-infura-project-id'; + await withController( { state: { - selectedNetworkClientId: networkType, - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - }, + selectedNetworkClientId: infuraNetworkType, + networkConfigurationsByChainId: { + [infuraChainId]: + buildInfuraNetworkConfiguration(infuraNetworkType), + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeTokenName: 'TEST', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network', + }), + ], + }), }, }, - infuraProjectId: 'some-infura-project-id', + infuraProjectId, }, async ({ controller, messenger }) => { const fakeProviders = [ @@ -4053,45 +7644,40 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, + chainId: '0x1337', + rpcUrl: 'https://test.network', ticker: 'TEST', + type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[0]) .calledWith({ - network: networkType, - chainId: BUILT_IN_NETWORKS[networkType].chainId, - ticker: BUILT_IN_NETWORKS[networkType].ticker, - infuraProjectId: 'some-infura-project-id', + chainId: infuraChainId, + infuraProjectId, + network: infuraNetworkType, + ticker: infuraNativeTokenName, type: NetworkClientType.Infura, }) .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); + await controller.setActiveNetwork('AAAA-AAAA-AAAA-AAAA'); expect( - controller.state.networksMetadata[ - controller.state.selectedNetworkClientId - ].status, + controller.state.networksMetadata['AAAA-AAAA-AAAA-AAAA'].status, ).toBe('available'); await waitForStateChanges({ messenger, - propertyPath: ['networksMetadata', networkType, 'status'], + propertyPath: ['networksMetadata', infuraNetworkType, '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, + controller.state.networksMetadata[infuraNetworkType].status, ).toBe('unknown'); }, }); @@ -4099,23 +7685,31 @@ describe('NetworkController', () => { ); }); - // This is a string. + // False negative - this is a string. // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - it(`initializes a provider pointed to the "${networkType}" Infura network`, async () => { + it(`initializes a provider pointed to the "${infuraNetworkType}" Infura network`, async () => { + const infuraProjectId = 'some-infura-project-id'; + await withController( { state: { - selectedNetworkClientId: networkType, - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - }, + selectedNetworkClientId: infuraNetworkType, + networkConfigurationsByChainId: { + [infuraChainId]: + buildInfuraNetworkConfiguration(infuraNetworkType), + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeTokenName: 'TEST', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network', + }), + ], + }), }, }, - infuraProjectId: 'some-infura-project-id', + infuraProjectId, }, async ({ controller }) => { const fakeProviders = [ @@ -4137,29 +7731,29 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, + chainId: '0x1337', + rpcUrl: 'https://test.network', ticker: 'TEST', + type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[0]) .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - chainId: BUILT_IN_NETWORKS[networkType].chainId, - ticker: BUILT_IN_NETWORKS[networkType].ticker, + chainId: infuraChainId, + infuraProjectId, + network: infuraNetworkType, + ticker: infuraNativeTokenName, type: NetworkClientType.Infura, }) .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); + await controller.setActiveNetwork('AAAA-AAAA-AAAA-AAAA'); await controller.rollbackToPreviousProvider(); - const { provider } = controller.getProviderAndBlockTracker(); - assert(provider, 'Provider is somehow unset'); - const promisifiedSendAsync = promisify(provider.sendAsync).bind( - provider, - ); + const networkClient = controller.getSelectedNetworkClient(); + assert(networkClient, 'Network client is somehow unset'); + const promisifiedSendAsync = promisify( + networkClient.provider.sendAsync, + ).bind(networkClient.provider); const response = await promisifiedSendAsync({ id: '1', jsonrpc: '2.0', @@ -4171,20 +7765,28 @@ describe('NetworkController', () => { }); it('replaces the provider object underlying the provider proxy without creating a new instance of the proxy itself', async () => { + const infuraProjectId = 'some-infura-project-id'; + await withController( { state: { - selectedNetworkClientId: networkType, - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - }, + selectedNetworkClientId: infuraNetworkType, + networkConfigurationsByChainId: { + [infuraChainId]: + buildInfuraNetworkConfiguration(infuraNetworkType), + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeTokenName: 'TEST', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network', + }), + ], + }), }, }, - infuraProjectId: 'some-infura-project-id', + infuraProjectId, }, async ({ controller }) => { const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; @@ -4194,48 +7796,58 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, + chainId: '0x1337', + rpcUrl: 'https://test.network', ticker: 'TEST', + type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[0]) .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - chainId: BUILT_IN_NETWORKS[networkType].chainId, - ticker: BUILT_IN_NETWORKS[networkType].ticker, + chainId: infuraChainId, + infuraProjectId, + network: infuraNetworkType, + ticker: infuraNativeTokenName, type: NetworkClientType.Infura, }) .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); - const { provider: providerBefore } = - controller.getProviderAndBlockTracker(); + await controller.setActiveNetwork('AAAA-AAAA-AAAA-AAAA'); + const networkClientBefore = controller.getSelectedNetworkClient(); + assert(networkClientBefore, 'Network client is somehow unset'); await controller.rollbackToPreviousProvider(); - const { provider: providerAfter } = - controller.getProviderAndBlockTracker(); - expect(providerBefore).toBe(providerAfter); + const networkClientAfter = controller.getSelectedNetworkClient(); + assert(networkClientAfter, 'Network client is somehow unset'); + expect(networkClientBefore.provider).toBe( + networkClientAfter.provider, + ); }, ); }); it('emits infuraIsBlocked or infuraIsUnblocked, depending on whether Infura is blocking requests for the previous network', async () => { + const infuraProjectId = 'some-infura-project-id'; + await withController( { state: { - selectedNetworkClientId: networkType, - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - }, + selectedNetworkClientId: infuraNetworkType, + networkConfigurationsByChainId: { + [infuraChainId]: + buildInfuraNetworkConfiguration(infuraNetworkType), + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeTokenName: 'TEST', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network', + }), + ], + }), }, }, - infuraProjectId: 'some-infura-project-id', + infuraProjectId, }, async ({ controller, messenger }) => { const fakeProviders = [ @@ -4255,21 +7867,21 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, + chainId: '0x1337', + rpcUrl: 'https://test.network', ticker: 'TEST', + type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[0]) .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - chainId: BUILT_IN_NETWORKS[networkType].chainId, - ticker: BUILT_IN_NETWORKS[networkType].ticker, + chainId: infuraChainId, + infuraProjectId, + network: infuraNetworkType, + ticker: infuraNativeTokenName, type: NetworkClientType.Infura, }) .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); + await controller.setActiveNetwork('AAAA-AAAA-AAAA-AAAA'); const promiseForNoInfuraIsUnblockedEvents = waitForPublishedEvents({ messenger, @@ -4290,20 +7902,28 @@ describe('NetworkController', () => { }); it('checks the status of the previous network again and updates state accordingly', async () => { + const infuraProjectId = 'some-infura-project-id'; + await withController( { state: { - selectedNetworkClientId: networkType, - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - }, + selectedNetworkClientId: infuraNetworkType, + networkConfigurationsByChainId: { + [infuraChainId]: + buildInfuraNetworkConfiguration(infuraNetworkType), + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeTokenName: 'TEST', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network', + }), + ], + }), }, }, - infuraProjectId: 'some-infura-project-id', + infuraProjectId, }, async ({ controller, messenger }) => { const fakeProviders = [ @@ -4330,58 +7950,62 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, + chainId: '0x1337', + rpcUrl: 'https://test.network', ticker: 'TEST', + type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[0]) .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - chainId: BUILT_IN_NETWORKS[networkType].chainId, - ticker: BUILT_IN_NETWORKS[networkType].ticker, + chainId: infuraChainId, + infuraProjectId, + network: infuraNetworkType, + ticker: infuraNativeTokenName, type: NetworkClientType.Infura, }) .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); + await controller.setActiveNetwork('AAAA-AAAA-AAAA-AAAA'); expect( - controller.state.networksMetadata[ - controller.state.selectedNetworkClientId - ].status, + controller.state.networksMetadata['AAAA-AAAA-AAAA-AAAA'].status, ).toBe('unavailable'); await waitForStateChanges({ messenger, - propertyPath: ['networksMetadata', networkType, 'status'], + propertyPath: ['networksMetadata', infuraNetworkType, 'status'], operation: async () => { await controller.rollbackToPreviousProvider(); }, }); expect( - controller.state.networksMetadata[ - controller.state.selectedNetworkClientId - ].status, + controller.state.networksMetadata[infuraNetworkType].status, ).toBe('available'); }, ); }); it('checks whether the previous network supports EIP-1559 again and updates state accordingly', async () => { + const infuraProjectId = 'some-infura-project-id'; + await withController( { state: { - selectedNetworkClientId: networkType, - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - }, + selectedNetworkClientId: infuraNetworkType, + networkConfigurationsByChainId: { + [infuraChainId]: + buildInfuraNetworkConfiguration(infuraNetworkType), + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeTokenName: 'TEST', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network', + }), + ], + }), }, }, - infuraProjectId: 'some-infura-project-id', + infuraProjectId, }, async ({ controller, messenger }) => { const fakeProviders = [ @@ -4412,39 +8036,36 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, + chainId: '0x1337', + rpcUrl: 'https://test.network', ticker: 'TEST', + type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[0]) .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - chainId: BUILT_IN_NETWORKS[networkType].chainId, - ticker: BUILT_IN_NETWORKS[networkType].ticker, + chainId: infuraChainId, + infuraProjectId, + network: infuraNetworkType, + ticker: infuraNativeTokenName, type: NetworkClientType.Infura, }) .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); + await controller.setActiveNetwork('AAAA-AAAA-AAAA-AAAA'); expect( - controller.state.networksMetadata[ - controller.state.selectedNetworkClientId - ].EIPS[1559], + controller.state.networksMetadata['AAAA-AAAA-AAAA-AAAA'] + .EIPS[1559], ).toBe(false); await waitForStateChanges({ messenger, - propertyPath: ['networksMetadata', networkType, 'EIPS'], + propertyPath: ['networksMetadata', infuraNetworkType, 'EIPS'], count: 2, operation: async () => { await controller.rollbackToPreviousProvider(); }, }); expect( - controller.state.networksMetadata[ - controller.state.selectedNetworkClientId - ].EIPS[1559], + controller.state.networksMetadata[infuraNetworkType].EIPS[1559], ).toBe(true); }, ); @@ -4452,19 +8073,26 @@ describe('NetworkController', () => { }); } - describe('when called following a network switch away from a network configuration', () => { + describe('when called following a switch away from a custom RPC endpoint', () => { 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', - }, + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeTokenName: 'TEST', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network', + }), + ], + }), + [ChainId.goerli]: buildInfuraNetworkConfiguration( + InfuraNetworkType.goerli, + ), }, }, }, @@ -4472,7 +8100,7 @@ describe('NetworkController', () => { const fakeProvider = buildFakeProvider(); const fakeNetworkClient = buildFakeClient(fakeProvider); mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.setProviderType(InfuraNetworkType.goerli); + await controller.setActiveNetwork(InfuraNetworkType.goerli); const networkWillChange = waitForPublishedEvents({ messenger, @@ -4495,14 +8123,21 @@ describe('NetworkController', () => { await withController( { state: { - selectedNetworkClientId: 'testNetworkConfiguration', - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - }, + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeTokenName: 'TEST', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network', + }), + ], + }), + [ChainId.goerli]: buildInfuraNetworkConfiguration( + InfuraNetworkType.goerli, + ), }, }, }, @@ -4510,7 +8145,7 @@ describe('NetworkController', () => { const fakeProvider = buildFakeProvider(); const fakeNetworkClient = buildFakeClient(fakeProvider); mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.setProviderType(InfuraNetworkType.goerli); + await controller.setActiveNetwork(InfuraNetworkType.goerli); const networkDidChange = waitForPublishedEvents({ messenger, @@ -4530,20 +8165,29 @@ describe('NetworkController', () => { }); it('sets selectedNetworkClientId to the previous version', async () => { + const infuraProjectId = 'some-infura-project-id'; + await withController( { state: { - selectedNetworkClientId: 'testNetworkConfiguration', - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - }, + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeTokenName: 'TEST', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network', + }), + ], + }), + [ChainId.goerli]: buildInfuraNetworkConfiguration( + InfuraNetworkType.goerli, + ), }, }, - infuraProjectId: 'some-infura-project-id', + infuraProjectId, }, async ({ controller }) => { const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; @@ -4553,46 +8197,57 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ + chainId: ChainId.goerli, + infuraProjectId, network: InfuraNetworkType.goerli, - infuraProjectId: 'some-infura-project-id', + ticker: NetworksTicker.goerli, 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, + chainId: '0x1337', + rpcUrl: 'https://test.network', ticker: 'TEST', + type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); - expect(controller.state.selectedNetworkClientId).toBe('goerli'); + await controller.setActiveNetwork(InfuraNetworkType.goerli); + expect(controller.state.selectedNetworkClientId).toBe( + InfuraNetworkType.goerli, + ); await controller.rollbackToPreviousProvider(); expect(controller.state.selectedNetworkClientId).toBe( - 'testNetworkConfiguration', + 'AAAA-AAAA-AAAA-AAAA', ); }, ); }); it('resets the network state to "unknown" before updating the provider', async () => { + const infuraProjectId = 'some-infura-project-id'; + await withController( { state: { - selectedNetworkClientId: 'testNetworkConfiguration', - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - }, + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeTokenName: 'TEST', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network', + }), + ], + }), + [ChainId.goerli]: buildInfuraNetworkConfiguration( + InfuraNetworkType.goerli, + ), }, }, - infuraProjectId: 'some-infura-project-id', + infuraProjectId, }, async ({ controller, messenger }) => { const fakeProviders = [ @@ -4612,21 +8267,21 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ + chainId: ChainId.goerli, + infuraProjectId, network: InfuraNetworkType.goerli, - infuraProjectId: 'some-infura-project-id', + ticker: NetworksTicker.goerli, 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, + chainId: '0x1337', + rpcUrl: 'https://test.network', ticker: 'TEST', + type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); + await controller.setActiveNetwork(InfuraNetworkType.goerli); expect( controller.state.networksMetadata[ controller.state.selectedNetworkClientId @@ -4637,7 +8292,7 @@ describe('NetworkController', () => { messenger, propertyPath: [ 'networksMetadata', - 'testNetworkConfiguration', + 'AAAA-AAAA-AAAA-AAAA', 'status', ], // We only care about the first state change, because it @@ -4651,9 +8306,8 @@ describe('NetworkController', () => { }, beforeResolving: () => { expect( - controller.state.networksMetadata[ - controller.state.selectedNetworkClientId - ].status, + controller.state.networksMetadata['AAAA-AAAA-AAAA-AAAA'] + .status, ).toBe('unknown'); }, }); @@ -4662,20 +8316,29 @@ describe('NetworkController', () => { }); it('initializes a provider pointed to the given RPC URL', async () => { + const infuraProjectId = 'some-infura-project-id'; + await withController( { state: { - selectedNetworkClientId: 'testNetworkConfiguration', - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - }, + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeTokenName: 'TEST', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network', + }), + ], + }), + [ChainId.goerli]: buildInfuraNetworkConfiguration( + InfuraNetworkType.goerli, + ), }, }, - infuraProjectId: 'some-infura-project-id', + infuraProjectId, }, async ({ controller }) => { const fakeProviders = [ @@ -4683,7 +8346,7 @@ describe('NetworkController', () => { buildFakeProvider([ { request: { - method: 'test', + method: 'test_method', }, response: { result: 'test response', @@ -4697,33 +8360,33 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ + chainId: ChainId.goerli, + infuraProjectId, network: InfuraNetworkType.goerli, - infuraProjectId: 'some-infura-project-id', + ticker: NetworksTicker.goerli, 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, + chainId: '0x1337', + rpcUrl: 'https://test.network', ticker: 'TEST', + type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); + await controller.setActiveNetwork(InfuraNetworkType.goerli); await controller.rollbackToPreviousProvider(); - const { provider } = controller.getProviderAndBlockTracker(); - assert(provider, 'Provider is somehow unset'); - const promisifiedSendAsync = promisify(provider.sendAsync).bind( - provider, - ); + const networkClient = controller.getSelectedNetworkClient(); + assert(networkClient, 'Network client is somehow unset'); + const promisifiedSendAsync = promisify( + networkClient.provider.sendAsync, + ).bind(networkClient.provider); const response = await promisifiedSendAsync({ id: '1', jsonrpc: '2.0', - method: 'test', + method: 'test_method', }); expect(response.result).toBe('test response'); }, @@ -4731,20 +8394,29 @@ describe('NetworkController', () => { }); it('replaces the provider object underlying the provider proxy without creating a new instance of the proxy itself', async () => { + const infuraProjectId = 'some-infura-project-id'; + await withController( { state: { - selectedNetworkClientId: 'testNetworkConfiguration', - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - }, + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeTokenName: 'TEST', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network', + }), + ], + }), + [ChainId.goerli]: buildInfuraNetworkConfiguration( + InfuraNetworkType.goerli, + ), }, }, - infuraProjectId: 'some-infura-project-id', + infuraProjectId, }, async ({ controller }) => { const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; @@ -4754,48 +8426,59 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ + chainId: ChainId.goerli, + infuraProjectId, network: InfuraNetworkType.goerli, - infuraProjectId: 'some-infura-project-id', + ticker: NetworksTicker.goerli, 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, + chainId: '0x1337', + rpcUrl: 'https://test.network', ticker: 'TEST', + type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); - const { provider: providerBefore } = - controller.getProviderAndBlockTracker(); + await controller.setActiveNetwork(InfuraNetworkType.goerli); + const networkClientBefore = controller.getSelectedNetworkClient(); + assert(networkClientBefore, 'Network client is somehow unset'); await controller.rollbackToPreviousProvider(); - const { provider: providerAfter } = - controller.getProviderAndBlockTracker(); - expect(providerBefore).toBe(providerAfter); + const networkClientAfter = controller.getSelectedNetworkClient(); + assert(networkClientAfter, 'Network client is somehow unset'); + expect(networkClientBefore.provider).toBe( + networkClientAfter.provider, + ); }, ); }); it('emits infuraIsUnblocked', async () => { + const infuraProjectId = 'some-infura-project-id'; + await withController( { state: { - selectedNetworkClientId: 'testNetworkConfiguration', - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - }, + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeTokenName: 'TEST', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network', + }), + ], + }), + [ChainId.goerli]: buildInfuraNetworkConfiguration( + InfuraNetworkType.goerli, + ), }, }, - infuraProjectId: 'some-infura-project-id', + infuraProjectId, }, async ({ controller, messenger }) => { const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; @@ -4805,21 +8488,21 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ + chainId: ChainId.goerli, + infuraProjectId, network: InfuraNetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - chainId: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].chainId, - ticker: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].ticker, + ticker: NetworksTicker.goerli, type: NetworkClientType.Infura, }) .mockReturnValue(fakeNetworkClients[0]) .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, + chainId: '0x1337', + rpcUrl: 'https://test.network', ticker: 'TEST', + type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); + await controller.setActiveNetwork(InfuraNetworkType.goerli); const promiseForInfuraIsUnblocked = waitForPublishedEvents({ messenger, @@ -4835,20 +8518,29 @@ describe('NetworkController', () => { }); it('checks the status of the previous network again and updates state accordingly', async () => { + const infuraProjectId = 'some-infura-project-id'; + await withController( { state: { - selectedNetworkClientId: 'testNetworkConfiguration', - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - }, + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeTokenName: 'TEST', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network', + }), + ], + }), + [ChainId.goerli]: buildInfuraNetworkConfiguration( + InfuraNetworkType.goerli, + ), }, }, - infuraProjectId: 'some-infura-project-id', + infuraProjectId, }, async ({ controller }) => { const fakeProviders = [ @@ -4875,52 +8567,58 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ + chainId: ChainId.goerli, + infuraProjectId, network: InfuraNetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - chainId: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].chainId, - ticker: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].ticker, + ticker: NetworksTicker.goerli, type: NetworkClientType.Infura, }) .mockReturnValue(fakeNetworkClients[0]) .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, + chainId: '0x1337', + rpcUrl: 'https://test.network', ticker: 'TEST', + type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); + await controller.setActiveNetwork(InfuraNetworkType.goerli); expect( - controller.state.networksMetadata[ - controller.state.selectedNetworkClientId - ].status, + controller.state.networksMetadata[InfuraNetworkType.goerli] + .status, ).toBe('unavailable'); await controller.rollbackToPreviousProvider(); expect( - controller.state.networksMetadata[ - controller.state.selectedNetworkClientId - ].status, + controller.state.networksMetadata['AAAA-AAAA-AAAA-AAAA'].status, ).toBe('available'); }, ); }); it('checks whether the previous network supports EIP-1559 again and updates state accordingly', async () => { + const infuraProjectId = 'some-infura-project-id'; + await withController( { state: { - selectedNetworkClientId: 'testNetworkConfiguration', - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - }, + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeTokenName: 'TEST', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network', + }), + ], + }), + [ChainId.goerli]: buildInfuraNetworkConfiguration( + InfuraNetworkType.goerli, + ), }, }, - infuraProjectId: 'some-infura-project-id', + infuraProjectId, }, async ({ controller }) => { const fakeProviders = [ @@ -4951,32 +8649,30 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ + chainId: ChainId.goerli, + infuraProjectId, network: InfuraNetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - chainId: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].chainId, - ticker: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].ticker, + ticker: NetworksTicker.goerli, type: NetworkClientType.Infura, }) .mockReturnValue(fakeNetworkClients[0]) .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, + chainId: '0x1337', + rpcUrl: 'https://test.network', ticker: 'TEST', + type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); + await controller.setActiveNetwork(InfuraNetworkType.goerli); expect( - controller.state.networksMetadata[ - controller.state.selectedNetworkClientId - ].EIPS[1559], + controller.state.networksMetadata[InfuraNetworkType.goerli] + .EIPS[1559], ).toBe(false); await controller.rollbackToPreviousProvider(); expect( - controller.state.networksMetadata[ - controller.state.selectedNetworkClientId - ].EIPS[1559], + controller.state.networksMetadata['AAAA-AAAA-AAAA-AAAA'] + .EIPS[1559], ).toBe(true); }, ); @@ -4988,43 +8684,77 @@ describe('NetworkController', () => { it('merges the network configurations from the given backup into state', async () => { await withController( { - state: { - networkConfigurations: { - networkConfigurationId1: { - id: 'networkConfigurationId1', - rpcUrl: 'https://rpc-url1.com', - chainId: toHex(1), - ticker: 'TEST1', + state: buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': { + chainId: '0x1337' as const, + defaultRpcEndpointUrl: 'https://test.network/1', + name: 'Test Network 1', + nativeTokenName: 'TOKEN1', + rpcEndpoints: [ + { + name: 'Test Endpoint', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network/1', + type: RpcEndpointType.Custom, + }, + ], }, }, - }, + }), }, ({ controller }) => { controller.loadBackup({ - networkConfigurations: { - networkConfigurationId2: { - id: 'networkConfigurationId2', - rpcUrl: 'https://rpc-url2.com', - chainId: toHex(2), - ticker: 'TEST2', + networkConfigurationsByChainId: { + '0x2448': { + chainId: '0x2448' as const, + defaultRpcEndpointUrl: 'https://test.network/2', + name: 'Test Network 2', + nativeTokenName: 'TOKEN2', + rpcEndpoints: [ + { + name: 'Test Endpoint', + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://test.network/2', + type: RpcEndpointType.Custom, + }, + ], }, }, }); - expect(controller.state.networkConfigurations).toStrictEqual({ - networkConfigurationId1: { - id: 'networkConfigurationId1', - rpcUrl: 'https://rpc-url1.com', - chainId: toHex(1), - ticker: 'TEST1', - }, - networkConfigurationId2: { - id: 'networkConfigurationId2', - rpcUrl: 'https://rpc-url2.com', - chainId: toHex(2), - ticker: 'TEST2', + expect(controller.state.networkConfigurationsByChainId).toStrictEqual( + { + '0x1337': { + chainId: '0x1337' as const, + defaultRpcEndpointUrl: 'https://test.network/1', + name: 'Test Network 1', + nativeTokenName: 'TOKEN1', + rpcEndpoints: [ + { + name: 'Test Endpoint', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network/1', + type: RpcEndpointType.Custom, + }, + ], + }, + '0x2448': { + chainId: '0x2448' as const, + defaultRpcEndpointUrl: 'https://test.network/2', + name: 'Test Network 2', + nativeTokenName: 'TOKEN2', + rpcEndpoints: [ + { + name: 'Test Endpoint', + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://test.network/2', + type: RpcEndpointType.Custom, + }, + ], + }, }, - }); + ); }, ); }); @@ -5055,49 +8785,6 @@ function mockCreateNetworkClient() { }); } -/** - * Creates a mocked version of `createNetworkClient` where multiple mock - * invocations can be specified. Requests for built-in networks are already - * mocked. - * - * @param options - The options. - * @param options.builtInNetworkClient - The network client to use for requests - * to built-in networks. - * @param options.infuraProjectId - The Infura project ID that each network - * client is expected to be created with. - * @returns The mocked version of `createNetworkClient`. - */ -function mockCreateNetworkClientWithDefaultsForBuiltInNetworkClients({ - builtInNetworkClient = buildFakeClient(), - infuraProjectId = 'infura-project-id', -} = {}) { - return mockCreateNetworkClient() - .calledWith({ - network: NetworkType.mainnet, - infuraProjectId, - type: NetworkClientType.Infura, - chainId: BUILT_IN_NETWORKS[InfuraNetworkType.mainnet].chainId, - ticker: BUILT_IN_NETWORKS[InfuraNetworkType.mainnet].ticker, - }) - .mockReturnValue(builtInNetworkClient) - .calledWith({ - network: NetworkType.goerli, - infuraProjectId, - chainId: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].chainId, - ticker: BUILT_IN_NETWORKS[InfuraNetworkType.goerli].ticker, - type: NetworkClientType.Infura, - }) - .mockReturnValue(builtInNetworkClient) - .calledWith({ - network: NetworkType.sepolia, - infuraProjectId, - chainId: BUILT_IN_NETWORKS[InfuraNetworkType.sepolia].chainId, - ticker: BUILT_IN_NETWORKS[InfuraNetworkType.sepolia].ticker, - type: NetworkClientType.Infura, - }) - .mockReturnValue(builtInNetworkClient); -} - /** * Test an operation that performs a `#refreshNetwork` call with the given * provider configuration. All effects of the `#refreshNetwork` call should be @@ -5277,30 +8964,45 @@ function refreshNetworkTests({ buildFakeClient(fakeProviders[1]), ]; const { selectedNetworkClientId } = controller.state; - let initializationNetworkClientOptions: Parameters< - typeof createNetworkClient - >[0]; + let initializationNetworkClientConfiguration: + | Parameters[0] + | undefined; + + for (const matchingNetworkConfiguration of Object.values( + controller.state.networkConfigurationsByChainId, + )) { + const matchingRpcEndpoint = + matchingNetworkConfiguration.rpcEndpoints.find( + (rpcEndpoint) => + rpcEndpoint.networkClientId === selectedNetworkClientId, + ); + if (matchingRpcEndpoint) { + if (isInfuraNetworkType(selectedNetworkClientId)) { + initializationNetworkClientConfiguration = { + chainId: ChainId[selectedNetworkClientId], + infuraProjectId: 'infura-project-id', + network: selectedNetworkClientId, + ticker: NetworksTicker[selectedNetworkClientId], + type: NetworkClientType.Infura, + }; + } else { + initializationNetworkClientConfiguration = { + chainId: matchingNetworkConfiguration.chainId, + rpcUrl: matchingRpcEndpoint.url, + ticker: matchingNetworkConfiguration.nativeTokenName, + type: NetworkClientType.Custom, + }; + } + } + } - 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, - }; + if (initializationNetworkClientConfiguration === undefined) { + throw new Error( + 'Could not set initializationNetworkClientConfiguration', + ); } - const operationNetworkClientOptions: Parameters< + const operationNetworkClientConfiguration: Parameters< typeof createNetworkClient >[0] = expectedNetworkClientConfiguration.type === NetworkClientType.Custom @@ -5310,9 +9012,9 @@ function refreshNetworkTests({ infuraProjectId: 'infura-project-id', }; mockCreateNetworkClient() - .calledWith(initializationNetworkClientOptions) + .calledWith(initializationNetworkClientConfiguration) .mockReturnValue(fakeNetworkClients[0]) - .calledWith(operationNetworkClientOptions) + .calledWith(operationNetworkClientConfiguration) .mockReturnValue(fakeNetworkClients[1]); await controller.initializeProvider(); const { provider: providerBefore } = @@ -5328,7 +9030,7 @@ function refreshNetworkTests({ }); lookupNetworkTests({ - expectedNetworkClientConfiguration, + expectedNetworkClientType: expectedNetworkClientConfiguration.type, initialState, operation, }); @@ -5340,17 +9042,17 @@ function refreshNetworkTests({ * covered by these tests. * * @param args - Arguments. - * @param args.expectedNetworkClientConfiguration - The network client - * configuration that the operation is expected to set. + * @param args.expectedNetworkClientType - The type of the network client + * 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({ - expectedNetworkClientConfiguration, + expectedNetworkClientType, initialState, operation, }: { - expectedNetworkClientConfiguration: NetworkClientConfiguration; + expectedNetworkClientType: NetworkClientType; initialState?: Partial; operation: (controller: NetworkController) => Promise; }) { @@ -5567,7 +9269,7 @@ function lookupNetworkTests({ ); }); - if (expectedNetworkClientConfiguration.type === NetworkClientType.Custom) { + if (expectedNetworkClientType === NetworkClientType.Custom) { it('emits infuraIsUnblocked', async () => { await withController( { @@ -5669,7 +9371,7 @@ function lookupNetworkTests({ }); describe('if a country blocked error is encountered while retrieving the network details of the current network', () => { - if (expectedNetworkClientConfiguration.type === NetworkClientType.Custom) { + if (expectedNetworkClientType === NetworkClientType.Custom) { it('updates the network in state to "unknown"', async () => { await withController( { @@ -5983,7 +9685,7 @@ function lookupNetworkTests({ ); }); - if (expectedNetworkClientConfiguration.type === NetworkClientType.Custom) { + if (expectedNetworkClientType === NetworkClientType.Custom) { it('emits infuraIsUnblocked', async () => { await withController( { @@ -6173,7 +9875,6 @@ async function withController( const restrictedMessenger = buildNetworkControllerMessenger(messenger); const controller = new NetworkController({ messenger: restrictedMessenger, - trackMetaMetricsEvent: jest.fn(), infuraProjectId: 'infura-project-id', ...rest, }); @@ -6499,3 +10200,74 @@ function didPropertyChange(patches: Patch[], propertyPath: string[]): boolean { ); }); } + +/** + * Extracts the network client configurations from a network client registry so + * that it is easier to test without having to ignore every property in + * NetworkClient but `configuration`. + * + * @param networkClientRegistry - The network client registry. + * @returns A map of network client ID to network client configuration. + */ +function getNetworkConfigurationsByNetworkClientId( + networkClientRegistry: AutoManagedBuiltInNetworkClientRegistry & + AutoManagedCustomNetworkClientRegistry, +): Record { + return Object.entries(networkClientRegistry).reduce( + ( + obj: Partial>, + [networkClientId, networkClient], + ) => { + return { + ...obj, + [networkClientId]: networkClient.configuration, + }; + }, + {}, + ) as Record; +} + +/** + * When initializing NetworkController with state, the `selectedNetworkClientId` + * property must match the `networkClientId` of an RPC endpoint in + * `networkConfigurationsByChainId`. Sometimes when writing tests we care about + * what the `selectedNetworkClientId` is, but sometimes we don't and we'd rather + * have this property automatically filled in for us. This function takes care + * of that step. + * + * @param networkControllerState - The desired NetworkController state + * overrides. + * @param networkControllerState.networkConfigurationsByChainId - The desired + * `networkConfigurationsByChainId`. + * @param networkControllerState.selectedNetworkClientId - The desired + * `selectedNetworkClientId`; if not provided, then will be set to the + * `networkClientId` of the first RPC endpoint in + * `networkConfigurationsByChainId`. + * @returns The complete NetworkController state with `selectedNetworkClientId` + * properly filled in. + */ +function buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId, + selectedNetworkClientId: givenSelectedNetworkClientId, + ...rest +}: Partial> & + Pick) { + if (givenSelectedNetworkClientId === undefined) { + const networkConfigurations = Object.values(networkConfigurationsByChainId); + const selectedNetworkClientId = + networkConfigurations.length > 0 + ? networkConfigurations[0].rpcEndpoints[0].networkClientId + : undefined; + return { + networkConfigurationsByChainId, + selectedNetworkClientId, + ...rest, + }; + } + + return { + networkConfigurationsByChainId, + selectedNetworkClientId: givenSelectedNetworkClientId, + ...rest, + }; +} diff --git a/packages/network-controller/tests/helpers.ts b/packages/network-controller/tests/helpers.ts index c7072665ea2..a3a7d9a2f47 100644 --- a/packages/network-controller/tests/helpers.ts +++ b/packages/network-controller/tests/helpers.ts @@ -1,21 +1,34 @@ import { - BUILT_IN_NETWORKS, + ChainId, InfuraNetworkType, + NetworkNickname, + NetworksTicker, toHex, } from '@metamask/controller-utils'; +import { v4 as uuidV4 } from 'uuid'; import { FakeBlockTracker } from '../../../tests/fake-block-tracker'; import { FakeProvider } from '../../../tests/fake-provider'; import type { FakeProviderStub } from '../../../tests/fake-provider'; +import { buildTestObject } from '../../../tests/helpers'; import type { BuiltInNetworkClientId, CustomNetworkClientId, NetworkClient, NetworkClientConfiguration, NetworkClientId, + NetworkConfiguration, NetworkController, } from '../src'; import type { AutoManagedNetworkClient } from '../src/create-auto-managed-network-client'; +import type { + AddNetworkCustomRpcEndpointFields, + AddNetworkFields, + CustomRpcEndpoint, + InfuraRpcEndpoint, + UpdateNetworkCustomRpcEndpointFields, +} from '../src/NetworkController'; +import { RpcEndpointType } from '../src/NetworkController'; import type { CustomNetworkClientConfiguration, InfuraNetworkClientConfiguration, @@ -138,8 +151,8 @@ export function buildInfuraNetworkClientConfiguration( type: NetworkClientType.Infura, network, infuraProjectId: 'test-infura-project-id', - chainId: BUILT_IN_NETWORKS[network].chainId, - ticker: BUILT_IN_NETWORKS[network].ticker, + chainId: ChainId[network], + ticker: NetworksTicker[network], ...overrides, }; } @@ -168,3 +181,250 @@ export function buildCustomNetworkClientConfiguration( }, ); } + +/** + * Constructs a NetworkConfiguration object for use in testing, providing + * defaults and allowing properties to be overridden at will. + * + * @param overrides - The properties to override the new + * NetworkConfiguration with. + * @param defaultRpcEndpointType - The type of the RPC endpoint you want to + * use by default. + * @returns The complete NetworkConfiguration object. + */ +export function buildNetworkConfiguration( + overrides: Partial = {}, + defaultRpcEndpointType: RpcEndpointType = RpcEndpointType.Custom, +): NetworkConfiguration { + return buildTestObject( + { + chainId: () => '0x1337', + // @ts-expect-error We will make sure that this property is set below. + defaultRpcEndpointUrl: () => undefined, + name: () => 'Some Network', + nativeTokenName: () => 'TOKEN', + rpcEndpoints: () => [ + defaultRpcEndpointType === RpcEndpointType.Infura + ? buildInfuraRpcEndpoint(InfuraNetworkType['linea-goerli']) + : buildCustomRpcEndpoint({ url: 'https://test.endpoint' }), + ], + }, + overrides, + (object) => { + if ( + object.defaultRpcEndpointUrl === undefined && + object.rpcEndpoints.length > 0 + ) { + return { + ...object, + defaultRpcEndpointUrl: object.rpcEndpoints[0].url, + }; + } + return object; + }, + ); +} + +/** + * Constructs a NetworkConfiguration object preloaded with a custom RPC endpoint + * for use in testing, providing defaults and allowing properties to be + * overridden at will. + * + * @param overrides - The properties to override the new NetworkConfiguration + * with. + * @returns The complete NetworkConfiguration object. + */ +export function buildCustomNetworkConfiguration( + overrides: Partial = {}, +): NetworkConfiguration { + return buildTestObject( + { + chainId: () => '0x1337' as const, + // @ts-expect-error We will make sure that this property is set below. + defaultRpcEndpointUrl: () => undefined, + name: () => 'Some Network', + nativeTokenName: () => 'TOKEN', + rpcEndpoints: () => [ + buildCustomRpcEndpoint({ + url: 'https://test.endpoint', + }), + ], + }, + overrides, + (object) => { + if ( + object.defaultRpcEndpointUrl === undefined && + object.rpcEndpoints.length > 0 + ) { + return { + ...object, + defaultRpcEndpointUrl: object.rpcEndpoints[0].url, + }; + } + return object; + }, + ); +} + +/** + * Constructs a NetworkConfiguration object preloaded with an Infura RPC + * endpoint for use in testing. + * + * @param infuraNetworkType - The Infura network type from which to create the + * NetworkConfiguration. + * @param overrides - The properties to override the new NetworkConfiguration + * with. + * @param overrides.rpcEndpoints - Extra RPC endpoints. + * @returns The complete NetworkConfiguration object. + */ +export function buildInfuraNetworkConfiguration( + infuraNetworkType: InfuraNetworkType, + overrides: Partial = {}, +): NetworkConfiguration { + const defaultRpcEndpoint = buildInfuraRpcEndpoint(infuraNetworkType); + return buildTestObject( + { + chainId: () => ChainId[infuraNetworkType], + // @ts-expect-error We will make sure that this property is set below. + defaultRpcEndpointUrl: () => undefined, + name: () => NetworkNickname[infuraNetworkType], + nativeTokenName: () => NetworksTicker[infuraNetworkType], + rpcEndpoints: () => [defaultRpcEndpoint], + }, + overrides, + (object) => { + if ( + object.defaultRpcEndpointUrl === undefined && + object.rpcEndpoints.length > 0 + ) { + return { + ...object, + defaultRpcEndpointUrl: object.rpcEndpoints[0].url, + }; + } + return object; + }, + ); +} + +/** + * Constructs a InfuraRpcEndpoint object for use in testing. + * + * @param infuraNetworkType - The Infura network type from which to create the + * InfuraRpcEndpoint. + * @returns The created InfuraRpcEndpoint object. + */ +export function buildInfuraRpcEndpoint( + infuraNetworkType: InfuraNetworkType, +): InfuraRpcEndpoint { + return { + name: NetworkNickname[infuraNetworkType], + networkClientId: infuraNetworkType, + type: RpcEndpointType.Infura as const, + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + url: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`, + }; +} + +/** + * Constructs an CustomRpcEndpoint object for use in testing, providing defaults + * and allowing properties to be overridden at will. + * + * @param overrides - The properties to override the new CustomRpcEndpoint with. + * @returns The complete CustomRpcEndpoint object. + */ +export function buildCustomRpcEndpoint( + overrides: Partial = {}, +): CustomRpcEndpoint { + return buildTestObject( + { + name: () => 'Test Endpoint', + networkClientId: () => uuidV4(), + type: () => RpcEndpointType.Custom as const, + url: () => 'https://test.endpoint', + }, + overrides, + ); +} + +/** + * Constructs an AddNetworkFields object for use in testing, providing defaults + * and allowing properties to be overridden at will. + * + * @param overrides - The properties to override the new AddNetworkFields with. + * @returns The complete AddNetworkFields object. + */ +export function buildAddNetworkFields( + overrides: Partial = {}, +): AddNetworkFields { + return buildTestObject( + { + chainId: () => '0x1337' as const, + // @ts-expect-error We will make sure that this property is set below. + defaultRpcEndpointUrl: () => undefined, + name: () => 'Some Network', + nativeTokenName: () => 'TOKEN', + rpcEndpoints: () => [ + buildAddNetworkCustomRpcEndpointFields({ + url: 'https://test.endpoint', + }), + ], + }, + overrides, + (object) => { + if ( + object.defaultRpcEndpointUrl === undefined && + object.rpcEndpoints.length > 0 + ) { + return { + ...object, + defaultRpcEndpointUrl: object.rpcEndpoints[0].url, + }; + } + return object; + }, + ); +} + +/** + * Constructs an AddNetworkCustomRpcEndpointFields object for use in testing, + * providing defaults and allowing properties to be overridden at will. + * + * @param overrides - The properties to override the new + * AddNetworkCustomRpcEndpointFields with. + * @returns The complete AddNetworkCustomRpcEndpointFields object. + */ +export function buildAddNetworkCustomRpcEndpointFields( + overrides: Partial = {}, +): AddNetworkCustomRpcEndpointFields { + return buildTestObject( + { + name: () => 'Test Endpoint', + type: () => RpcEndpointType.Custom as const, + url: () => 'https://test.endpoint', + }, + overrides, + ); +} + +/** + * Constructs an UpdateNetworkCustomRpcEndpointFields object for use in testing, + * providing defaults and allowing properties to be overridden at will. + * + * @param overrides - The properties to override the new + * UpdateNetworkCustomRpcEndpointFields with. + * @returns The complete UpdateNetworkCustomRpcEndpointFields object. + */ +export function buildUpdateNetworkCustomRpcEndpointFields( + overrides: Partial = {}, +): UpdateNetworkCustomRpcEndpointFields { + return buildTestObject( + { + name: () => 'Test Endpoint', + type: () => RpcEndpointType.Custom as const, + url: () => 'https://test.endpoint', + }, + overrides, + ); +} diff --git a/packages/queued-request-controller/src/QueuedRequestController.test.ts b/packages/queued-request-controller/src/QueuedRequestController.test.ts index e51877a3acd..17ecd9f4a10 100644 --- a/packages/queued-request-controller/src/QueuedRequestController.test.ts +++ b/packages/queued-request-controller/src/QueuedRequestController.test.ts @@ -1,12 +1,11 @@ import { ControllerMessenger } from '@metamask/base-controller'; import { - defaultState as defaultNetworkState, + getDefaultNetworkControllerState, type NetworkControllerGetStateAction, type NetworkControllerSetActiveNetworkAction, } from '@metamask/network-controller'; import type { SelectedNetworkControllerGetNetworkClientIdForDomainAction } from '@metamask/selected-network-controller'; import { createDeferredPromise } from '@metamask/utils'; -import { cloneDeep } from 'lodash'; import type { AllowedActions, @@ -66,7 +65,7 @@ describe('QueuedRequestController', () => { const mockSetActiveNetwork = jest.fn(); const { messenger } = buildControllerMessenger({ networkControllerGetState: jest.fn().mockReturnValue({ - ...cloneDeep(defaultNetworkState), + ...getDefaultNetworkControllerState(), selectedNetworkClientId: 'selectedNetworkClientId', }), networkControllerSetActiveNetwork: mockSetActiveNetwork, @@ -102,7 +101,7 @@ describe('QueuedRequestController', () => { const mockSetActiveNetwork = jest.fn(); const { messenger } = buildControllerMessenger({ networkControllerGetState: jest.fn().mockReturnValue({ - ...cloneDeep(defaultNetworkState), + ...getDefaultNetworkControllerState(), selectedNetworkClientId: 'selectedNetworkClientId', }), networkControllerSetActiveNetwork: mockSetActiveNetwork, @@ -133,7 +132,7 @@ describe('QueuedRequestController', () => { const mockSetActiveNetwork = jest.fn(); const { messenger } = buildControllerMessenger({ networkControllerGetState: jest.fn().mockReturnValue({ - ...cloneDeep(defaultNetworkState), + ...getDefaultNetworkControllerState(), selectedNetworkClientId: 'selectedNetworkClientId', }), networkControllerSetActiveNetwork: mockSetActiveNetwork, @@ -423,7 +422,7 @@ describe('QueuedRequestController', () => { const mockSetActiveNetwork = jest.fn(); const { messenger } = buildControllerMessenger({ networkControllerGetState: jest.fn().mockReturnValue({ - ...cloneDeep(defaultNetworkState), + ...getDefaultNetworkControllerState(), selectedNetworkClientId: 'selectedNetworkClientId', }), networkControllerSetActiveNetwork: mockSetActiveNetwork, @@ -478,7 +477,7 @@ describe('QueuedRequestController', () => { const mockSetActiveNetwork = jest.fn(); const { messenger } = buildControllerMessenger({ networkControllerGetState: jest.fn().mockReturnValue({ - ...cloneDeep(defaultNetworkState), + ...getDefaultNetworkControllerState(), selectedNetworkClientId: 'selectedNetworkClientId', }), networkControllerSetActiveNetwork: mockSetActiveNetwork, @@ -525,7 +524,7 @@ describe('QueuedRequestController', () => { const switchError = new Error('switch error'); const { messenger } = buildControllerMessenger({ networkControllerGetState: jest.fn().mockReturnValue({ - ...cloneDeep(defaultNetworkState), + ...getDefaultNetworkControllerState(), selectedNetworkClientId: 'selectedNetworkClientId', }), networkControllerSetActiveNetwork: jest @@ -556,7 +555,7 @@ describe('QueuedRequestController', () => { const switchError = new Error('switch error'); const { messenger } = buildControllerMessenger({ networkControllerGetState: jest.fn().mockReturnValue({ - ...cloneDeep(defaultNetworkState), + ...getDefaultNetworkControllerState(), selectedNetworkClientId: 'selectedNetworkClientId', }), networkControllerSetActiveNetwork: jest @@ -610,7 +609,7 @@ describe('QueuedRequestController', () => { const switchError = new Error('switch error'); const { messenger } = buildControllerMessenger({ networkControllerGetState: jest.fn().mockReturnValue({ - ...cloneDeep(defaultNetworkState), + ...getDefaultNetworkControllerState(), selectedNetworkClientId: 'selectedNetworkClientId', }), networkControllerSetActiveNetwork: jest @@ -663,7 +662,7 @@ describe('QueuedRequestController', () => { const switchError = new Error('switch error'); const { messenger } = buildControllerMessenger({ networkControllerGetState: jest.fn().mockReturnValue({ - ...cloneDeep(defaultNetworkState), + ...getDefaultNetworkControllerState(), selectedNetworkClientId: 'selectedNetworkClientId', }), networkControllerSetActiveNetwork: jest @@ -969,7 +968,7 @@ function buildControllerMessenger({ const mockNetworkControllerGetState = networkControllerGetState ?? jest.fn().mockReturnValue({ - ...cloneDeep(defaultNetworkState), + ...getDefaultNetworkControllerState(), selectedNetworkClientId: 'defaultNetworkClientId', }); messenger.registerActionHandler( diff --git a/packages/selected-network-controller/tests/SelectedNetworkController.test.ts b/packages/selected-network-controller/tests/SelectedNetworkController.test.ts index a36ebb50fc7..b8b04ee6c1f 100644 --- a/packages/selected-network-controller/tests/SelectedNetworkController.test.ts +++ b/packages/selected-network-controller/tests/SelectedNetworkController.test.ts @@ -244,7 +244,7 @@ describe('SelectedNetworkController', () => { 'NetworkController:stateChange', { selectedNetworkClientId: 'goerli', - networkConfigurations: {}, + networkConfigurationsByChainId: {}, networksMetadata: {}, }, [ diff --git a/packages/transaction-controller/src/TransactionController.test.ts b/packages/transaction-controller/src/TransactionController.test.ts index ed7f8b6fb7b..688a22554e4 100644 --- a/packages/transaction-controller/src/TransactionController.test.ts +++ b/packages/transaction-controller/src/TransactionController.test.ts @@ -29,7 +29,7 @@ import type { import { NetworkClientType, NetworkStatus, - defaultState as defaultNetworkState, + getDefaultNetworkControllerState, } from '@metamask/network-controller'; import * as NonceTrackerPackage from '@metamask/nonce-tracker'; import { errorCodes, providerErrors, rpcErrors } from '@metamask/rpc-errors'; @@ -337,7 +337,7 @@ const MOCK_NETWORK: MockNetwork = { status: NetworkStatus.Available, }, }, - networkConfigurations: {}, + networkConfigurationsByChainId: {}, }, subscribe: () => undefined, }; @@ -354,7 +354,7 @@ const MOCK_MAINNET_NETWORK: MockNetwork = { status: NetworkStatus.Available, }, }, - networkConfigurations: {}, + networkConfigurationsByChainId: {}, }, subscribe: () => undefined, }; @@ -371,7 +371,7 @@ const MOCK_LINEA_MAINNET_NETWORK: MockNetwork = { status: NetworkStatus.Available, }, }, - networkConfigurations: {}, + networkConfigurationsByChainId: {}, }, subscribe: () => undefined, }; @@ -388,7 +388,7 @@ const MOCK_LINEA_GOERLI_NETWORK: MockNetwork = { status: NetworkStatus.Available, }, }, - networkConfigurations: {}, + networkConfigurationsByChainId: {}, }, subscribe: () => undefined, }; @@ -560,7 +560,7 @@ describe('TransactionController', () => { >; } = {}) { let networkState = { - ...defaultNetworkState, + ...getDefaultNetworkControllerState(), selectedNetworkClientId: MOCK_NETWORK.state.selectedNetworkClientId, ...network.state, }; diff --git a/packages/transaction-controller/src/TransactionControllerIntegration.test.ts b/packages/transaction-controller/src/TransactionControllerIntegration.test.ts index e683f48243e..ee211238c07 100644 --- a/packages/transaction-controller/src/TransactionControllerIntegration.test.ts +++ b/packages/transaction-controller/src/TransactionControllerIntegration.test.ts @@ -9,11 +9,11 @@ import { ControllerMessenger } from '@metamask/base-controller'; import { ApprovalType, BUILT_IN_NETWORKS, + ChainId, InfuraNetworkType, NetworkType, } from '@metamask/controller-utils'; import type { InternalAccount } from '@metamask/keyring-api'; -import { EthAccountType, EthMethod } from '@metamask/keyring-api'; import { NetworkController, NetworkClientType, @@ -28,10 +28,15 @@ import assert from 'assert'; import nock from 'nock'; import type { SinonFakeTimers } from 'sinon'; import { useFakeTimers } from 'sinon'; -import { v4 } from 'uuid'; +import { v4 as uuidV4 } from 'uuid'; import { advanceTime } from '../../../tests/helpers'; import { mockNetwork } from '../../../tests/mock-network'; +import { + buildAddNetworkFields, + buildCustomNetworkClientConfiguration, + buildUpdateNetworkCustomRpcEndpointFields, +} from '../../network-controller/tests/helpers'; import { ETHERSCAN_TRANSACTION_BASE_MOCK, ETHERSCAN_TRANSACTION_RESPONSE_MOCK, @@ -59,6 +64,15 @@ import { TransactionStatus, TransactionType } from './types'; import { getEtherscanApiHost } from './utils/etherscan'; import * as etherscanUtils from './utils/etherscan'; +jest.mock('uuid', () => { + const actual = jest.requireActual('uuid'); + + return { + ...actual, + v4: jest.fn().mockReturnValue('UUID'), + }; +}); + type UnrestrictedControllerMessenger = ControllerMessenger< | NetworkControllerActions | ApprovalControllerActions @@ -69,40 +83,7 @@ type UnrestrictedControllerMessenger = ControllerMessenger< | TransactionControllerEvents >; -const createMockInternalAccount = ({ - id = v4(), - address = '0x2990079bcdee240329a520d2444386fc119da21a', - name = 'Account 1', - importTime = Date.now(), - lastSelected = Date.now(), -}: { - id?: string; - address?: string; - name?: string; - importTime?: number; - lastSelected?: number; -} = {}): InternalAccount => { - return { - id, - address, - options: {}, - methods: [ - EthMethod.PersonalSign, - EthMethod.Sign, - EthMethod.SignTransaction, - EthMethod.SignTypedDataV1, - EthMethod.SignTypedDataV3, - EthMethod.SignTypedDataV4, - ], - type: EthAccountType.Eoa, - metadata: { - name, - keyring: { type: 'HD Key Tree' }, - importTime, - lastSelected, - }, - } as InternalAccount; -}; +const uuidV4Mock = jest.mocked(uuidV4); const ACCOUNT_MOCK = '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207'; const INTERNAL_ACCOUNT_MOCK = createMockInternalAccount({ @@ -132,13 +113,6 @@ function buildInfuraNetworkClientConfiguration( }; } -const customGoerliNetworkClientConfiguration = { - type: NetworkClientType.Custom, - chainId: BUILT_IN_NETWORKS[NetworkType.goerli].chainId, - ticker: BUILT_IN_NETWORKS[NetworkType.goerli].ticker, - rpcUrl: 'https://mock.rpc.url', -} as const; - const setupController = async ( givenOptions: Partial< ConstructorParameters[0] @@ -168,9 +142,6 @@ const setupController = async ( allowedActions: [], allowedEvents: [], }), - trackMetaMetricsEvent: () => { - // noop - }, infuraProjectId, }); await networkController.initializeProvider(); @@ -223,8 +194,8 @@ const setupController = async ( ); }, getNetworkState: () => networkController.state, - getNetworkClientRegistry: - networkController.getNetworkClientRegistry.bind(networkController), + getNetworkClientRegistry: () => + networkController.getNetworkClientRegistry(), getPermittedAccounts: async () => [ACCOUNT_MOCK], hooks: {}, isMultichainEnabled: false, @@ -805,10 +776,10 @@ describe('TransactionController Integration', () => { describe('when transactions are added concurrently with different networkClientIds but on the same chainId', () => { it('should add each transaction with consecutive nonces', async () => { + const goerliNetworkClientConfiguration = + buildInfuraNetworkClientConfiguration(InfuraNetworkType.goerli); mockNetwork({ - networkClientConfiguration: buildInfuraNetworkClientConfiguration( - InfuraNetworkType.goerli, - ), + networkClientConfiguration: goerliNetworkClientConfiguration, mocks: [ buildEthBlockNumberRequestMock('0x1'), buildEthGetBlockByNumberRequestMock('0x1'), @@ -833,7 +804,10 @@ describe('TransactionController Integration', () => { }); mockNetwork({ - networkClientConfiguration: customGoerliNetworkClientConfiguration, + networkClientConfiguration: buildCustomNetworkClientConfiguration({ + rpcUrl: 'https://mock.rpc.url', + ticker: goerliNetworkClientConfiguration.ticker, + }), mocks: [ buildEthBlockNumberRequestMock('0x1'), buildEthBlockNumberRequestMock('0x1'), @@ -858,25 +832,34 @@ describe('TransactionController Integration', () => { }); const { approvalController, networkController, transactionController } = - await setupController( - { - isMultichainEnabled: true, - getPermittedAccounts: async () => [ACCOUNT_MOCK], - }, - { selectedAccount: INTERNAL_ACCOUNT_MOCK }, - ); - const otherNetworkClientIdOnGoerli = - await networkController.upsertNetworkConfiguration( - { - rpcUrl: 'https://mock.rpc.url', - chainId: BUILT_IN_NETWORKS[NetworkType.goerli].chainId, - ticker: BUILT_IN_NETWORKS[NetworkType.goerli].ticker, - }, - { - referrer: 'https://mock.referrer', - source: 'dapp', - }, - ); + await setupController({ + isMultichainEnabled: true, + getPermittedAccounts: async () => [ACCOUNT_MOCK], + }); + const existingGoerliNetworkConfiguration = + networkController.getNetworkConfigurationByChainId(ChainId.goerli); + assert( + existingGoerliNetworkConfiguration, + 'Could not find network configuration for Goerli', + ); + const updatedGoerliNetworkConfiguration = + networkController.updateNetwork(ChainId.goerli, { + ...existingGoerliNetworkConfiguration, + rpcEndpoints: [ + ...existingGoerliNetworkConfiguration.rpcEndpoints, + buildUpdateNetworkCustomRpcEndpointFields({ + url: 'https://mock.rpc.url', + }), + ], + }); + const otherGoerliRpcEndpoint = + updatedGoerliNetworkConfiguration.rpcEndpoints.find((rpcEndpoint) => { + return rpcEndpoint.url === 'https://mock.rpc.url'; + }); + assert( + otherGoerliRpcEndpoint, + 'Could not find other Goerli RPC endpoint', + ); const addTx1 = await transactionController.addTransaction( { @@ -892,7 +875,7 @@ describe('TransactionController Integration', () => { to: ACCOUNT_3_MOCK, }, { - networkClientId: otherNetworkClientIdOnGoerli, + networkClientId: otherGoerliRpcEndpoint.networkClientId, }, ); @@ -989,79 +972,103 @@ describe('TransactionController Integration', () => { }); }); - describe('when changing rpcUrl of networkClient', () => { - it('should start tracking when a new network is added', async () => { - mockNetwork({ - networkClientConfiguration: customGoerliNetworkClientConfiguration, - mocks: [ - buildEthBlockNumberRequestMock('0x1'), - buildEthBlockNumberRequestMock('0x1'), - buildEthGetBlockByNumberRequestMock('0x1'), - buildEthGetCodeRequestMock(ACCOUNT_2_MOCK), - buildEthGasPriceRequestMock(), + it('should start tracking when a new network is added', async () => { + mockNetwork({ + networkClientConfiguration: buildInfuraNetworkClientConfiguration( + InfuraNetworkType.goerli, + ), + mocks: [ + buildEthBlockNumberRequestMock('0x1'), + buildEthBlockNumberRequestMock('0x1'), + buildEthGetBlockByNumberRequestMock('0x1'), + buildEthGetCodeRequestMock(ACCOUNT_2_MOCK), + buildEthGasPriceRequestMock(), + ], + }); + mockNetwork({ + networkClientConfiguration: buildCustomNetworkClientConfiguration({ + rpcUrl: 'https://mock.rpc.url', + }), + mocks: [ + buildEthBlockNumberRequestMock('0x1'), + buildEthBlockNumberRequestMock('0x1'), + buildEthGetBlockByNumberRequestMock('0x1'), + buildEthGetCodeRequestMock(ACCOUNT_2_MOCK), + buildEthGasPriceRequestMock(), + ], + }); + const { networkController, transactionController } = await setupController({ + isMultichainEnabled: true, + }); + + const existingGoerliNetworkConfiguration = + networkController.getNetworkConfigurationByChainId(ChainId.goerli); + assert( + existingGoerliNetworkConfiguration, + 'Could not find network configuration for Goerli', + ); + const updatedGoerliNetworkConfiguration = networkController.updateNetwork( + ChainId.goerli, + { + ...existingGoerliNetworkConfiguration, + rpcEndpoints: [ + ...existingGoerliNetworkConfiguration.rpcEndpoints, + buildUpdateNetworkCustomRpcEndpointFields({ + url: 'https://mock.rpc.url', + }), ], + }, + ); + const otherGoerliRpcEndpoint = + updatedGoerliNetworkConfiguration.rpcEndpoints.find((rpcEndpoint) => { + return rpcEndpoint.url === 'https://mock.rpc.url'; }); - const { networkController, transactionController } = - await setupController({ isMultichainEnabled: true }); + assert(otherGoerliRpcEndpoint, 'Could not find other Goerli RPC endpoint'); + + await transactionController.addTransaction( + { + from: ACCOUNT_MOCK, + to: ACCOUNT_3_MOCK, + }, + { + networkClientId: otherGoerliRpcEndpoint.networkClientId, + }, + ); + + expect(transactionController.state.transactions[0]).toStrictEqual( + expect.objectContaining({ + networkClientId: otherGoerliRpcEndpoint.networkClientId, + }), + ); + transactionController.destroy(); + }); - const otherNetworkClientIdOnGoerli = - await networkController.upsertNetworkConfiguration( - customGoerliNetworkClientConfiguration, - { - setActive: false, - referrer: 'https://mock.referrer', - source: 'dapp', - }, - ); + it('should stop tracking when a network is removed', async () => { + const { networkController, transactionController } = + await setupController(); + + const networkConfiguration = await networkController.addNetwork( + buildAddNetworkFields(), + ); + + networkController.removeNetwork(networkConfiguration.chainId); - await transactionController.addTransaction( + await expect( + transactionController.addTransaction( { from: ACCOUNT_MOCK, - to: ACCOUNT_3_MOCK, + to: ACCOUNT_2_MOCK, }, { - networkClientId: otherNetworkClientIdOnGoerli, + networkClientId: networkConfiguration.rpcEndpoints[0].networkClientId, }, - ); - - expect(transactionController.state.transactions[0]).toStrictEqual( - expect.objectContaining({ - networkClientId: otherNetworkClientIdOnGoerli, - }), - ); - transactionController.destroy(); - }); - it('should stop tracking when a network is removed', async () => { - const { networkController, transactionController } = - await setupController(); - - const configurationId = - await networkController.upsertNetworkConfiguration( - customGoerliNetworkClientConfiguration, - { - setActive: false, - referrer: 'https://mock.referrer', - source: 'dapp', - }, - ); - - networkController.removeNetworkConfiguration(configurationId); - - await expect( - transactionController.addTransaction( - { - from: ACCOUNT_MOCK, - to: ACCOUNT_2_MOCK, - }, - { networkClientId: configurationId }, - ), - ).rejects.toThrow( - 'The networkClientId for this transaction could not be found', - ); + ), + ).rejects.toThrow( + 'The networkClientId for this transaction could not be found', + ); - expect(transactionController).toBeDefined(); - transactionController.destroy(); - }); + expect(transactionController).toBeDefined(); + transactionController.destroy(); }); describe('feature flag', () => { @@ -1084,15 +1091,9 @@ describe('TransactionController Integration', () => { isMultichainEnabled: false, }); - const configurationId = - await networkController.upsertNetworkConfiguration( - customGoerliNetworkClientConfiguration, - { - setActive: false, - referrer: 'https://mock.referrer', - source: 'dapp', - }, - ); + const networkConfiguration = await networkController.addNetwork( + buildAddNetworkFields(), + ); // add a transaction with the networkClientId of the newly added network // and expect it to throw since the networkClientId won't be found in the trackingMap @@ -1102,7 +1103,10 @@ describe('TransactionController Integration', () => { from: ACCOUNT_MOCK, to: ACCOUNT_2_MOCK, }, - { networkClientId: configurationId }, + { + networkClientId: + networkConfiguration.rpcEndpoints[0].networkClientId, + }, ), ).rejects.toThrow( 'The networkClientId for this transaction could not be found', @@ -1117,14 +1121,9 @@ describe('TransactionController Integration', () => { ).toBeDefined(); transactionController.destroy(); }); + it('should not call getNetworkClientRegistry on networkController:stateChange when feature flag is disabled', async () => { - const getNetworkClientRegistrySpy = jest.fn().mockImplementation(() => { - return { - [NetworkType.goerli]: { - configuration: customGoerliNetworkClientConfiguration, - }, - }; - }); + const getNetworkClientRegistrySpy = jest.fn(); const { networkController, transactionController } = await setupController({ @@ -1132,45 +1131,28 @@ describe('TransactionController Integration', () => { getNetworkClientRegistry: getNetworkClientRegistrySpy, }); - await networkController.upsertNetworkConfiguration( - customGoerliNetworkClientConfiguration, - { - setActive: false, - referrer: 'https://mock.referrer', - source: 'dapp', - }, - ); + await networkController.addNetwork(buildAddNetworkFields()); expect(getNetworkClientRegistrySpy).not.toHaveBeenCalled(); transactionController.destroy(); }); + it('should call getNetworkClientRegistry on networkController:stateChange when feature flag is enabled', async () => { - const getNetworkClientRegistrySpy = jest.fn().mockImplementation(() => { - return { - [NetworkType.goerli]: { - configuration: BUILT_IN_NETWORKS[NetworkType.goerli], - }, - }; - }); + uuidV4Mock.mockReturnValue('AAAA-AAAA-AAAA-AAAA'); const { networkController, transactionController } = - await setupController({ - isMultichainEnabled: true, - getNetworkClientRegistry: getNetworkClientRegistrySpy, - }); - - await networkController.upsertNetworkConfiguration( - customGoerliNetworkClientConfiguration, - { - setActive: false, - referrer: 'https://mock.referrer', - source: 'dapp', - }, + await setupController({ isMultichainEnabled: true }); + const getNetworkClientRegistrySpy = jest.spyOn( + networkController, + 'getNetworkClientRegistry', ); + await networkController.addNetwork(buildAddNetworkFields()); + expect(getNetworkClientRegistrySpy).toHaveBeenCalled(); transactionController.destroy(); }); + it('should call getNetworkClientRegistry on construction when feature flag is enabled', async () => { const getNetworkClientRegistrySpy = jest.fn().mockImplementation(() => { return { @@ -1374,9 +1356,8 @@ describe('TransactionController Integration', () => { // mock the other goerli network client node requests mockNetwork({ networkClientConfiguration: { + ...buildInfuraNetworkClientConfiguration(InfuraNetworkType.goerli), type: NetworkClientType.Custom, - chainId: BUILT_IN_NETWORKS[NetworkType.goerli].chainId, - ticker: BUILT_IN_NETWORKS[NetworkType.goerli].ticker, rpcUrl: 'https://mock.rpc.url', }, mocks: [ @@ -1400,19 +1381,35 @@ describe('TransactionController Integration', () => { { selectedAccount: selectedAccountMock }, ); - const otherGoerliClientNetworkClientId = - await networkController.upsertNetworkConfiguration( - customGoerliNetworkClientConfiguration, - { - referrer: 'https://mock.referrer', - source: 'dapp', - }, - ); + const existingGoerliNetworkConfiguration = + networkController.getNetworkConfigurationByChainId(ChainId.goerli); + assert( + existingGoerliNetworkConfiguration, + 'Could not find network configuration for Goerli', + ); + const updatedGoerliNetworkConfiguration = + networkController.updateNetwork(ChainId.goerli, { + ...existingGoerliNetworkConfiguration, + rpcEndpoints: [ + ...existingGoerliNetworkConfiguration.rpcEndpoints, + buildUpdateNetworkCustomRpcEndpointFields({ + url: 'https://mock.rpc.url', + }), + ], + }); + const otherGoerliRpcEndpoint = + updatedGoerliNetworkConfiguration.rpcEndpoints.find((rpcEndpoint) => { + return rpcEndpoint.url === 'https://mock.rpc.url'; + }); + assert( + otherGoerliRpcEndpoint, + 'Could not find other Goerli RPC endpoint', + ); // Etherscan API Mocks // Non-token transactions - nock(getEtherscanApiHost(BUILT_IN_NETWORKS[NetworkType.goerli].chainId)) + nock(getEtherscanApiHost(ChainId.goerli)) .get( `/api?module=account&address=${ETHERSCAN_TRANSACTION_BASE_MOCK.to}&offset=40&sort=desc&action=txlist&tag=latest&page=1`, ) @@ -1431,7 +1428,7 @@ describe('TransactionController Integration', () => { .persist(); // token transactions - nock(getEtherscanApiHost(BUILT_IN_NETWORKS[NetworkType.goerli].chainId)) + nock(getEtherscanApiHost(ChainId.goerli)) .get( `/api?module=account&address=${ETHERSCAN_TRANSACTION_BASE_MOCK.to}&offset=40&sort=desc&action=tokentx&tag=latest&page=1`, ) @@ -1451,7 +1448,7 @@ describe('TransactionController Integration', () => { // start polling with two clients which share the same chainId transactionController.startIncomingTransactionPolling([ NetworkType.goerli, - otherGoerliClientNetworkClientId, + otherGoerliRpcEndpoint.networkClientId, ]); await advanceTime({ clock, duration: 1 }); expect(fetchEtherscanNativeTxFetchSpy).toHaveBeenCalledTimes(1); @@ -1867,21 +1864,43 @@ describe('TransactionController Integration', () => { }); mockNetwork({ - networkClientConfiguration: customGoerliNetworkClientConfiguration, + networkClientConfiguration: { + ...buildInfuraNetworkClientConfiguration(InfuraNetworkType.goerli), + rpcUrl: 'https://mock.rpc.url', + type: NetworkClientType.Custom, + }, mocks: [ buildEthBlockNumberRequestMock('0x1'), buildEthGetTransactionCountRequestMock(ACCOUNT_MOCK, '0x1', '0xa'), ], }); - const otherNetworkClientIdOnGoerli = - await networkController.upsertNetworkConfiguration( - customGoerliNetworkClientConfiguration, - { - referrer: 'https://mock.referrer', - source: 'dapp', - }, - ); + const existingGoerliNetworkConfiguration = + networkController.getNetworkConfigurationByChainId(ChainId.goerli); + assert( + existingGoerliNetworkConfiguration, + 'Could not find network configuration for Goerli', + ); + const updatedGoerliNetworkConfiguration = networkController.updateNetwork( + ChainId.goerli, + { + ...existingGoerliNetworkConfiguration, + rpcEndpoints: [ + ...existingGoerliNetworkConfiguration.rpcEndpoints, + buildUpdateNetworkCustomRpcEndpointFields({ + url: 'https://mock.rpc.url', + }), + ], + }, + ); + const otherGoerliRpcEndpoint = + updatedGoerliNetworkConfiguration.rpcEndpoints.find((rpcEndpoint) => { + return rpcEndpoint.url === 'https://mock.rpc.url'; + }); + assert( + otherGoerliRpcEndpoint, + 'Could not find other Goerli RPC endpoint', + ); const firstNonceLockPromise = transactionController.getNonceLock( ACCOUNT_MOCK, @@ -1895,7 +1914,7 @@ describe('TransactionController Integration', () => { const secondNonceLockPromise = transactionController.getNonceLock( ACCOUNT_MOCK, - otherNetworkClientIdOnGoerli, + otherGoerliRpcEndpoint.networkClientId, ); const delay = () => // TODO: Either fix this lint violation or explain why it's necessary to ignore. diff --git a/packages/transaction-controller/tsconfig.json b/packages/transaction-controller/tsconfig.json index bf67c437107..338e2016b85 100644 --- a/packages/transaction-controller/tsconfig.json +++ b/packages/transaction-controller/tsconfig.json @@ -12,5 +12,5 @@ { "path": "../gas-fee-controller" }, { "path": "../network-controller" } ], - "include": ["../../types", "./src"] + "include": ["../../types", "./src", "./tests"] } diff --git a/tests/helpers.ts b/tests/helpers.ts index ed0b2660f82..e3d8731fbd2 100644 --- a/tests/helpers.ts +++ b/tests/helpers.ts @@ -1,3 +1,5 @@ +import { getKnownPropertyNames } from '@metamask/utils'; + /** * Advances the provided fake timer by a specified duration in incremental steps. * Between each step, any enqueued promises are processed. However, any setTimeouts created @@ -39,3 +41,48 @@ export async function advanceTime({ export async function flushPromises(): Promise { await new Promise(jest.requireActual('timers').setImmediate); } + +/** + * It's common when writing tests to need an object which fits the shape of a + * type. However, some properties are unimportant to a test, and so it's useful + * if such properties can get filled in with defaults if not explicitly + * provided, so that a complete version of that object can still be produced. + * + * A naive approach to doing this is to define those defaults and then mix them + * in with overrides using the spread operator; however, this causes issues if + * creating a default value causes a change in global test state — such as + * causing a mocked function to get called inadvertently. + * + * This function solves this problem by allowing defaults to be defined lazily. + * + * @param defaults - An object where each value is wrapped in a function so that + * it doesn't get evaluated unless `overrides` does not contain the key. + * @param overrides - The values to override the defaults with. + * @param finalizeObject - An optional function to call which will create the + * final version of the object. This is useful if you need to customize how a + * value receives its default version (say, if it needs be calculated based on + * some other property). + * @returns The complete version of the object. + */ +export function buildTestObject>( + defaults: { [K in keyof Type]: () => Type[K] }, + overrides: Partial, + finalizeObject?: (object: Type) => Type, +): Type { + const keys = [ + ...new Set([ + ...getKnownPropertyNames(defaults), + ...getKnownPropertyNames(overrides), + ]), + ]; + const object = keys.reduce((workingObject: Partial, key) => { + if (key in overrides) { + return { ...workingObject, [key]: overrides[key] }; + } else if (key in defaults) { + return { ...workingObject, [key]: defaults[key]() }; + } + return workingObject; + }, {}) as unknown as Type; + + return finalizeObject ? finalizeObject(object) : object; +} diff --git a/yarn.lock b/yarn.lock index d82e498cd5b..4ccd381c83e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2613,6 +2613,7 @@ __metadata: typedoc: ^0.24.8 typedoc-plugin-missing-exports: ^2.0.0 typescript: ~4.9.5 + uri-js: ^4.4.1 uuid: ^8.3.2 languageName: unknown linkType: soft From a7facb3ceaa752515e5c9144b86f176d1bf8ce16 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Wed, 12 Jun 2024 11:48:49 -0600 Subject: [PATCH 02/49] WIP --- constraints.pro | 6 +++--- jest.config.scripts.js | 2 +- package.json | 11 ++++++----- packages/accounts-controller/package.json | 4 ++-- packages/address-book-controller/package.json | 6 +++--- packages/announcement-controller/package.json | 6 +++--- packages/approval-controller/package.json | 6 +++--- packages/assets-controllers/package.json | 6 +++--- packages/base-controller/package.json | 6 +++--- packages/build-utils/package.json | 6 +++--- packages/chain-controller/package.json | 6 +++--- packages/composable-controller/package.json | 6 +++--- packages/controller-utils/package.json | 6 +++--- packages/ens-controller/package.json | 6 +++--- packages/eth-json-rpc-provider/package.json | 6 +++--- packages/gas-fee-controller/package.json | 6 +++--- packages/json-rpc-engine/package.json | 6 +++--- packages/json-rpc-middleware-stream/package.json | 6 +++--- packages/keyring-controller/package.json | 6 +++--- packages/logging-controller/package.json | 6 +++--- packages/message-manager/package.json | 6 +++--- packages/name-controller/package.json | 6 +++--- packages/network-controller/package.json | 6 +++--- packages/notification-controller/package.json | 6 +++--- packages/permission-controller/package.json | 6 +++--- packages/permission-log-controller/package.json | 6 +++--- packages/phishing-controller/package.json | 6 +++--- packages/polling-controller/package.json | 6 +++--- packages/preferences-controller/package.json | 6 +++--- packages/profile-sync-controller/package.json | 6 +++--- packages/queued-request-controller/package.json | 6 +++--- packages/rate-limit-controller/package.json | 6 +++--- packages/selected-network-controller/package.json | 6 +++--- packages/signature-controller/package.json | 6 +++--- packages/transaction-controller/package.json | 6 +++--- packages/user-operation-controller/package.json | 6 +++--- 36 files changed, 108 insertions(+), 107 deletions(-) diff --git a/constraints.pro b/constraints.pro index 84cc63f1b55..387aab4bef5 100644 --- a/constraints.pro +++ b/constraints.pro @@ -312,11 +312,11 @@ gen_enforced_field(WorkspaceCwd, 'scripts.changelog:update', CorrectChangelogUpd \+ atom_concat(ExpectedPrefix, _, ChangelogUpdateCommand). % All non-root packages must have the same "test" script. -gen_enforced_field(WorkspaceCwd, 'scripts.test', 'jest --reporters=jest-silent-reporter') :- +gen_enforced_field(WorkspaceCwd, 'scripts.test', 'yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter') :- WorkspaceCwd \= '.'. -% All non-root packages must have the same "test:clean" script. -gen_enforced_field(WorkspaceCwd, 'scripts.test:clean', 'jest --clearCache') :- +% All non-root packages must have the same "test:clean-only" script. +gen_enforced_field(WorkspaceCwd, 'scripts.test:clean-only', 'jest --clearCache') :- WorkspaceCwd \= '.'. % All non-root packages must have the same "test:verbose" script. diff --git a/jest.config.scripts.js b/jest.config.scripts.js index a86dd27b949..52d7d203077 100644 --- a/jest.config.scripts.js +++ b/jest.config.scripts.js @@ -13,7 +13,7 @@ module.exports = { collectCoverage: true, // An array of glob patterns indicating a set of files for which coverage information should be collected - collectCoverageFrom: ['/scripts/**/*.ts'], + collectCoverageFrom: ['/scripts/create-package/**/*.ts'], // The directory where Jest should output its coverage files coverageDirectory: '/scripts/coverage', diff --git a/package.json b/package.json index 7a213821104..d25ab8bc66a 100644 --- a/package.json +++ b/package.json @@ -31,11 +31,12 @@ "prepare-preview-builds": "./scripts/prepare-preview-builds.sh", "publish-previews": "yarn workspaces foreach --no-private --parallel --verbose run publish:preview", "setup": "yarn install", - "test": "yarn test:scripts --silent --collectCoverage=false --reporters=jest-silent-reporter && yarn test:packages", - "test:clean": "yarn workspaces foreach --parallel --verbose run test:clean && yarn test", - "test:packages": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", - "test:scripts": "yarn jest --config ./jest.config.scripts.js --silent", - "test:verbose": "yarn workspaces foreach --parallel --verbose run test:verbose", + "test": "yarn test:scripts && yarn test:packages", + "test:clean-only": "yarn workspaces foreach --parallel --verbose run test:clean-only", + "test:packages": "yarn test:packages:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", + "test:packages:verbose": "yarn workspaces foreach --parallel --verbose run test:verbose", + "test:scripts": "yarn test:scripts:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", + "test:scripts:verbose": "yarn jest --config ./jest.config.scripts.js --verbose", "update-readme-content": "ts-node scripts/update-readme-content.ts", "workspaces:list-versions": "./scripts/list-workspace-versions.sh" }, diff --git a/packages/accounts-controller/package.json b/packages/accounts-controller/package.json index 8f9b11c1fe9..c40d54ef239 100644 --- a/packages/accounts-controller/package.json +++ b/packages/accounts-controller/package.json @@ -35,8 +35,8 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/accounts-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/accounts-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "jest --reporters=jest-silent-reporter", - "test:clean": "jest --clearCache", + "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", + "test:clean-only": "jest --clearCache", "test:verbose": "jest --verbose", "test:watch": "jest --watch" }, diff --git a/packages/address-book-controller/package.json b/packages/address-book-controller/package.json index 262ef3af82f..2b2a6e13c68 100644 --- a/packages/address-book-controller/package.json +++ b/packages/address-book-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/address-book-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/address-book-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "jest --reporters=jest-silent-reporter", - "test:clean": "jest --clearCache", + "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", "test:verbose": "jest --verbose", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "test:clean-only": "jest --clearCache" }, "dependencies": { "@metamask/base-controller": "^6.0.0", diff --git a/packages/announcement-controller/package.json b/packages/announcement-controller/package.json index 8f5aee8e457..a7bfdc90c89 100644 --- a/packages/announcement-controller/package.json +++ b/packages/announcement-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/announcement-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/announcement-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "jest --reporters=jest-silent-reporter", - "test:clean": "jest --clearCache", + "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", "test:verbose": "jest --verbose", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "test:clean-only": "jest --clearCache" }, "dependencies": { "@metamask/base-controller": "^6.0.0" diff --git a/packages/approval-controller/package.json b/packages/approval-controller/package.json index f8f6e61aea4..c97dd208e35 100644 --- a/packages/approval-controller/package.json +++ b/packages/approval-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/approval-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/approval-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "jest --reporters=jest-silent-reporter", - "test:clean": "jest --clearCache", + "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", "test:verbose": "jest --verbose", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "test:clean-only": "jest --clearCache" }, "dependencies": { "@metamask/base-controller": "^6.0.0", diff --git a/packages/assets-controllers/package.json b/packages/assets-controllers/package.json index f592e17c741..3f5a5a4145b 100644 --- a/packages/assets-controllers/package.json +++ b/packages/assets-controllers/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/assets-controllers", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/assets-controllers", "publish:preview": "yarn npm publish --tag preview", - "test": "jest --reporters=jest-silent-reporter", - "test:clean": "jest --clearCache", + "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", "test:verbose": "jest --verbose", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "test:clean-only": "jest --clearCache" }, "dependencies": { "@ethereumjs/util": "^8.1.0", diff --git a/packages/base-controller/package.json b/packages/base-controller/package.json index 8cb96411341..bef19586bf1 100644 --- a/packages/base-controller/package.json +++ b/packages/base-controller/package.json @@ -34,10 +34,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/base-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/base-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "jest --reporters=jest-silent-reporter", - "test:clean": "jest --clearCache", + "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", "test:verbose": "jest --verbose", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "test:clean-only": "jest --clearCache" }, "dependencies": { "@metamask/utils": "^8.3.0", diff --git a/packages/build-utils/package.json b/packages/build-utils/package.json index 29b7eec38e7..b659296aef8 100644 --- a/packages/build-utils/package.json +++ b/packages/build-utils/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/build-utils", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/build-utils", "publish:preview": "yarn npm publish --tag preview", - "test": "jest --reporters=jest-silent-reporter", - "test:clean": "jest --clearCache", + "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", "test:verbose": "jest --verbose", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "test:clean-only": "jest --clearCache" }, "dependencies": { "@metamask/utils": "^8.3.0", diff --git a/packages/chain-controller/package.json b/packages/chain-controller/package.json index cebebef3c7b..72e99b5d763 100644 --- a/packages/chain-controller/package.json +++ b/packages/chain-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/chain-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/chain-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "jest --reporters=jest-silent-reporter", - "test:clean": "jest --clearCache", + "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", "test:verbose": "jest --verbose", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "test:clean-only": "jest --clearCache" }, "dependencies": { "@metamask/base-controller": "^6.0.0", diff --git a/packages/composable-controller/package.json b/packages/composable-controller/package.json index 9865ba373af..bd2bfd7e067 100644 --- a/packages/composable-controller/package.json +++ b/packages/composable-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/composable-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/composable-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "jest --reporters=jest-silent-reporter", - "test:clean": "jest --clearCache", + "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", "test:verbose": "jest --verbose", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "test:clean-only": "jest --clearCache" }, "dependencies": { "@metamask/base-controller": "^6.0.0" diff --git a/packages/controller-utils/package.json b/packages/controller-utils/package.json index 893a4bd1f33..b87bcd03d09 100644 --- a/packages/controller-utils/package.json +++ b/packages/controller-utils/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/controller-utils", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/controller-utils", "publish:preview": "yarn npm publish --tag preview", - "test": "jest --reporters=jest-silent-reporter", - "test:clean": "jest --clearCache", + "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", "test:verbose": "jest --verbose", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "test:clean-only": "jest --clearCache" }, "dependencies": { "@ethereumjs/util": "^8.1.0", diff --git a/packages/ens-controller/package.json b/packages/ens-controller/package.json index 1bc1316b786..6d29499c02f 100644 --- a/packages/ens-controller/package.json +++ b/packages/ens-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/ens-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/ens-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "jest --reporters=jest-silent-reporter", - "test:clean": "jest --clearCache", + "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", "test:verbose": "jest --verbose", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "test:clean-only": "jest --clearCache" }, "dependencies": { "@ethersproject/providers": "^5.7.0", diff --git a/packages/eth-json-rpc-provider/package.json b/packages/eth-json-rpc-provider/package.json index 8f70b981914..751918d4cf2 100644 --- a/packages/eth-json-rpc-provider/package.json +++ b/packages/eth-json-rpc-provider/package.json @@ -40,10 +40,10 @@ "lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write && yarn lint:dependencies", "lint:misc": "prettier '**/*.json' '**/*.md' '!CHANGELOG.md' '**/*.yml' '!.yarnrc.yml' --ignore-path .gitignore --no-error-on-unmatched-pattern", "publish:preview": "yarn npm publish --tag preview", - "test": "jest --reporters=jest-silent-reporter", - "test:clean": "jest --clearCache", + "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", "test:verbose": "jest --verbose", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "test:clean-only": "jest --clearCache" }, "dependencies": { "@metamask/json-rpc-engine": "^9.0.0", diff --git a/packages/gas-fee-controller/package.json b/packages/gas-fee-controller/package.json index 61c1093a9dc..99b63508af3 100644 --- a/packages/gas-fee-controller/package.json +++ b/packages/gas-fee-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/gas-fee-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/gas-fee-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "jest --reporters=jest-silent-reporter", - "test:clean": "jest --clearCache", + "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", "test:verbose": "jest --verbose", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "test:clean-only": "jest --clearCache" }, "dependencies": { "@metamask/base-controller": "^6.0.0", diff --git a/packages/json-rpc-engine/package.json b/packages/json-rpc-engine/package.json index f306669f9a5..dee759ae882 100644 --- a/packages/json-rpc-engine/package.json +++ b/packages/json-rpc-engine/package.json @@ -44,10 +44,10 @@ "lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write && yarn lint:dependencies && yarn lint:changelog", "lint:misc": "prettier '**/*.json' '**/*.md' '!CHANGELOG.md' '**/*.yml' '!.yarnrc.yml' --ignore-path .gitignore --no-error-on-unmatched-pattern", "publish:preview": "yarn npm publish --tag preview", - "test": "jest --reporters=jest-silent-reporter", - "test:clean": "jest --clearCache", + "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", "test:verbose": "jest --verbose", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "test:clean-only": "jest --clearCache" }, "dependencies": { "@metamask/rpc-errors": "^6.2.1", diff --git a/packages/json-rpc-middleware-stream/package.json b/packages/json-rpc-middleware-stream/package.json index 88d92353c62..6849110d809 100644 --- a/packages/json-rpc-middleware-stream/package.json +++ b/packages/json-rpc-middleware-stream/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/json-rpc-middleware-stream --tag-prefix-before-package-rename json-rpc-middleware-stream@ --version-before-package-rename 5.0.1", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/json-rpc-middleware-stream --tag-prefix-before-package-rename json-rpc-middleware-stream@ --version-before-package-rename 5.0.1", "publish:preview": "yarn npm publish --tag preview", - "test": "jest --reporters=jest-silent-reporter", - "test:clean": "jest --clearCache", + "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", "test:verbose": "jest --verbose", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "test:clean-only": "jest --clearCache" }, "dependencies": { "@metamask/json-rpc-engine": "^9.0.0", diff --git a/packages/keyring-controller/package.json b/packages/keyring-controller/package.json index 88dc40a5775..7bb9673759d 100644 --- a/packages/keyring-controller/package.json +++ b/packages/keyring-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/keyring-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/keyring-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "jest --reporters=jest-silent-reporter", - "test:clean": "jest --clearCache", + "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", "test:verbose": "jest --verbose", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "test:clean-only": "jest --clearCache" }, "dependencies": { "@ethereumjs/util": "^8.1.0", diff --git a/packages/logging-controller/package.json b/packages/logging-controller/package.json index 5abe098c4f9..02d20ace4c5 100644 --- a/packages/logging-controller/package.json +++ b/packages/logging-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/logging-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/logging-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "jest --reporters=jest-silent-reporter", - "test:clean": "jest --clearCache", + "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", "test:verbose": "jest --verbose", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "test:clean-only": "jest --clearCache" }, "dependencies": { "@metamask/base-controller": "^6.0.0", diff --git a/packages/message-manager/package.json b/packages/message-manager/package.json index efbffd04d75..f3b2945dc0c 100644 --- a/packages/message-manager/package.json +++ b/packages/message-manager/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/message-manager", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/message-manager", "publish:preview": "yarn npm publish --tag preview", - "test": "jest --reporters=jest-silent-reporter", - "test:clean": "jest --clearCache", + "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", "test:verbose": "jest --verbose", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "test:clean-only": "jest --clearCache" }, "dependencies": { "@metamask/base-controller": "^6.0.0", diff --git a/packages/name-controller/package.json b/packages/name-controller/package.json index 01fe50988e8..876b8608c90 100644 --- a/packages/name-controller/package.json +++ b/packages/name-controller/package.json @@ -36,10 +36,10 @@ "changelog:validate": "../../scripts/validate-changelog.sh @metamask/name-controller", "prepare-manifest:preview": "../../scripts/prepare-preview-manifest.sh", "publish:preview": "yarn npm publish --tag preview", - "test": "jest --reporters=jest-silent-reporter", - "test:clean": "jest --clearCache", + "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", "test:verbose": "jest --verbose", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "test:clean-only": "jest --clearCache" }, "dependencies": { "@metamask/base-controller": "^6.0.0", diff --git a/packages/network-controller/package.json b/packages/network-controller/package.json index 947ed829b50..e59c1f69b58 100644 --- a/packages/network-controller/package.json +++ b/packages/network-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/network-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/network-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "jest --reporters=jest-silent-reporter", - "test:clean": "jest --clearCache", + "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", "test:verbose": "jest --verbose", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "test:clean-only": "jest --clearCache" }, "dependencies": { "@metamask/base-controller": "^6.0.0", diff --git a/packages/notification-controller/package.json b/packages/notification-controller/package.json index a95c51a01b7..ffaf5732471 100644 --- a/packages/notification-controller/package.json +++ b/packages/notification-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/notification-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/notification-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "jest --reporters=jest-silent-reporter", - "test:clean": "jest --clearCache", + "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", "test:verbose": "jest --verbose", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "test:clean-only": "jest --clearCache" }, "dependencies": { "@metamask/base-controller": "^6.0.0", diff --git a/packages/permission-controller/package.json b/packages/permission-controller/package.json index eaff8633b69..49def5901be 100644 --- a/packages/permission-controller/package.json +++ b/packages/permission-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/permission-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/permission-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "jest --reporters=jest-silent-reporter", - "test:clean": "jest --clearCache", + "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", "test:verbose": "jest --verbose", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "test:clean-only": "jest --clearCache" }, "dependencies": { "@metamask/base-controller": "^6.0.0", diff --git a/packages/permission-log-controller/package.json b/packages/permission-log-controller/package.json index 73101114062..f3029fc968a 100644 --- a/packages/permission-log-controller/package.json +++ b/packages/permission-log-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/permission-log-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/permission-log-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "jest --reporters=jest-silent-reporter", - "test:clean": "jest --clearCache", + "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", "test:verbose": "jest --verbose", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "test:clean-only": "jest --clearCache" }, "dependencies": { "@metamask/base-controller": "^6.0.0", diff --git a/packages/phishing-controller/package.json b/packages/phishing-controller/package.json index 843fb31d19f..48e192aa723 100644 --- a/packages/phishing-controller/package.json +++ b/packages/phishing-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/phishing-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/phishing-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "jest --reporters=jest-silent-reporter", - "test:clean": "jest --clearCache", + "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", "test:verbose": "jest --verbose", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "test:clean-only": "jest --clearCache" }, "dependencies": { "@metamask/base-controller": "^6.0.0", diff --git a/packages/polling-controller/package.json b/packages/polling-controller/package.json index 7e312b70a01..38a19a7a605 100644 --- a/packages/polling-controller/package.json +++ b/packages/polling-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/polling-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/polling-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "jest --reporters=jest-silent-reporter", - "test:clean": "jest --clearCache", + "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", "test:verbose": "jest --verbose", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "test:clean-only": "jest --clearCache" }, "dependencies": { "@metamask/base-controller": "^6.0.0", diff --git a/packages/preferences-controller/package.json b/packages/preferences-controller/package.json index 8465f6c9a42..c72a39c1bb5 100644 --- a/packages/preferences-controller/package.json +++ b/packages/preferences-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/preferences-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/preferences-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "jest --reporters=jest-silent-reporter", - "test:clean": "jest --clearCache", + "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", "test:verbose": "jest --verbose", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "test:clean-only": "jest --clearCache" }, "dependencies": { "@metamask/base-controller": "^6.0.0", diff --git a/packages/profile-sync-controller/package.json b/packages/profile-sync-controller/package.json index 3a2ea77c52b..0485d1ee96b 100644 --- a/packages/profile-sync-controller/package.json +++ b/packages/profile-sync-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/profile-sync-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/profile-sync-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "jest --reporters=jest-silent-reporter", - "test:clean": "jest --clearCache", + "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", "test:verbose": "jest --verbose", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "test:clean-only": "jest --clearCache" }, "dependencies": { "@noble/ciphers": "^0.5.2", diff --git a/packages/queued-request-controller/package.json b/packages/queued-request-controller/package.json index a6e33594cd5..2ca51f5ec86 100644 --- a/packages/queued-request-controller/package.json +++ b/packages/queued-request-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/queued-request-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/queued-request-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "jest --reporters=jest-silent-reporter", - "test:clean": "jest --clearCache", + "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", "test:verbose": "jest --verbose", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "test:clean-only": "jest --clearCache" }, "dependencies": { "@metamask/base-controller": "^6.0.0", diff --git a/packages/rate-limit-controller/package.json b/packages/rate-limit-controller/package.json index 3d7e6976107..ef29c7ff97e 100644 --- a/packages/rate-limit-controller/package.json +++ b/packages/rate-limit-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/rate-limit-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/rate-limit-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "jest --reporters=jest-silent-reporter", - "test:clean": "jest --clearCache", + "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", "test:verbose": "jest --verbose", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "test:clean-only": "jest --clearCache" }, "dependencies": { "@metamask/base-controller": "^6.0.0", diff --git a/packages/selected-network-controller/package.json b/packages/selected-network-controller/package.json index f0063491590..0e462da1662 100644 --- a/packages/selected-network-controller/package.json +++ b/packages/selected-network-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/selected-network-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/selected-network-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "jest --reporters=jest-silent-reporter", - "test:clean": "jest --clearCache", + "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", "test:verbose": "jest --verbose", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "test:clean-only": "jest --clearCache" }, "dependencies": { "@metamask/base-controller": "^6.0.0", diff --git a/packages/signature-controller/package.json b/packages/signature-controller/package.json index 245b89d31fc..bb37afebb76 100644 --- a/packages/signature-controller/package.json +++ b/packages/signature-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/signature-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/signature-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "jest --reporters=jest-silent-reporter", - "test:clean": "jest --clearCache", + "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", "test:verbose": "jest --verbose", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "test:clean-only": "jest --clearCache" }, "dependencies": { "@metamask/approval-controller": "^7.0.0", diff --git a/packages/transaction-controller/package.json b/packages/transaction-controller/package.json index 1b34db2bf6b..5b1ad09da8e 100644 --- a/packages/transaction-controller/package.json +++ b/packages/transaction-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/transaction-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/transaction-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "jest --reporters=jest-silent-reporter", - "test:clean": "jest --clearCache", + "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", "test:verbose": "jest --verbose", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "test:clean-only": "jest --clearCache" }, "dependencies": { "@ethereumjs/common": "^3.2.0", diff --git a/packages/user-operation-controller/package.json b/packages/user-operation-controller/package.json index bac9c0a988c..37a94c369e1 100644 --- a/packages/user-operation-controller/package.json +++ b/packages/user-operation-controller/package.json @@ -36,10 +36,10 @@ "changelog:validate": "../../scripts/validate-changelog.sh @metamask/user-operation-controller", "prepare-manifest:preview": "../../scripts/prepare-preview-manifest.sh", "publish:preview": "yarn npm publish --tag preview", - "test": "jest --reporters=jest-silent-reporter", - "test:clean": "jest --clearCache", + "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", "test:verbose": "jest --verbose", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "test:clean-only": "jest --clearCache" }, "dependencies": { "@metamask/approval-controller": "^7.0.0", From c5f45347d684f768d0b3b5177fbfe1565d7b5b2e Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Wed, 12 Jun 2024 13:49:14 -0600 Subject: [PATCH 03/49] Revert "WIP" This reverts commit 417ab4ef6f6c2fe6e9dcabb93f0b4f3498d1145b. --- constraints.pro | 6 +++--- jest.config.scripts.js | 2 +- package.json | 11 +++++------ packages/accounts-controller/package.json | 4 ++-- packages/address-book-controller/package.json | 6 +++--- packages/announcement-controller/package.json | 6 +++--- packages/approval-controller/package.json | 6 +++--- packages/assets-controllers/package.json | 6 +++--- packages/base-controller/package.json | 6 +++--- packages/build-utils/package.json | 6 +++--- packages/chain-controller/package.json | 6 +++--- packages/composable-controller/package.json | 6 +++--- packages/controller-utils/package.json | 6 +++--- packages/ens-controller/package.json | 6 +++--- packages/eth-json-rpc-provider/package.json | 6 +++--- packages/gas-fee-controller/package.json | 6 +++--- packages/json-rpc-engine/package.json | 6 +++--- packages/json-rpc-middleware-stream/package.json | 6 +++--- packages/keyring-controller/package.json | 6 +++--- packages/logging-controller/package.json | 6 +++--- packages/message-manager/package.json | 6 +++--- packages/name-controller/package.json | 6 +++--- packages/network-controller/package.json | 6 +++--- packages/notification-controller/package.json | 6 +++--- packages/permission-controller/package.json | 6 +++--- packages/permission-log-controller/package.json | 6 +++--- packages/phishing-controller/package.json | 6 +++--- packages/polling-controller/package.json | 6 +++--- packages/preferences-controller/package.json | 6 +++--- packages/profile-sync-controller/package.json | 6 +++--- packages/queued-request-controller/package.json | 6 +++--- packages/rate-limit-controller/package.json | 6 +++--- packages/selected-network-controller/package.json | 6 +++--- packages/signature-controller/package.json | 6 +++--- packages/transaction-controller/package.json | 6 +++--- packages/user-operation-controller/package.json | 6 +++--- 36 files changed, 107 insertions(+), 108 deletions(-) diff --git a/constraints.pro b/constraints.pro index 387aab4bef5..84cc63f1b55 100644 --- a/constraints.pro +++ b/constraints.pro @@ -312,11 +312,11 @@ gen_enforced_field(WorkspaceCwd, 'scripts.changelog:update', CorrectChangelogUpd \+ atom_concat(ExpectedPrefix, _, ChangelogUpdateCommand). % All non-root packages must have the same "test" script. -gen_enforced_field(WorkspaceCwd, 'scripts.test', 'yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter') :- +gen_enforced_field(WorkspaceCwd, 'scripts.test', 'jest --reporters=jest-silent-reporter') :- WorkspaceCwd \= '.'. -% All non-root packages must have the same "test:clean-only" script. -gen_enforced_field(WorkspaceCwd, 'scripts.test:clean-only', 'jest --clearCache') :- +% All non-root packages must have the same "test:clean" script. +gen_enforced_field(WorkspaceCwd, 'scripts.test:clean', 'jest --clearCache') :- WorkspaceCwd \= '.'. % All non-root packages must have the same "test:verbose" script. diff --git a/jest.config.scripts.js b/jest.config.scripts.js index 52d7d203077..a86dd27b949 100644 --- a/jest.config.scripts.js +++ b/jest.config.scripts.js @@ -13,7 +13,7 @@ module.exports = { collectCoverage: true, // An array of glob patterns indicating a set of files for which coverage information should be collected - collectCoverageFrom: ['/scripts/create-package/**/*.ts'], + collectCoverageFrom: ['/scripts/**/*.ts'], // The directory where Jest should output its coverage files coverageDirectory: '/scripts/coverage', diff --git a/package.json b/package.json index d25ab8bc66a..7a213821104 100644 --- a/package.json +++ b/package.json @@ -31,12 +31,11 @@ "prepare-preview-builds": "./scripts/prepare-preview-builds.sh", "publish-previews": "yarn workspaces foreach --no-private --parallel --verbose run publish:preview", "setup": "yarn install", - "test": "yarn test:scripts && yarn test:packages", - "test:clean-only": "yarn workspaces foreach --parallel --verbose run test:clean-only", - "test:packages": "yarn test:packages:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", - "test:packages:verbose": "yarn workspaces foreach --parallel --verbose run test:verbose", - "test:scripts": "yarn test:scripts:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", - "test:scripts:verbose": "yarn jest --config ./jest.config.scripts.js --verbose", + "test": "yarn test:scripts --silent --collectCoverage=false --reporters=jest-silent-reporter && yarn test:packages", + "test:clean": "yarn workspaces foreach --parallel --verbose run test:clean && yarn test", + "test:packages": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", + "test:scripts": "yarn jest --config ./jest.config.scripts.js --silent", + "test:verbose": "yarn workspaces foreach --parallel --verbose run test:verbose", "update-readme-content": "ts-node scripts/update-readme-content.ts", "workspaces:list-versions": "./scripts/list-workspace-versions.sh" }, diff --git a/packages/accounts-controller/package.json b/packages/accounts-controller/package.json index c40d54ef239..8f9b11c1fe9 100644 --- a/packages/accounts-controller/package.json +++ b/packages/accounts-controller/package.json @@ -35,8 +35,8 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/accounts-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/accounts-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", - "test:clean-only": "jest --clearCache", + "test": "jest --reporters=jest-silent-reporter", + "test:clean": "jest --clearCache", "test:verbose": "jest --verbose", "test:watch": "jest --watch" }, diff --git a/packages/address-book-controller/package.json b/packages/address-book-controller/package.json index 2b2a6e13c68..262ef3af82f 100644 --- a/packages/address-book-controller/package.json +++ b/packages/address-book-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/address-book-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/address-book-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", + "test": "jest --reporters=jest-silent-reporter", + "test:clean": "jest --clearCache", "test:verbose": "jest --verbose", - "test:watch": "jest --watch", - "test:clean-only": "jest --clearCache" + "test:watch": "jest --watch" }, "dependencies": { "@metamask/base-controller": "^6.0.0", diff --git a/packages/announcement-controller/package.json b/packages/announcement-controller/package.json index a7bfdc90c89..8f5aee8e457 100644 --- a/packages/announcement-controller/package.json +++ b/packages/announcement-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/announcement-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/announcement-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", + "test": "jest --reporters=jest-silent-reporter", + "test:clean": "jest --clearCache", "test:verbose": "jest --verbose", - "test:watch": "jest --watch", - "test:clean-only": "jest --clearCache" + "test:watch": "jest --watch" }, "dependencies": { "@metamask/base-controller": "^6.0.0" diff --git a/packages/approval-controller/package.json b/packages/approval-controller/package.json index c97dd208e35..f8f6e61aea4 100644 --- a/packages/approval-controller/package.json +++ b/packages/approval-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/approval-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/approval-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", + "test": "jest --reporters=jest-silent-reporter", + "test:clean": "jest --clearCache", "test:verbose": "jest --verbose", - "test:watch": "jest --watch", - "test:clean-only": "jest --clearCache" + "test:watch": "jest --watch" }, "dependencies": { "@metamask/base-controller": "^6.0.0", diff --git a/packages/assets-controllers/package.json b/packages/assets-controllers/package.json index 3f5a5a4145b..f592e17c741 100644 --- a/packages/assets-controllers/package.json +++ b/packages/assets-controllers/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/assets-controllers", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/assets-controllers", "publish:preview": "yarn npm publish --tag preview", - "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", + "test": "jest --reporters=jest-silent-reporter", + "test:clean": "jest --clearCache", "test:verbose": "jest --verbose", - "test:watch": "jest --watch", - "test:clean-only": "jest --clearCache" + "test:watch": "jest --watch" }, "dependencies": { "@ethereumjs/util": "^8.1.0", diff --git a/packages/base-controller/package.json b/packages/base-controller/package.json index bef19586bf1..8cb96411341 100644 --- a/packages/base-controller/package.json +++ b/packages/base-controller/package.json @@ -34,10 +34,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/base-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/base-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", + "test": "jest --reporters=jest-silent-reporter", + "test:clean": "jest --clearCache", "test:verbose": "jest --verbose", - "test:watch": "jest --watch", - "test:clean-only": "jest --clearCache" + "test:watch": "jest --watch" }, "dependencies": { "@metamask/utils": "^8.3.0", diff --git a/packages/build-utils/package.json b/packages/build-utils/package.json index b659296aef8..29b7eec38e7 100644 --- a/packages/build-utils/package.json +++ b/packages/build-utils/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/build-utils", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/build-utils", "publish:preview": "yarn npm publish --tag preview", - "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", + "test": "jest --reporters=jest-silent-reporter", + "test:clean": "jest --clearCache", "test:verbose": "jest --verbose", - "test:watch": "jest --watch", - "test:clean-only": "jest --clearCache" + "test:watch": "jest --watch" }, "dependencies": { "@metamask/utils": "^8.3.0", diff --git a/packages/chain-controller/package.json b/packages/chain-controller/package.json index 72e99b5d763..cebebef3c7b 100644 --- a/packages/chain-controller/package.json +++ b/packages/chain-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/chain-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/chain-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", + "test": "jest --reporters=jest-silent-reporter", + "test:clean": "jest --clearCache", "test:verbose": "jest --verbose", - "test:watch": "jest --watch", - "test:clean-only": "jest --clearCache" + "test:watch": "jest --watch" }, "dependencies": { "@metamask/base-controller": "^6.0.0", diff --git a/packages/composable-controller/package.json b/packages/composable-controller/package.json index bd2bfd7e067..9865ba373af 100644 --- a/packages/composable-controller/package.json +++ b/packages/composable-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/composable-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/composable-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", + "test": "jest --reporters=jest-silent-reporter", + "test:clean": "jest --clearCache", "test:verbose": "jest --verbose", - "test:watch": "jest --watch", - "test:clean-only": "jest --clearCache" + "test:watch": "jest --watch" }, "dependencies": { "@metamask/base-controller": "^6.0.0" diff --git a/packages/controller-utils/package.json b/packages/controller-utils/package.json index b87bcd03d09..893a4bd1f33 100644 --- a/packages/controller-utils/package.json +++ b/packages/controller-utils/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/controller-utils", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/controller-utils", "publish:preview": "yarn npm publish --tag preview", - "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", + "test": "jest --reporters=jest-silent-reporter", + "test:clean": "jest --clearCache", "test:verbose": "jest --verbose", - "test:watch": "jest --watch", - "test:clean-only": "jest --clearCache" + "test:watch": "jest --watch" }, "dependencies": { "@ethereumjs/util": "^8.1.0", diff --git a/packages/ens-controller/package.json b/packages/ens-controller/package.json index 6d29499c02f..1bc1316b786 100644 --- a/packages/ens-controller/package.json +++ b/packages/ens-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/ens-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/ens-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", + "test": "jest --reporters=jest-silent-reporter", + "test:clean": "jest --clearCache", "test:verbose": "jest --verbose", - "test:watch": "jest --watch", - "test:clean-only": "jest --clearCache" + "test:watch": "jest --watch" }, "dependencies": { "@ethersproject/providers": "^5.7.0", diff --git a/packages/eth-json-rpc-provider/package.json b/packages/eth-json-rpc-provider/package.json index 751918d4cf2..8f70b981914 100644 --- a/packages/eth-json-rpc-provider/package.json +++ b/packages/eth-json-rpc-provider/package.json @@ -40,10 +40,10 @@ "lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write && yarn lint:dependencies", "lint:misc": "prettier '**/*.json' '**/*.md' '!CHANGELOG.md' '**/*.yml' '!.yarnrc.yml' --ignore-path .gitignore --no-error-on-unmatched-pattern", "publish:preview": "yarn npm publish --tag preview", - "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", + "test": "jest --reporters=jest-silent-reporter", + "test:clean": "jest --clearCache", "test:verbose": "jest --verbose", - "test:watch": "jest --watch", - "test:clean-only": "jest --clearCache" + "test:watch": "jest --watch" }, "dependencies": { "@metamask/json-rpc-engine": "^9.0.0", diff --git a/packages/gas-fee-controller/package.json b/packages/gas-fee-controller/package.json index 99b63508af3..61c1093a9dc 100644 --- a/packages/gas-fee-controller/package.json +++ b/packages/gas-fee-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/gas-fee-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/gas-fee-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", + "test": "jest --reporters=jest-silent-reporter", + "test:clean": "jest --clearCache", "test:verbose": "jest --verbose", - "test:watch": "jest --watch", - "test:clean-only": "jest --clearCache" + "test:watch": "jest --watch" }, "dependencies": { "@metamask/base-controller": "^6.0.0", diff --git a/packages/json-rpc-engine/package.json b/packages/json-rpc-engine/package.json index dee759ae882..f306669f9a5 100644 --- a/packages/json-rpc-engine/package.json +++ b/packages/json-rpc-engine/package.json @@ -44,10 +44,10 @@ "lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write && yarn lint:dependencies && yarn lint:changelog", "lint:misc": "prettier '**/*.json' '**/*.md' '!CHANGELOG.md' '**/*.yml' '!.yarnrc.yml' --ignore-path .gitignore --no-error-on-unmatched-pattern", "publish:preview": "yarn npm publish --tag preview", - "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", + "test": "jest --reporters=jest-silent-reporter", + "test:clean": "jest --clearCache", "test:verbose": "jest --verbose", - "test:watch": "jest --watch", - "test:clean-only": "jest --clearCache" + "test:watch": "jest --watch" }, "dependencies": { "@metamask/rpc-errors": "^6.2.1", diff --git a/packages/json-rpc-middleware-stream/package.json b/packages/json-rpc-middleware-stream/package.json index 6849110d809..88d92353c62 100644 --- a/packages/json-rpc-middleware-stream/package.json +++ b/packages/json-rpc-middleware-stream/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/json-rpc-middleware-stream --tag-prefix-before-package-rename json-rpc-middleware-stream@ --version-before-package-rename 5.0.1", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/json-rpc-middleware-stream --tag-prefix-before-package-rename json-rpc-middleware-stream@ --version-before-package-rename 5.0.1", "publish:preview": "yarn npm publish --tag preview", - "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", + "test": "jest --reporters=jest-silent-reporter", + "test:clean": "jest --clearCache", "test:verbose": "jest --verbose", - "test:watch": "jest --watch", - "test:clean-only": "jest --clearCache" + "test:watch": "jest --watch" }, "dependencies": { "@metamask/json-rpc-engine": "^9.0.0", diff --git a/packages/keyring-controller/package.json b/packages/keyring-controller/package.json index 7bb9673759d..88dc40a5775 100644 --- a/packages/keyring-controller/package.json +++ b/packages/keyring-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/keyring-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/keyring-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", + "test": "jest --reporters=jest-silent-reporter", + "test:clean": "jest --clearCache", "test:verbose": "jest --verbose", - "test:watch": "jest --watch", - "test:clean-only": "jest --clearCache" + "test:watch": "jest --watch" }, "dependencies": { "@ethereumjs/util": "^8.1.0", diff --git a/packages/logging-controller/package.json b/packages/logging-controller/package.json index 02d20ace4c5..5abe098c4f9 100644 --- a/packages/logging-controller/package.json +++ b/packages/logging-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/logging-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/logging-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", + "test": "jest --reporters=jest-silent-reporter", + "test:clean": "jest --clearCache", "test:verbose": "jest --verbose", - "test:watch": "jest --watch", - "test:clean-only": "jest --clearCache" + "test:watch": "jest --watch" }, "dependencies": { "@metamask/base-controller": "^6.0.0", diff --git a/packages/message-manager/package.json b/packages/message-manager/package.json index f3b2945dc0c..efbffd04d75 100644 --- a/packages/message-manager/package.json +++ b/packages/message-manager/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/message-manager", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/message-manager", "publish:preview": "yarn npm publish --tag preview", - "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", + "test": "jest --reporters=jest-silent-reporter", + "test:clean": "jest --clearCache", "test:verbose": "jest --verbose", - "test:watch": "jest --watch", - "test:clean-only": "jest --clearCache" + "test:watch": "jest --watch" }, "dependencies": { "@metamask/base-controller": "^6.0.0", diff --git a/packages/name-controller/package.json b/packages/name-controller/package.json index 876b8608c90..01fe50988e8 100644 --- a/packages/name-controller/package.json +++ b/packages/name-controller/package.json @@ -36,10 +36,10 @@ "changelog:validate": "../../scripts/validate-changelog.sh @metamask/name-controller", "prepare-manifest:preview": "../../scripts/prepare-preview-manifest.sh", "publish:preview": "yarn npm publish --tag preview", - "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", + "test": "jest --reporters=jest-silent-reporter", + "test:clean": "jest --clearCache", "test:verbose": "jest --verbose", - "test:watch": "jest --watch", - "test:clean-only": "jest --clearCache" + "test:watch": "jest --watch" }, "dependencies": { "@metamask/base-controller": "^6.0.0", diff --git a/packages/network-controller/package.json b/packages/network-controller/package.json index e59c1f69b58..947ed829b50 100644 --- a/packages/network-controller/package.json +++ b/packages/network-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/network-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/network-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", + "test": "jest --reporters=jest-silent-reporter", + "test:clean": "jest --clearCache", "test:verbose": "jest --verbose", - "test:watch": "jest --watch", - "test:clean-only": "jest --clearCache" + "test:watch": "jest --watch" }, "dependencies": { "@metamask/base-controller": "^6.0.0", diff --git a/packages/notification-controller/package.json b/packages/notification-controller/package.json index ffaf5732471..a95c51a01b7 100644 --- a/packages/notification-controller/package.json +++ b/packages/notification-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/notification-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/notification-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", + "test": "jest --reporters=jest-silent-reporter", + "test:clean": "jest --clearCache", "test:verbose": "jest --verbose", - "test:watch": "jest --watch", - "test:clean-only": "jest --clearCache" + "test:watch": "jest --watch" }, "dependencies": { "@metamask/base-controller": "^6.0.0", diff --git a/packages/permission-controller/package.json b/packages/permission-controller/package.json index 49def5901be..eaff8633b69 100644 --- a/packages/permission-controller/package.json +++ b/packages/permission-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/permission-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/permission-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", + "test": "jest --reporters=jest-silent-reporter", + "test:clean": "jest --clearCache", "test:verbose": "jest --verbose", - "test:watch": "jest --watch", - "test:clean-only": "jest --clearCache" + "test:watch": "jest --watch" }, "dependencies": { "@metamask/base-controller": "^6.0.0", diff --git a/packages/permission-log-controller/package.json b/packages/permission-log-controller/package.json index f3029fc968a..73101114062 100644 --- a/packages/permission-log-controller/package.json +++ b/packages/permission-log-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/permission-log-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/permission-log-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", + "test": "jest --reporters=jest-silent-reporter", + "test:clean": "jest --clearCache", "test:verbose": "jest --verbose", - "test:watch": "jest --watch", - "test:clean-only": "jest --clearCache" + "test:watch": "jest --watch" }, "dependencies": { "@metamask/base-controller": "^6.0.0", diff --git a/packages/phishing-controller/package.json b/packages/phishing-controller/package.json index 48e192aa723..843fb31d19f 100644 --- a/packages/phishing-controller/package.json +++ b/packages/phishing-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/phishing-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/phishing-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", + "test": "jest --reporters=jest-silent-reporter", + "test:clean": "jest --clearCache", "test:verbose": "jest --verbose", - "test:watch": "jest --watch", - "test:clean-only": "jest --clearCache" + "test:watch": "jest --watch" }, "dependencies": { "@metamask/base-controller": "^6.0.0", diff --git a/packages/polling-controller/package.json b/packages/polling-controller/package.json index 38a19a7a605..7e312b70a01 100644 --- a/packages/polling-controller/package.json +++ b/packages/polling-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/polling-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/polling-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", + "test": "jest --reporters=jest-silent-reporter", + "test:clean": "jest --clearCache", "test:verbose": "jest --verbose", - "test:watch": "jest --watch", - "test:clean-only": "jest --clearCache" + "test:watch": "jest --watch" }, "dependencies": { "@metamask/base-controller": "^6.0.0", diff --git a/packages/preferences-controller/package.json b/packages/preferences-controller/package.json index c72a39c1bb5..8465f6c9a42 100644 --- a/packages/preferences-controller/package.json +++ b/packages/preferences-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/preferences-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/preferences-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", + "test": "jest --reporters=jest-silent-reporter", + "test:clean": "jest --clearCache", "test:verbose": "jest --verbose", - "test:watch": "jest --watch", - "test:clean-only": "jest --clearCache" + "test:watch": "jest --watch" }, "dependencies": { "@metamask/base-controller": "^6.0.0", diff --git a/packages/profile-sync-controller/package.json b/packages/profile-sync-controller/package.json index 0485d1ee96b..3a2ea77c52b 100644 --- a/packages/profile-sync-controller/package.json +++ b/packages/profile-sync-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/profile-sync-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/profile-sync-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", + "test": "jest --reporters=jest-silent-reporter", + "test:clean": "jest --clearCache", "test:verbose": "jest --verbose", - "test:watch": "jest --watch", - "test:clean-only": "jest --clearCache" + "test:watch": "jest --watch" }, "dependencies": { "@noble/ciphers": "^0.5.2", diff --git a/packages/queued-request-controller/package.json b/packages/queued-request-controller/package.json index 2ca51f5ec86..a6e33594cd5 100644 --- a/packages/queued-request-controller/package.json +++ b/packages/queued-request-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/queued-request-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/queued-request-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", + "test": "jest --reporters=jest-silent-reporter", + "test:clean": "jest --clearCache", "test:verbose": "jest --verbose", - "test:watch": "jest --watch", - "test:clean-only": "jest --clearCache" + "test:watch": "jest --watch" }, "dependencies": { "@metamask/base-controller": "^6.0.0", diff --git a/packages/rate-limit-controller/package.json b/packages/rate-limit-controller/package.json index ef29c7ff97e..3d7e6976107 100644 --- a/packages/rate-limit-controller/package.json +++ b/packages/rate-limit-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/rate-limit-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/rate-limit-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", + "test": "jest --reporters=jest-silent-reporter", + "test:clean": "jest --clearCache", "test:verbose": "jest --verbose", - "test:watch": "jest --watch", - "test:clean-only": "jest --clearCache" + "test:watch": "jest --watch" }, "dependencies": { "@metamask/base-controller": "^6.0.0", diff --git a/packages/selected-network-controller/package.json b/packages/selected-network-controller/package.json index 0e462da1662..f0063491590 100644 --- a/packages/selected-network-controller/package.json +++ b/packages/selected-network-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/selected-network-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/selected-network-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", + "test": "jest --reporters=jest-silent-reporter", + "test:clean": "jest --clearCache", "test:verbose": "jest --verbose", - "test:watch": "jest --watch", - "test:clean-only": "jest --clearCache" + "test:watch": "jest --watch" }, "dependencies": { "@metamask/base-controller": "^6.0.0", diff --git a/packages/signature-controller/package.json b/packages/signature-controller/package.json index bb37afebb76..245b89d31fc 100644 --- a/packages/signature-controller/package.json +++ b/packages/signature-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/signature-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/signature-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", + "test": "jest --reporters=jest-silent-reporter", + "test:clean": "jest --clearCache", "test:verbose": "jest --verbose", - "test:watch": "jest --watch", - "test:clean-only": "jest --clearCache" + "test:watch": "jest --watch" }, "dependencies": { "@metamask/approval-controller": "^7.0.0", diff --git a/packages/transaction-controller/package.json b/packages/transaction-controller/package.json index 5b1ad09da8e..1b34db2bf6b 100644 --- a/packages/transaction-controller/package.json +++ b/packages/transaction-controller/package.json @@ -35,10 +35,10 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/transaction-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/transaction-controller", "publish:preview": "yarn npm publish --tag preview", - "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", + "test": "jest --reporters=jest-silent-reporter", + "test:clean": "jest --clearCache", "test:verbose": "jest --verbose", - "test:watch": "jest --watch", - "test:clean-only": "jest --clearCache" + "test:watch": "jest --watch" }, "dependencies": { "@ethereumjs/common": "^3.2.0", diff --git a/packages/user-operation-controller/package.json b/packages/user-operation-controller/package.json index 37a94c369e1..bac9c0a988c 100644 --- a/packages/user-operation-controller/package.json +++ b/packages/user-operation-controller/package.json @@ -36,10 +36,10 @@ "changelog:validate": "../../scripts/validate-changelog.sh @metamask/user-operation-controller", "prepare-manifest:preview": "../../scripts/prepare-preview-manifest.sh", "publish:preview": "yarn npm publish --tag preview", - "test": "yarn test:verbose --silent --collectCoverage=false --reporters=jest-silent-reporter", + "test": "jest --reporters=jest-silent-reporter", + "test:clean": "jest --clearCache", "test:verbose": "jest --verbose", - "test:watch": "jest --watch", - "test:clean-only": "jest --clearCache" + "test:watch": "jest --watch" }, "dependencies": { "@metamask/approval-controller": "^7.0.0", From 648004d6c318cb132e317af6cb5ab827900a338f Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Thu, 13 Jun 2024 10:31:25 -0600 Subject: [PATCH 04/49] Fix tests --- .../tests/SelectedNetworkController.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/selected-network-controller/tests/SelectedNetworkController.test.ts b/packages/selected-network-controller/tests/SelectedNetworkController.test.ts index b8b04ee6c1f..d42f5e829a8 100644 --- a/packages/selected-network-controller/tests/SelectedNetworkController.test.ts +++ b/packages/selected-network-controller/tests/SelectedNetworkController.test.ts @@ -281,7 +281,7 @@ describe('SelectedNetworkController', () => { 'NetworkController:stateChange', { selectedNetworkClientId: 'goerli', - networkConfigurations: {}, + networkConfigurationsByChainId: {}, networksMetadata: {}, }, [ From f5f52328fd4f53abc379f64249db084f35ce478e Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Thu, 13 Jun 2024 12:03:11 -0600 Subject: [PATCH 05/49] Update JSDoc for nativeTokenName Co-authored-by: Jongsun Suh --- packages/network-controller/src/NetworkController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index 29b6b1261c7..03224fe8a39 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -187,7 +187,7 @@ export type NetworkConfiguration = { */ name: string; /** - * The name of the token that represents the native currency for the chain. + * The name of the token that represents the native currency for the chain (i.e. the ticker symbol). */ nativeTokenName: string; /** From c2a4e0269ba04843c65b7b5530da97321f4683b4 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Thu, 13 Jun 2024 12:08:52 -0600 Subject: [PATCH 06/49] Remove extra empty line Co-authored-by: Jongsun Suh --- packages/network-controller/src/NetworkController.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index 03224fe8a39..2a025e5dba3 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -216,7 +216,6 @@ export type AddNetworkCustomRpcEndpointFields = Omit< * Custom RPC endpoints do not need a `networkClientId` property because it is * assumed that they have not already been added and are not represented by * network clients yet. - * */ export type AddNetworkFields = Omit & { rpcEndpoints: (InfuraRpcEndpoint | AddNetworkCustomRpcEndpointFields)[]; From cc15c05c387afa2faeb45ddecaafd6453239e806 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Thu, 13 Jun 2024 12:02:37 -0600 Subject: [PATCH 07/49] Really fix tests --- .../src/NftDetectionController.test.ts | 4 +-- .../TransactionControllerIntegration.test.ts | 36 +++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/packages/assets-controllers/src/NftDetectionController.test.ts b/packages/assets-controllers/src/NftDetectionController.test.ts index 38b31b0d0d7..5339eac3841 100644 --- a/packages/assets-controllers/src/NftDetectionController.test.ts +++ b/packages/assets-controllers/src/NftDetectionController.test.ts @@ -2,8 +2,8 @@ import type { AccountsController } from '@metamask/accounts-controller'; import { ControllerMessenger } from '@metamask/base-controller'; import { NFT_API_BASE_URL, ChainId } from '@metamask/controller-utils'; import { + getDefaultNetworkControllerState, NetworkClientType, - defaultState as defaultNetworkState, } from '@metamask/network-controller'; import type { NetworkClient, @@ -1033,7 +1033,7 @@ async function withController( messenger.registerActionHandler( 'NetworkController:getState', jest.fn().mockReturnValue({ - ...defaultNetworkState, + ...getDefaultNetworkControllerState(), ...mockNetworkState, }), ); diff --git a/packages/transaction-controller/src/TransactionControllerIntegration.test.ts b/packages/transaction-controller/src/TransactionControllerIntegration.test.ts index ee211238c07..71f7d84cca7 100644 --- a/packages/transaction-controller/src/TransactionControllerIntegration.test.ts +++ b/packages/transaction-controller/src/TransactionControllerIntegration.test.ts @@ -14,6 +14,7 @@ import { NetworkType, } from '@metamask/controller-utils'; import type { InternalAccount } from '@metamask/keyring-api'; +import { EthAccountType, EthMethod } from '@metamask/keyring-api'; import { NetworkController, NetworkClientType, @@ -85,6 +86,41 @@ type UnrestrictedControllerMessenger = ControllerMessenger< const uuidV4Mock = jest.mocked(uuidV4); +const createMockInternalAccount = ({ + id = uuidV4(), + address = '0x2990079bcdee240329a520d2444386fc119da21a', + name = 'Account 1', + importTime = Date.now(), + lastSelected = Date.now(), +}: { + id?: string; + address?: string; + name?: string; + importTime?: number; + lastSelected?: number; +} = {}): InternalAccount => { + return { + id, + address, + options: {}, + methods: [ + EthMethod.PersonalSign, + EthMethod.Sign, + EthMethod.SignTransaction, + EthMethod.SignTypedDataV1, + EthMethod.SignTypedDataV3, + EthMethod.SignTypedDataV4, + ], + type: EthAccountType.Eoa, + metadata: { + name, + keyring: { type: 'HD Key Tree' }, + importTime, + lastSelected, + }, + } as InternalAccount; +}; + const ACCOUNT_MOCK = '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207'; const INTERNAL_ACCOUNT_MOCK = createMockInternalAccount({ address: ACCOUNT_MOCK, From 89345a5abba10c42620246b376249bb5aee2e7cf Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Thu, 13 Jun 2024 12:05:04 -0600 Subject: [PATCH 08/49] Drop type assertions for reduce --- .../src/NetworkController.ts | 49 +++++++++---------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index 2a025e5dba3..6f982934fd9 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -501,32 +501,31 @@ function getDefaultNetworkConfigurationsByChainId(): Record< Hex, NetworkConfiguration > { - return Object.values(InfuraNetworkType).reduce( - (obj: Partial>, infuraNetworkType) => { - const chainId = ChainId[infuraNetworkType]; - // False negative - this is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - const rpcEndpointUrl = `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`; - - const networkConfiguration: NetworkConfiguration = { - chainId, - defaultRpcEndpointUrl: rpcEndpointUrl, - name: NetworkNickname[infuraNetworkType], - nativeTokenName: NetworksTicker[infuraNetworkType], - rpcEndpoints: [ - { - name: `Infura ${NetworkNickname[infuraNetworkType] as string}`, - networkClientId: infuraNetworkType, - type: RpcEndpointType.Infura, - url: rpcEndpointUrl, - }, - ], - }; + return Object.values(InfuraNetworkType).reduce< + Record + >((obj, infuraNetworkType) => { + const chainId = ChainId[infuraNetworkType]; + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + const rpcEndpointUrl = `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`; + + const networkConfiguration: NetworkConfiguration = { + chainId, + defaultRpcEndpointUrl: rpcEndpointUrl, + name: NetworkNickname[infuraNetworkType], + nativeTokenName: NetworksTicker[infuraNetworkType], + rpcEndpoints: [ + { + name: `Infura ${NetworkNickname[infuraNetworkType] as string}`, + networkClientId: infuraNetworkType, + type: RpcEndpointType.Infura, + url: rpcEndpointUrl, + }, + ], + }; - return { ...obj, [chainId]: networkConfiguration }; - }, - {}, - ) as Record; + return { ...obj, [chainId]: networkConfiguration }; + }, {}); } /** From a600e9244be9f77c88e9369f6aab7c833b0374a1 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Thu, 13 Jun 2024 12:08:38 -0600 Subject: [PATCH 09/49] Add @link's to @see's --- .../network-controller/src/NetworkController.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index 6f982934fd9..da135e688c0 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -77,8 +77,8 @@ export type NetworkMetadata = { /** * The type of an RPC endpoint. * - * @see CustomRpcEndpoint - * @see InfuraRpcEndpoint + * @see {@link CustomRpcEndpoint} + * @see {@link InfuraRpcEndpoint} */ export enum RpcEndpointType { Custom = 'custom', @@ -146,8 +146,8 @@ export type CustomRpcEndpoint = { * An RPC endpoint is a reference to a server which fronts an EVM chain. There * are two varieties of RPC endpoints: Infura and custom. * - * @see CustomRpcEndpoint - * @see InfuraRpcEndpoint + * @see {@link CustomRpcEndpoint} + * @see {@link InfuraRpcEndpoint} */ export type RpcEndpoint = InfuraRpcEndpoint | CustomRpcEndpoint; @@ -316,7 +316,7 @@ export type NetworkState = { * The registry of networks and corresponding RPC endpoints that the * controller can use to make requests for various chains. * - * @see NetworkConfiguration + * @see {@link NetworkConfiguration} */ networkConfigurationsByChainId: Record; /** @@ -1359,7 +1359,7 @@ export class NetworkController extends BaseController< * the RPC endpoints which front that chain. * @returns The newly added network configuration. * @throws if any part of `fields` would produce invalid state. - * @see NetworkConfiguration + * @see {@link NetworkConfiguration} */ addNetwork(fields: AddNetworkFields): NetworkConfiguration { const { @@ -1598,7 +1598,7 @@ export class NetworkController extends BaseController< * @returns The updated network configuration. * @throws if `chainId` does not refer to an existing network configuration, * or if any part of `fields` would produce invalid state. - * @see NetworkConfiguration + * @see {@link NetworkConfiguration} */ updateNetwork( chainId: Hex, @@ -1907,7 +1907,7 @@ export class NetworkController extends BaseController< * @param chainId - The chain ID associated with an existing network. * @throws if `chainId` does not refer to an existing network configuration, * or if the currently selected network is being removed. - * @see NetworkConfiguration + * @see {@link NetworkConfiguration} */ removeNetwork(chainId: Hex) { const existingNetworkConfiguration = From fb10024711e4b99ceeb67b1d96b9e7366dc3dab4 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Thu, 13 Jun 2024 12:10:45 -0600 Subject: [PATCH 10/49] Fix lint --- packages/network-controller/src/NetworkController.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index da135e688c0..543dd54e643 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -187,7 +187,8 @@ export type NetworkConfiguration = { */ name: string; /** - * The name of the token that represents the native currency for the chain (i.e. the ticker symbol). + * The name of the token that represents the native currency for the chain + * (i.e. the ticker symbol). */ nativeTokenName: string; /** From 0ee2e0a700447e5da18459defaf9ec8031f3a4b8 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Thu, 13 Jun 2024 12:15:20 -0600 Subject: [PATCH 11/49] Fix tests again --- .../assets-controllers/src/AccountTrackerController.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/assets-controllers/src/AccountTrackerController.test.ts b/packages/assets-controllers/src/AccountTrackerController.test.ts index 1cc04814ae8..c7c2980ac0c 100644 --- a/packages/assets-controllers/src/AccountTrackerController.test.ts +++ b/packages/assets-controllers/src/AccountTrackerController.test.ts @@ -8,7 +8,7 @@ import type { InternalAccount } from '@metamask/keyring-api'; import { type NetworkClientId, type NetworkClientConfiguration, - defaultState as defaultnetworkControllerState, + getDefaultNetworkControllerState, } from '@metamask/network-controller'; import { buildCustomNetworkClientConfiguration, @@ -618,7 +618,7 @@ async function withController( ); const mockNetworkState = jest.fn().mockReturnValue({ - ...defaultnetworkControllerState, + ...getDefaultNetworkControllerState(), chainId: initialChainId, }); messenger.registerActionHandler( From 98d4c2a0269e9897453616c51610a66c3f308472 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Tue, 18 Jun 2024 14:54:46 -0600 Subject: [PATCH 12/49] Rename nativeTokenName to nativeCurrency --- .../src/TokenDetectionController.test.ts | 2 +- packages/network-controller/CHANGELOG.md | 2 +- .../src/NetworkController.ts | 16 +- .../tests/NetworkController.test.ts | 138 +++++++++--------- packages/network-controller/tests/helpers.ts | 8 +- 5 files changed, 83 insertions(+), 83 deletions(-) diff --git a/packages/assets-controllers/src/TokenDetectionController.test.ts b/packages/assets-controllers/src/TokenDetectionController.test.ts index d957a8c6d6c..c7c67f69c13 100644 --- a/packages/assets-controllers/src/TokenDetectionController.test.ts +++ b/packages/assets-controllers/src/TokenDetectionController.test.ts @@ -124,7 +124,7 @@ const mockNetworkConfigurations: Record = { chainId: '0x89', defaultRpcEndpointUrl: 'https://polygon-mainnet.infura.io/v3/fakekey', name: 'Polygon Mainnet', - nativeTokenName: 'MATIC', + nativeCurrency: 'MATIC', rpcEndpoints: [ buildCustomRpcEndpoint({ url: 'https://polygon-mainnet.infura.io/v3/fakekey', diff --git a/packages/network-controller/CHANGELOG.md b/packages/network-controller/CHANGELOG.md index e5d59230a82..54af6e8e161 100644 --- a/packages/network-controller/CHANGELOG.md +++ b/packages/network-controller/CHANGELOG.md @@ -25,7 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **BREAKING:** Replace `NetworkConfiguration` type with a new definition ([#4268](https://github.com/MetaMask/core/pull/4286)) - A network configuration no longer represents a single RPC endpoint but rather a collection of RPC endpoints that can all be used to interface with a single chain. - The only property that has been retained on this type is `chainId`. - - `ticker` has been renamed to `nativeTokenName`. + - `ticker` has been renamed to `nativeCurrency`. - `nickname` has been renamed to `name`. - `blockExplorerUrl` has been pulled out of `rpcPrefs`. - `rpcEndpoints` has been added as well. This is an an array of objects, where each object has properties `name`, `networkClientId` (optional), `type`, and `url`. diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index 543dd54e643..bb9e6342ab3 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -190,7 +190,7 @@ export type NetworkConfiguration = { * The name of the token that represents the native currency for the chain * (i.e. the ticker symbol). */ - nativeTokenName: string; + nativeCurrency: string; /** * The collection of possible RPC endpoints that the client can use to * interact with the chain. @@ -514,7 +514,7 @@ function getDefaultNetworkConfigurationsByChainId(): Record< chainId, defaultRpcEndpointUrl: rpcEndpointUrl, name: NetworkNickname[infuraNetworkType], - nativeTokenName: NetworksTicker[infuraNetworkType], + nativeCurrency: NetworksTicker[infuraNetworkType], rpcEndpoints: [ { name: `Infura ${NetworkNickname[infuraNetworkType] as string}`, @@ -1367,7 +1367,7 @@ export class NetworkController extends BaseController< blockExplorerUrl, chainId, defaultRpcEndpointUrl, - nativeTokenName, + nativeCurrency, rpcEndpoints: setOfRpcEndpointFields, } = fields; const rpcEndpointUrls = setOfRpcEndpointFields.map( @@ -1546,7 +1546,7 @@ export class NetworkController extends BaseController< chainId, infuraProjectId: this.#infuraProjectId, network: rpcEndpoint.networkClientId, - ticker: nativeTokenName, + ticker: nativeCurrency, type: NetworkClientType.Infura, }); } else { @@ -1555,7 +1555,7 @@ export class NetworkController extends BaseController< ] = createAutoManagedNetworkClient({ chainId, rpcUrl: rpcEndpoint.url, - ticker: nativeTokenName, + ticker: nativeCurrency, type: NetworkClientType.Custom, }); } @@ -1619,7 +1619,7 @@ export class NetworkController extends BaseController< blockExplorerUrl: newBlockExplorerUrl, chainId: newChainId, defaultRpcEndpointUrl: newInfuraRpcEndpointUrl, - nativeTokenName: newNativeTokenName, + nativeCurrency: newNativeTokenName, rpcEndpoints: setOfNewRpcEndpointFields, } = fields; const newRpcEndpointUrls = setOfNewRpcEndpointFields.map( @@ -2053,7 +2053,7 @@ export class NetworkController extends BaseController< network: infuraNetworkName, infuraProjectId: this.#infuraProjectId, chainId: networkConfiguration.chainId, - ticker: networkConfiguration.nativeTokenName, + ticker: networkConfiguration.nativeCurrency, }), ] as const; } @@ -2063,7 +2063,7 @@ export class NetworkController extends BaseController< type: NetworkClientType.Custom, chainId: networkConfiguration.chainId, rpcUrl: rpcEndpoint.url, - ticker: networkConfiguration.nativeTokenName, + ticker: networkConfiguration.nativeCurrency, }), ] as const; }); diff --git a/packages/network-controller/tests/NetworkController.test.ts b/packages/network-controller/tests/NetworkController.test.ts index 4d51976e1c7..a49a63f1e76 100644 --- a/packages/network-controller/tests/NetworkController.test.ts +++ b/packages/network-controller/tests/NetworkController.test.ts @@ -295,7 +295,7 @@ describe('NetworkController', () => { "chainId": "0x1", "defaultRpcEndpointUrl": "https://mainnet.infura.io/v3/{infuraProjectId}", "name": "Mainnet", - "nativeTokenName": "ETH", + "nativeCurrency": "ETH", "rpcEndpoints": Array [ Object { "name": "Infura Mainnet", @@ -309,7 +309,7 @@ describe('NetworkController', () => { "chainId": "0x5", "defaultRpcEndpointUrl": "https://goerli.infura.io/v3/{infuraProjectId}", "name": "Goerli", - "nativeTokenName": "GoerliETH", + "nativeCurrency": "GoerliETH", "rpcEndpoints": Array [ Object { "name": "Infura Goerli", @@ -323,7 +323,7 @@ describe('NetworkController', () => { "chainId": "0xaa36a7", "defaultRpcEndpointUrl": "https://sepolia.infura.io/v3/{infuraProjectId}", "name": "Sepolia", - "nativeTokenName": "SepoliaETH", + "nativeCurrency": "SepoliaETH", "rpcEndpoints": Array [ Object { "name": "Infura Sepolia", @@ -337,7 +337,7 @@ describe('NetworkController', () => { "chainId": "0xe704", "defaultRpcEndpointUrl": "https://linea-goerli.infura.io/v3/{infuraProjectId}", "name": "Linea Goerli", - "nativeTokenName": "LineaETH", + "nativeCurrency": "LineaETH", "rpcEndpoints": Array [ Object { "name": "Infura Linea Goerli", @@ -351,7 +351,7 @@ describe('NetworkController', () => { "chainId": "0xe705", "defaultRpcEndpointUrl": "https://linea-sepolia.infura.io/v3/{infuraProjectId}", "name": "Linea Sepolia", - "nativeTokenName": "LineaETH", + "nativeCurrency": "LineaETH", "rpcEndpoints": Array [ Object { "name": "Infura Linea Sepolia", @@ -365,7 +365,7 @@ describe('NetworkController', () => { "chainId": "0xe708", "defaultRpcEndpointUrl": "https://linea-mainnet.infura.io/v3/{infuraProjectId}", "name": "Linea Mainnet", - "nativeTokenName": "ETH", + "nativeCurrency": "ETH", "rpcEndpoints": Array [ Object { "name": "Infura Linea Mainnet", @@ -394,7 +394,7 @@ describe('NetworkController', () => { defaultRpcEndpointUrl: 'https://goerli.infura.io/v3/{infuraProjectId}', name: 'Goerli', - nativeTokenName: 'GoerliETH', + nativeCurrency: 'GoerliETH', rpcEndpoints: [ { name: 'Goerli', @@ -421,7 +421,7 @@ describe('NetworkController', () => { "chainId": "0x5", "defaultRpcEndpointUrl": "https://goerli.infura.io/v3/{infuraProjectId}", "name": "Goerli", - "nativeTokenName": "GoerliETH", + "nativeCurrency": "GoerliETH", "rpcEndpoints": Array [ Object { "name": "Goerli", @@ -559,7 +559,7 @@ describe('NetworkController', () => { networkConfigurationsByChainId: { '0x1337': buildCustomNetworkConfiguration({ chainId: '0x1337', - nativeTokenName: 'TEST', + nativeCurrency: 'TEST', rpcEndpoints: [ buildCustomRpcEndpoint({ networkClientId: 'AAAA-AAAA-AAAA-AAAA', @@ -616,7 +616,7 @@ describe('NetworkController', () => { networkConfigurationsByChainId: { '0x1337': buildCustomNetworkConfiguration({ chainId: '0x1337', - nativeTokenName: 'TEST', + nativeCurrency: 'TEST', rpcEndpoints: [ buildCustomRpcEndpoint({ networkClientId: 'AAAA-AAAA-AAAA-AAAA', @@ -679,7 +679,7 @@ describe('NetworkController', () => { networkConfigurationsByChainId: { '0x1337': buildCustomNetworkConfiguration({ chainId: '0x1337', - nativeTokenName: 'TEST', + nativeCurrency: 'TEST', rpcEndpoints: [ buildCustomRpcEndpoint({ networkClientId: 'AAAA-AAAA-AAAA-AAAA', @@ -780,7 +780,7 @@ describe('NetworkController', () => { ), '0x1337': buildCustomNetworkConfiguration({ chainId: '0x1337', - nativeTokenName: 'TEST', + nativeCurrency: 'TEST', rpcEndpoints: [ buildCustomRpcEndpoint({ networkClientId: 'AAAA-AAAA-AAAA-AAAA', @@ -973,7 +973,7 @@ describe('NetworkController', () => { networkConfigurationsByChainId: { '0x1337': buildCustomNetworkConfiguration({ chainId: '0x1337', - nativeTokenName: 'TEST', + nativeCurrency: 'TEST', rpcEndpoints: [ buildCustomRpcEndpoint({ networkClientId: 'AAAA-AAAA-AAAA-AAAA', @@ -1125,7 +1125,7 @@ describe('NetworkController', () => { networkConfigurationsByChainId: { '0x1337': buildCustomNetworkConfiguration({ chainId: '0x1337', - nativeTokenName: 'TOKEN1', + nativeCurrency: 'TOKEN1', rpcEndpoints: [ buildCustomRpcEndpoint({ networkClientId: 'AAAA-AAAA-AAAA-AAAA', @@ -1135,7 +1135,7 @@ describe('NetworkController', () => { }), '0x2448': buildCustomNetworkConfiguration({ chainId: '0x2448', - nativeTokenName: 'TOKEN2', + nativeCurrency: 'TOKEN2', rpcEndpoints: [ buildCustomRpcEndpoint({ networkClientId: 'BBBB-BBBB-BBBB-BBBB', @@ -1229,7 +1229,7 @@ describe('NetworkController', () => { buildInfuraNetworkConfiguration(infuraNetworkType), '0x1337': buildCustomNetworkConfiguration({ chainId: '0x1337', - nativeTokenName: 'TEST', + nativeCurrency: 'TEST', rpcEndpoints: [ buildCustomRpcEndpoint({ networkClientId: 'AAAA-AAAA-AAAA-AAAA', @@ -1321,7 +1321,7 @@ describe('NetworkController', () => { buildInfuraNetworkConfiguration(infuraNetworkType), '0x1337': buildCustomNetworkConfiguration({ chainId: '0x1337', - nativeTokenName: 'TEST', + nativeCurrency: 'TEST', rpcEndpoints: [ buildCustomRpcEndpoint({ networkClientId: 'AAAA-AAAA-AAAA-AAAA', @@ -1420,7 +1420,7 @@ describe('NetworkController', () => { buildInfuraNetworkConfiguration(infuraNetworkType), '0x1337': buildCustomNetworkConfiguration({ chainId: '0x1337', - nativeTokenName: 'TEST', + nativeCurrency: 'TEST', rpcEndpoints: [ buildCustomRpcEndpoint({ networkClientId: 'AAAA-AAAA-AAAA-AAAA', @@ -1544,7 +1544,7 @@ describe('NetworkController', () => { ), '0x1337': buildCustomNetworkConfiguration({ chainId: '0x1337', - nativeTokenName: 'TEST', + nativeCurrency: 'TEST', rpcEndpoints: [ buildCustomRpcEndpoint({ networkClientId: 'AAAA-AAAA-AAAA-AAAA', @@ -1637,7 +1637,7 @@ describe('NetworkController', () => { ), '0x1337': buildCustomNetworkConfiguration({ chainId: '0x1337', - nativeTokenName: 'TEST', + nativeCurrency: 'TEST', rpcEndpoints: [ buildCustomRpcEndpoint({ networkClientId: 'AAAA-AAAA-AAAA-AAAA', @@ -1741,7 +1741,7 @@ describe('NetworkController', () => { ), '0x1337': buildCustomNetworkConfiguration({ chainId: '0x1337', - nativeTokenName: 'TEST', + nativeCurrency: 'TEST', rpcEndpoints: [ buildCustomRpcEndpoint({ networkClientId: 'AAAA-AAAA-AAAA-AAAA', @@ -1834,7 +1834,7 @@ describe('NetworkController', () => { networkConfigurationsByChainId: { '0x1337': buildCustomNetworkConfiguration({ chainId: '0x1337', - nativeTokenName: 'TEST', + nativeCurrency: 'TEST', rpcEndpoints: [ buildCustomRpcEndpoint({ networkClientId: 'AAAA-AAAA-AAAA-AAAA', @@ -2061,7 +2061,7 @@ describe('NetworkController', () => { ), '0x1337': buildCustomNetworkConfiguration({ chainId: '0x1337', - nativeTokenName: 'TEST', + nativeCurrency: 'TEST', rpcEndpoints: [ buildCustomRpcEndpoint({ networkClientId: 'AAAA-AAAA-AAAA-AAAA', @@ -2088,7 +2088,7 @@ describe('NetworkController', () => { ), '0x1337': buildCustomNetworkConfiguration({ chainId: '0x1337', - nativeTokenName: 'TEST', + nativeCurrency: 'TEST', rpcEndpoints: [ buildCustomRpcEndpoint({ networkClientId: 'AAAA-AAAA-AAAA-AAAA', @@ -2123,7 +2123,7 @@ describe('NetworkController', () => { ), '0x1337': buildCustomNetworkConfiguration({ chainId: '0x1337', - nativeTokenName: 'TEST', + nativeCurrency: 'TEST', rpcEndpoints: [ buildCustomRpcEndpoint({ networkClientId: 'AAAA-AAAA-AAAA-AAAA', @@ -2552,7 +2552,7 @@ describe('NetworkController', () => { networkConfigurationsByChainId: { '0x1337': buildCustomNetworkConfiguration({ chainId: '0x1337', - nativeTokenName: 'TEST', + nativeCurrency: 'TEST', rpcEndpoints: [ buildCustomRpcEndpoint({ networkClientId: 'AAAA-AAAA-AAAA-AAAA', @@ -3257,7 +3257,7 @@ describe('NetworkController', () => { chainId: infuraChainId, defaultRpcEndpointUrl: 'https://test.endpoint/2', name: infuraNetworkType, - nativeTokenName: infuraNativeTokenName, + nativeCurrency: infuraNativeTokenName, rpcEndpoints: [ defaultRpcEndpoint, { @@ -3354,7 +3354,7 @@ describe('NetworkController', () => { chainId: infuraChainId, defaultRpcEndpointUrl: 'https://test.endpoint/2', name: 'Some Network', - nativeTokenName: 'TOKEN', + nativeCurrency: 'TOKEN', rpcEndpoints: [ { name: 'Test Network', @@ -3381,7 +3381,7 @@ describe('NetworkController', () => { chainId: infuraChainId, defaultRpcEndpointUrl: 'https://test.endpoint/2', name: 'Some Network', - nativeTokenName: 'TOKEN', + nativeCurrency: 'TOKEN', rpcEndpoints: [ { name: 'Test Network', @@ -3434,7 +3434,7 @@ describe('NetworkController', () => { // eslint-disable-next-line @typescript-eslint/restrict-template-expressions defaultRpcEndpointUrl: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`, name: 'Some Network', - nativeTokenName: 'TOKEN', + nativeCurrency: 'TOKEN', rpcEndpoints: [ { name: infuraNetworkNickname, @@ -3453,7 +3453,7 @@ describe('NetworkController', () => { // eslint-disable-next-line @typescript-eslint/restrict-template-expressions defaultRpcEndpointUrl: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`, name: 'Some Network', - nativeTokenName: 'TOKEN', + nativeCurrency: 'TOKEN', rpcEndpoints: [ { name: infuraNetworkNickname, @@ -3494,7 +3494,7 @@ describe('NetworkController', () => { // eslint-disable-next-line @typescript-eslint/restrict-template-expressions defaultRpcEndpointUrl: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`, name: 'Some Network', - nativeTokenName: 'TOKEN', + nativeCurrency: 'TOKEN', rpcEndpoints: [ { name: infuraNetworkNickname, @@ -3513,7 +3513,7 @@ describe('NetworkController', () => { // eslint-disable-next-line @typescript-eslint/restrict-template-expressions defaultRpcEndpointUrl: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`, name: 'Some Network', - nativeTokenName: 'TOKEN', + nativeCurrency: 'TOKEN', rpcEndpoints: [ { name: infuraNetworkNickname, @@ -3557,7 +3557,7 @@ describe('NetworkController', () => { chainId: '0x1337', defaultRpcEndpointUrl: 'https://test.endpoint/1', name: 'Some Network', - nativeTokenName: 'TOKEN', + nativeCurrency: 'TOKEN', rpcEndpoints: [ defaultRpcEndpoint, { @@ -3586,7 +3586,7 @@ describe('NetworkController', () => { chainId: '0x1337', defaultRpcEndpointUrl: 'https://test.endpoint/1', name: 'Some Network', - nativeTokenName: 'TOKEN', + nativeCurrency: 'TOKEN', rpcEndpoints: [ { name: 'Test Network 1', @@ -3634,7 +3634,7 @@ describe('NetworkController', () => { chainId: '0x1337', defaultRpcEndpointUrl: 'https://test.endpoint/1', name: 'Some Network', - nativeTokenName: 'TOKEN', + nativeCurrency: 'TOKEN', rpcEndpoints: [ { name: 'Test Network 1', @@ -3655,7 +3655,7 @@ describe('NetworkController', () => { chainId: '0x1337', defaultRpcEndpointUrl: 'https://test.endpoint/1', name: 'Some Network', - nativeTokenName: 'TOKEN', + nativeCurrency: 'TOKEN', rpcEndpoints: [ { name: 'Test Network 1', @@ -3688,7 +3688,7 @@ describe('NetworkController', () => { chainId: '0x1337', defaultRpcEndpointUrl: 'https://test.endpoint', name: 'Some Network', - nativeTokenName: 'TOKEN', + nativeCurrency: 'TOKEN', rpcEndpoints: [ { name: 'Test Network', @@ -3702,7 +3702,7 @@ describe('NetworkController', () => { chainId: '0x1337', defaultRpcEndpointUrl: 'https://test.endpoint', name: 'Some Network', - nativeTokenName: 'TOKEN', + nativeCurrency: 'TOKEN', rpcEndpoints: [ { name: 'Test Network', @@ -3729,7 +3729,7 @@ describe('NetworkController', () => { chainId: '0x1337', defaultRpcEndpointUrl: 'https://test.endpoint', name: 'Some Network', - nativeTokenName: 'TOKEN', + nativeCurrency: 'TOKEN', rpcEndpoints: [ { name: 'Test Network', @@ -3743,7 +3743,7 @@ describe('NetworkController', () => { chainId: '0x1337', defaultRpcEndpointUrl: 'https://test.endpoint', name: 'Some Network', - nativeTokenName: 'TOKEN', + nativeCurrency: 'TOKEN', rpcEndpoints: [ { name: 'Test Network', @@ -4980,7 +4980,7 @@ describe('NetworkController', () => { }); const networkConfigurationToUpdate = buildNetworkConfiguration({ chainId: '0x1337', - nativeTokenName: 'TOKEN', + nativeCurrency: 'TOKEN', rpcEndpoints: [rpcEndpoint1], }); @@ -5395,7 +5395,7 @@ describe('NetworkController', () => { const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ chainId: '0x1337', defaultRpcEndpointUrl: 'https://test.endpoint/1', - nativeTokenName: 'TOKEN', + nativeCurrency: 'TOKEN', rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], }); @@ -5821,7 +5821,7 @@ describe('NetworkController', () => { const networkConfigurationToUpdate = buildNetworkConfiguration({ chainId: '0x1337', - nativeTokenName: 'TOKEN', + nativeCurrency: 'TOKEN', rpcEndpoints: [ buildCustomRpcEndpoint({ name: 'Test Network 1', @@ -6182,7 +6182,7 @@ describe('NetworkController', () => { ]; const networkConfigurationToUpdate = buildInfuraNetworkConfiguration(infuraNetworkType, { - nativeTokenName: 'ETH', + nativeCurrency: 'ETH', rpcEndpoints: [ defaultRpcEndpoint, customRpcEndpoint1, @@ -6550,7 +6550,7 @@ describe('NetworkController', () => { ]; const networkConfigurationToUpdate = buildInfuraNetworkConfiguration(infuraNetworkType, { - nativeTokenName: 'ETH', + nativeCurrency: 'ETH', rpcEndpoints: [ defaultRpcEndpoint, customRpcEndpoint1, @@ -6874,7 +6874,7 @@ describe('NetworkController', () => { const networkConfigurationToUpdate = buildNetworkConfiguration({ chainId: '0x1337', - nativeTokenName: 'TOKEN', + nativeCurrency: 'TOKEN', rpcEndpoints: [ buildCustomRpcEndpoint({ name: 'Test Network 1', @@ -7433,7 +7433,7 @@ describe('NetworkController', () => { networkConfigurationsByChainId: { '0x1337': buildCustomNetworkConfiguration({ chainId: '0x1337', - nativeTokenName: 'TEST', + nativeCurrency: 'TEST', rpcEndpoints: [ buildCustomRpcEndpoint({ networkClientId: 'AAAA-AAAA-AAAA-AAAA', @@ -7554,7 +7554,7 @@ describe('NetworkController', () => { buildInfuraNetworkConfiguration(infuraNetworkType), '0x1337': buildCustomNetworkConfiguration({ chainId: '0x1337', - nativeTokenName: 'TEST', + nativeCurrency: 'TEST', rpcEndpoints: [ buildCustomRpcEndpoint({ networkClientId: 'AAAA-AAAA-AAAA-AAAA', @@ -7614,7 +7614,7 @@ describe('NetworkController', () => { buildInfuraNetworkConfiguration(infuraNetworkType), '0x1337': buildCustomNetworkConfiguration({ chainId: '0x1337', - nativeTokenName: 'TEST', + nativeCurrency: 'TEST', rpcEndpoints: [ buildCustomRpcEndpoint({ networkClientId: 'AAAA-AAAA-AAAA-AAAA', @@ -7699,7 +7699,7 @@ describe('NetworkController', () => { buildInfuraNetworkConfiguration(infuraNetworkType), '0x1337': buildCustomNetworkConfiguration({ chainId: '0x1337', - nativeTokenName: 'TEST', + nativeCurrency: 'TEST', rpcEndpoints: [ buildCustomRpcEndpoint({ networkClientId: 'AAAA-AAAA-AAAA-AAAA', @@ -7776,7 +7776,7 @@ describe('NetworkController', () => { buildInfuraNetworkConfiguration(infuraNetworkType), '0x1337': buildCustomNetworkConfiguration({ chainId: '0x1337', - nativeTokenName: 'TEST', + nativeCurrency: 'TEST', rpcEndpoints: [ buildCustomRpcEndpoint({ networkClientId: 'AAAA-AAAA-AAAA-AAAA', @@ -7837,7 +7837,7 @@ describe('NetworkController', () => { buildInfuraNetworkConfiguration(infuraNetworkType), '0x1337': buildCustomNetworkConfiguration({ chainId: '0x1337', - nativeTokenName: 'TEST', + nativeCurrency: 'TEST', rpcEndpoints: [ buildCustomRpcEndpoint({ networkClientId: 'AAAA-AAAA-AAAA-AAAA', @@ -7913,7 +7913,7 @@ describe('NetworkController', () => { buildInfuraNetworkConfiguration(infuraNetworkType), '0x1337': buildCustomNetworkConfiguration({ chainId: '0x1337', - nativeTokenName: 'TEST', + nativeCurrency: 'TEST', rpcEndpoints: [ buildCustomRpcEndpoint({ networkClientId: 'AAAA-AAAA-AAAA-AAAA', @@ -7995,7 +7995,7 @@ describe('NetworkController', () => { buildInfuraNetworkConfiguration(infuraNetworkType), '0x1337': buildCustomNetworkConfiguration({ chainId: '0x1337', - nativeTokenName: 'TEST', + nativeCurrency: 'TEST', rpcEndpoints: [ buildCustomRpcEndpoint({ networkClientId: 'AAAA-AAAA-AAAA-AAAA', @@ -8082,7 +8082,7 @@ describe('NetworkController', () => { networkConfigurationsByChainId: { '0x1337': buildCustomNetworkConfiguration({ chainId: '0x1337', - nativeTokenName: 'TEST', + nativeCurrency: 'TEST', rpcEndpoints: [ buildCustomRpcEndpoint({ networkClientId: 'AAAA-AAAA-AAAA-AAAA', @@ -8127,7 +8127,7 @@ describe('NetworkController', () => { networkConfigurationsByChainId: { '0x1337': buildCustomNetworkConfiguration({ chainId: '0x1337', - nativeTokenName: 'TEST', + nativeCurrency: 'TEST', rpcEndpoints: [ buildCustomRpcEndpoint({ networkClientId: 'AAAA-AAAA-AAAA-AAAA', @@ -8174,7 +8174,7 @@ describe('NetworkController', () => { networkConfigurationsByChainId: { '0x1337': buildCustomNetworkConfiguration({ chainId: '0x1337', - nativeTokenName: 'TEST', + nativeCurrency: 'TEST', rpcEndpoints: [ buildCustomRpcEndpoint({ networkClientId: 'AAAA-AAAA-AAAA-AAAA', @@ -8234,7 +8234,7 @@ describe('NetworkController', () => { networkConfigurationsByChainId: { '0x1337': buildCustomNetworkConfiguration({ chainId: '0x1337', - nativeTokenName: 'TEST', + nativeCurrency: 'TEST', rpcEndpoints: [ buildCustomRpcEndpoint({ networkClientId: 'AAAA-AAAA-AAAA-AAAA', @@ -8325,7 +8325,7 @@ describe('NetworkController', () => { networkConfigurationsByChainId: { '0x1337': buildCustomNetworkConfiguration({ chainId: '0x1337', - nativeTokenName: 'TEST', + nativeCurrency: 'TEST', rpcEndpoints: [ buildCustomRpcEndpoint({ networkClientId: 'AAAA-AAAA-AAAA-AAAA', @@ -8403,7 +8403,7 @@ describe('NetworkController', () => { networkConfigurationsByChainId: { '0x1337': buildCustomNetworkConfiguration({ chainId: '0x1337', - nativeTokenName: 'TEST', + nativeCurrency: 'TEST', rpcEndpoints: [ buildCustomRpcEndpoint({ networkClientId: 'AAAA-AAAA-AAAA-AAAA', @@ -8465,7 +8465,7 @@ describe('NetworkController', () => { networkConfigurationsByChainId: { '0x1337': buildCustomNetworkConfiguration({ chainId: '0x1337', - nativeTokenName: 'TEST', + nativeCurrency: 'TEST', rpcEndpoints: [ buildCustomRpcEndpoint({ networkClientId: 'AAAA-AAAA-AAAA-AAAA', @@ -8527,7 +8527,7 @@ describe('NetworkController', () => { networkConfigurationsByChainId: { '0x1337': buildCustomNetworkConfiguration({ chainId: '0x1337', - nativeTokenName: 'TEST', + nativeCurrency: 'TEST', rpcEndpoints: [ buildCustomRpcEndpoint({ networkClientId: 'AAAA-AAAA-AAAA-AAAA', @@ -8605,7 +8605,7 @@ describe('NetworkController', () => { networkConfigurationsByChainId: { '0x1337': buildCustomNetworkConfiguration({ chainId: '0x1337', - nativeTokenName: 'TEST', + nativeCurrency: 'TEST', rpcEndpoints: [ buildCustomRpcEndpoint({ networkClientId: 'AAAA-AAAA-AAAA-AAAA', @@ -8690,7 +8690,7 @@ describe('NetworkController', () => { chainId: '0x1337' as const, defaultRpcEndpointUrl: 'https://test.network/1', name: 'Test Network 1', - nativeTokenName: 'TOKEN1', + nativeCurrency: 'TOKEN1', rpcEndpoints: [ { name: 'Test Endpoint', @@ -8710,7 +8710,7 @@ describe('NetworkController', () => { chainId: '0x2448' as const, defaultRpcEndpointUrl: 'https://test.network/2', name: 'Test Network 2', - nativeTokenName: 'TOKEN2', + nativeCurrency: 'TOKEN2', rpcEndpoints: [ { name: 'Test Endpoint', @@ -8729,7 +8729,7 @@ describe('NetworkController', () => { chainId: '0x1337' as const, defaultRpcEndpointUrl: 'https://test.network/1', name: 'Test Network 1', - nativeTokenName: 'TOKEN1', + nativeCurrency: 'TOKEN1', rpcEndpoints: [ { name: 'Test Endpoint', @@ -8743,7 +8743,7 @@ describe('NetworkController', () => { chainId: '0x2448' as const, defaultRpcEndpointUrl: 'https://test.network/2', name: 'Test Network 2', - nativeTokenName: 'TOKEN2', + nativeCurrency: 'TOKEN2', rpcEndpoints: [ { name: 'Test Endpoint', @@ -8989,7 +8989,7 @@ function refreshNetworkTests({ initializationNetworkClientConfiguration = { chainId: matchingNetworkConfiguration.chainId, rpcUrl: matchingRpcEndpoint.url, - ticker: matchingNetworkConfiguration.nativeTokenName, + ticker: matchingNetworkConfiguration.nativeCurrency, type: NetworkClientType.Custom, }; } diff --git a/packages/network-controller/tests/helpers.ts b/packages/network-controller/tests/helpers.ts index a3a7d9a2f47..bf5de4ca3ab 100644 --- a/packages/network-controller/tests/helpers.ts +++ b/packages/network-controller/tests/helpers.ts @@ -202,7 +202,7 @@ export function buildNetworkConfiguration( // @ts-expect-error We will make sure that this property is set below. defaultRpcEndpointUrl: () => undefined, name: () => 'Some Network', - nativeTokenName: () => 'TOKEN', + nativeCurrency: () => 'TOKEN', rpcEndpoints: () => [ defaultRpcEndpointType === RpcEndpointType.Infura ? buildInfuraRpcEndpoint(InfuraNetworkType['linea-goerli']) @@ -243,7 +243,7 @@ export function buildCustomNetworkConfiguration( // @ts-expect-error We will make sure that this property is set below. defaultRpcEndpointUrl: () => undefined, name: () => 'Some Network', - nativeTokenName: () => 'TOKEN', + nativeCurrency: () => 'TOKEN', rpcEndpoints: () => [ buildCustomRpcEndpoint({ url: 'https://test.endpoint', @@ -288,7 +288,7 @@ export function buildInfuraNetworkConfiguration( // @ts-expect-error We will make sure that this property is set below. defaultRpcEndpointUrl: () => undefined, name: () => NetworkNickname[infuraNetworkType], - nativeTokenName: () => NetworksTicker[infuraNetworkType], + nativeCurrency: () => NetworksTicker[infuraNetworkType], rpcEndpoints: () => [defaultRpcEndpoint], }, overrides, @@ -364,7 +364,7 @@ export function buildAddNetworkFields( // @ts-expect-error We will make sure that this property is set below. defaultRpcEndpointUrl: () => undefined, name: () => 'Some Network', - nativeTokenName: () => 'TOKEN', + nativeCurrency: () => 'TOKEN', rpcEndpoints: () => [ buildAddNetworkCustomRpcEndpointFields({ url: 'https://test.endpoint', From 467b70284df9a53330f0e9ee9e6a94e8091b2242 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Mon, 24 Jun 2024 16:01:48 -0600 Subject: [PATCH 13/49] Tweak controller-utils CHANGELOG Co-authored-by: Jongsun Suh --- packages/controller-utils/CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/controller-utils/CHANGELOG.md b/packages/controller-utils/CHANGELOG.md index 23a46e3a868..95a280ec6f7 100644 --- a/packages/controller-utils/CHANGELOG.md +++ b/packages/controller-utils/CHANGELOG.md @@ -9,9 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Add `BlockExplorerUrl` for looking up the block explorer URL for any Infura network ([#4268](https://github.com/MetaMask/core/pull/4286)) -- Add `NetworkNickname` for looking up the common nickname for any Infura network ([#4268](https://github.com/MetaMask/core/pull/4286)) -- Add `Partialize` for making select keys in an object type optional ([#4268](https://github.com/MetaMask/core/pull/4286)) +- Add `BlockExplorerUrl` object and type for looking up the block explorer URL of any Infura network ([#4268](https://github.com/MetaMask/core/pull/4286)) +- Add `NetworkNickname` object and type for looking up the common nickname for any Infura network ([#4268](https://github.com/MetaMask/core/pull/4286)) +- Add `Partialize` type for making select keys in an object type optional ([#4268](https://github.com/MetaMask/core/pull/4286)) - `toHex` now supports converting a `bigint` into a hex string ([#4268](https://github.com/MetaMask/core/pull/4286)) ## [11.0.0] From 9d419df718ac68228962318ae22b704c7458cc28 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Tue, 25 Jun 2024 14:41:43 -0600 Subject: [PATCH 14/49] Use the type parameter for `reduce` Co-authored-by: Jongsun Suh --- tests/helpers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/helpers.ts b/tests/helpers.ts index e3d8731fbd2..8267f1b7c8e 100644 --- a/tests/helpers.ts +++ b/tests/helpers.ts @@ -75,14 +75,14 @@ export function buildTestObject>( ...getKnownPropertyNames(overrides), ]), ]; - const object = keys.reduce((workingObject: Partial, key) => { + const object = keys.reduce((workingObject, key) => { if (key in overrides) { return { ...workingObject, [key]: overrides[key] }; } else if (key in defaults) { return { ...workingObject, [key]: defaults[key]() }; } return workingObject; - }, {}) as unknown as Type; + }, {} as never); return finalizeObject ? finalizeObject(object) : object; } From 00e0282fc658ddfb338b569854c5b52ee636ca7d Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Mon, 1 Jul 2024 09:29:50 -0600 Subject: [PATCH 15/49] Export RpcEndpointType Co-authored-by: Brian Bergeron --- packages/network-controller/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/network-controller/src/index.ts b/packages/network-controller/src/index.ts index 7f0892249f4..6ad80870f1f 100644 --- a/packages/network-controller/src/index.ts +++ b/packages/network-controller/src/index.ts @@ -32,6 +32,7 @@ export { getDefaultNetworkControllerState, knownKeysOf, NetworkController, + RpcEndpointType, } from './NetworkController'; export * from './constants'; export type { BlockTracker, Provider } from './types'; From 4be6a344dfc1b09b9e3a864eb7fd9c7810c800a6 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Mon, 1 Jul 2024 09:31:50 -0600 Subject: [PATCH 16/49] Add changelog entry for RpcEndpointType export --- packages/network-controller/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/network-controller/CHANGELOG.md b/packages/network-controller/CHANGELOG.md index 54af6e8e161..2a34db617f5 100644 --- a/packages/network-controller/CHANGELOG.md +++ b/packages/network-controller/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `updateNetwork`, which replaces one half of `upsertNetworkConfiguration` and can be used to recreate the network clients for an existing chain based on an updated configuration ([#4268](https://github.com/MetaMask/core/pull/4286)) - Add `removeNetwork`, which replaces `removeNetworkConfiguration` and can be used to remove existing network clients for a chain ([#4268](https://github.com/MetaMask/core/pull/4286)) - Add `getDefaultNetworkControllerState` function, which replaces `defaultState` and matches patterns in other controllers ([#4268](https://github.com/MetaMask/core/pull/4286)) +- Add `RpcEndpointType` type ([#4268](https://github.com/MetaMask/core/pull/4286)) ### Changed From e2b5c23c397348dd481256c680db0319446ce8dc Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Tue, 2 Jul 2024 15:15:52 -0600 Subject: [PATCH 17/49] Rename defaultRpcEndpointUrl to defaultRpcEndpointIndex --- packages/network-controller/CHANGELOG.md | 4 +- .../src/NetworkController.ts | 40 ++--- .../tests/NetworkController.test.ts | 151 ++++++++---------- packages/network-controller/tests/helpers.ts | 24 +-- 4 files changed, 101 insertions(+), 118 deletions(-) diff --git a/packages/network-controller/CHANGELOG.md b/packages/network-controller/CHANGELOG.md index 2a34db617f5..4555d95278c 100644 --- a/packages/network-controller/CHANGELOG.md +++ b/packages/network-controller/CHANGELOG.md @@ -30,14 +30,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `nickname` has been renamed to `name`. - `blockExplorerUrl` has been pulled out of `rpcPrefs`. - `rpcEndpoints` has been added as well. This is an an array of objects, where each object has properties `name`, `networkClientId` (optional), `type`, and `url`. - - `defaultRpcEndpointUrl` has been added. This must point to an entry in `rpcEndpoints`. + - `defaultRpcEndpointIndex` has been added. This must point to an entry in `rpcEndpoints`. - `id` has been removed. Previously, this represented the ID of the network client associated with the network configuration. Since network clients are now created from RPC endpoints, the equivalent to this is the `networkClientId` property on an `RpcEndpoint`. - **BREAKING:** The network controller messenger must now allow the action `NetworkController:getNetworkConfigurationByChainId` ([#4268](https://github.com/MetaMask/core/pull/4286)) - **BREAKING:** The network controller messenger must now allow the event `NetworkController:networkAdded` ([#4268](https://github.com/MetaMask/core/pull/4286)) - **BREAKING:** The `NetworkController` constructor will now throw if the initial state provided is invalid ([#4268](https://github.com/MetaMask/core/pull/4286)) - `networkConfigurationsByChainId` cannot be empty. - The `chainId` of a network configuration in `networkConfigurationsByChainId` must match the chain ID it is filed under. - - The `defaultRpcEndpointUrl` of a network configuration in `networkConfigurationsByChainId` must match an entry in its `rpcEndpoints`. + - The `defaultRpcEndpointIndex` of a network configuration in `networkConfigurationsByChainId` must point to an entry in its `rpcEndpoints`. - `selectedNetworkClientId` must match the `networkClientId` of an RPC endpoint in `networkConfigurationsByChainId`. - **BREAKING:** Update `getNetworkConfigurationByNetworkClientId` so that when given an Infura network name (that is, a value from `InfuraNetworkType`), it will return a masked version of the RPC endpoint URL for the associated Infura network ([#4268](https://github.com/MetaMask/core/pull/4286)) - If you want the unmasked version, you'll need the `url` property from the network _client_ configuration, which you can get by calling `getNetworkClientById` and then accessing the `configuration` property off of the network client. diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index bb9e6342ab3..5443561a779 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -177,11 +177,11 @@ export type NetworkConfiguration = { */ chainId: Hex; /** - * The RPC endpoint URL that all requests will use by default in order to - * interact with the chain. The value of this property must match the `url` - * property of an entry in `rpcEndpoints`. + * A reference to an RPC endpoint that all requests will use by default in order to + * interact with the chain. This index must refer to an item in + * `rpcEndpoints`. */ - defaultRpcEndpointUrl: string; + defaultRpcEndpointIndex: number; /** * The user-facing nickname assigned to the chain. */ @@ -512,7 +512,7 @@ function getDefaultNetworkConfigurationsByChainId(): Record< const networkConfiguration: NetworkConfiguration = { chainId, - defaultRpcEndpointUrl: rpcEndpointUrl, + defaultRpcEndpointIndex: 0, name: NetworkNickname[infuraNetworkType], nativeCurrency: NetworksTicker[infuraNetworkType], rpcEndpoints: [ @@ -617,7 +617,7 @@ function deriveInfuraNetworkNameFromRpcEndpointUrl( * errors. * * In the case of NetworkController, there are several parts of state that need - * to match. For instance, `defaultRpcEndpointUrl` needs to match an entry + * to match. For instance, `defaultRpcEndpointIndex` needs to match an entry * within `rpcEndpoints`, and `selectedNetworkClientId` needs to point to an RPC * endpoint within a network configuration. * @@ -650,13 +650,12 @@ function validateNetworkControllerState(state: NetworkState) { } if ( - !networkConfiguration.rpcEndpoints.some( - (rpcEndpoint) => - rpcEndpoint.url === networkConfiguration.defaultRpcEndpointUrl, - ) + networkConfiguration.rpcEndpoints[ + networkConfiguration.defaultRpcEndpointIndex + ] === undefined ) { throw new Error( - `NetworkController state has invalid \`networkConfigurationsByChainId\`: Network configuration '${networkConfiguration.name}' has a \`defaultRpcEndpointUrl\` that does not match an entry in \`rpcEndpoints\``, + `NetworkController state has invalid \`networkConfigurationsByChainId\`: Network configuration '${networkConfiguration.name}' has a \`defaultRpcEndpointIndex\` that does not refer to an entry in \`rpcEndpoints\``, ); } } @@ -1366,7 +1365,7 @@ export class NetworkController extends BaseController< const { blockExplorerUrl, chainId, - defaultRpcEndpointUrl, + defaultRpcEndpointIndex, nativeCurrency, rpcEndpoints: setOfRpcEndpointFields, } = fields; @@ -1490,11 +1489,9 @@ export class NetworkController extends BaseController< } } - if (!rpcEndpointUrls.includes(defaultRpcEndpointUrl)) { + if (setOfRpcEndpointFields[defaultRpcEndpointIndex] === undefined) { throw new Error( - `Cannot add network: \`defaultRpcEndpointUrl\` '${defaultRpcEndpointUrl}' must match an entry in \`rpcEndpoints\` (${inspect( - rpcEndpointUrls, - )})`, + `Cannot add network: \`defaultRpcEndpointIndex\` must refer to an entry in \`rpcEndpoints\``, ); } @@ -1618,13 +1615,10 @@ export class NetworkController extends BaseController< const { blockExplorerUrl: newBlockExplorerUrl, chainId: newChainId, - defaultRpcEndpointUrl: newInfuraRpcEndpointUrl, + defaultRpcEndpointIndex: newDefaultRpcEndpointIndex, nativeCurrency: newNativeTokenName, rpcEndpoints: setOfNewRpcEndpointFields, } = fields; - const newRpcEndpointUrls = setOfNewRpcEndpointFields.map( - (newRpcEndpointFields) => newRpcEndpointFields.url, - ); const infuraRpcEndpoints = setOfNewRpcEndpointFields.filter( (newRpcEndpointFields): newRpcEndpointFields is InfuraRpcEndpoint => newRpcEndpointFields.type === RpcEndpointType.Infura, @@ -1769,11 +1763,9 @@ export class NetworkController extends BaseController< } } - if (!newRpcEndpointUrls.includes(newInfuraRpcEndpointUrl)) { + if (setOfNewRpcEndpointFields[newDefaultRpcEndpointIndex] === undefined) { throw new Error( - `Cannot update network: \`defaultRpcEndpointUrl\` '${newInfuraRpcEndpointUrl}' must match an entry in \`rpcEndpoints\` (${inspect( - newRpcEndpointUrls, - )})`, + `Cannot update network: \`defaultRpcEndpointIndex\` must refer to an entry in \`rpcEndpoints\``, ); } diff --git a/packages/network-controller/tests/NetworkController.test.ts b/packages/network-controller/tests/NetworkController.test.ts index 25f462fc18b..528263e3c3b 100644 --- a/packages/network-controller/tests/NetworkController.test.ts +++ b/packages/network-controller/tests/NetworkController.test.ts @@ -177,7 +177,7 @@ describe('NetworkController', () => { ); }); - it('throws if a network configuration has an invalid defaultRpcEndpointUrl', () => { + it('throws if a network configuration has an invalid defaultRpcEndpointIndex', () => { const messenger = buildMessenger(); const restrictedMessenger = buildNetworkControllerMessenger(messenger); expect( @@ -189,10 +189,10 @@ describe('NetworkController', () => { '0x1337': buildCustomNetworkConfiguration({ chainId: '0x1337', name: 'Test Network', - defaultRpcEndpointUrl: 'https://some.endpoint', + defaultRpcEndpointIndex: 99999, rpcEndpoints: [ buildCustomRpcEndpoint({ - url: 'https://different.endpoint', + url: 'https://some.endpoint', }), ], }), @@ -201,7 +201,7 @@ describe('NetworkController', () => { infuraProjectId: 'infura-project-id', }), ).toThrow( - "NetworkController state has invalid `networkConfigurationsByChainId`: Network configuration 'Test Network' has a `defaultRpcEndpointUrl` that does not match an entry in `rpcEndpoints`", + "NetworkController state has invalid `networkConfigurationsByChainId`: Network configuration 'Test Network' has a `defaultRpcEndpointIndex` that does not refer to an entry in `rpcEndpoints`", ); }); @@ -291,7 +291,7 @@ describe('NetworkController', () => { "networkConfigurationsByChainId": Object { "0x1": Object { "chainId": "0x1", - "defaultRpcEndpointUrl": "https://mainnet.infura.io/v3/{infuraProjectId}", + "defaultRpcEndpointIndex": 0, "name": "Mainnet", "nativeCurrency": "ETH", "rpcEndpoints": Array [ @@ -305,7 +305,7 @@ describe('NetworkController', () => { }, "0x5": Object { "chainId": "0x5", - "defaultRpcEndpointUrl": "https://goerli.infura.io/v3/{infuraProjectId}", + "defaultRpcEndpointIndex": 0, "name": "Goerli", "nativeCurrency": "GoerliETH", "rpcEndpoints": Array [ @@ -319,7 +319,7 @@ describe('NetworkController', () => { }, "0xaa36a7": Object { "chainId": "0xaa36a7", - "defaultRpcEndpointUrl": "https://sepolia.infura.io/v3/{infuraProjectId}", + "defaultRpcEndpointIndex": 0, "name": "Sepolia", "nativeCurrency": "SepoliaETH", "rpcEndpoints": Array [ @@ -333,7 +333,7 @@ describe('NetworkController', () => { }, "0xe704": Object { "chainId": "0xe704", - "defaultRpcEndpointUrl": "https://linea-goerli.infura.io/v3/{infuraProjectId}", + "defaultRpcEndpointIndex": 0, "name": "Linea Goerli", "nativeCurrency": "LineaETH", "rpcEndpoints": Array [ @@ -347,7 +347,7 @@ describe('NetworkController', () => { }, "0xe705": Object { "chainId": "0xe705", - "defaultRpcEndpointUrl": "https://linea-sepolia.infura.io/v3/{infuraProjectId}", + "defaultRpcEndpointIndex": 0, "name": "Linea Sepolia", "nativeCurrency": "LineaETH", "rpcEndpoints": Array [ @@ -361,7 +361,7 @@ describe('NetworkController', () => { }, "0xe708": Object { "chainId": "0xe708", - "defaultRpcEndpointUrl": "https://linea-mainnet.infura.io/v3/{infuraProjectId}", + "defaultRpcEndpointIndex": 0, "name": "Linea Mainnet", "nativeCurrency": "ETH", "rpcEndpoints": Array [ @@ -389,8 +389,7 @@ describe('NetworkController', () => { networkConfigurationsByChainId: { [ChainId.goerli]: { chainId: ChainId.goerli, - defaultRpcEndpointUrl: - 'https://goerli.infura.io/v3/{infuraProjectId}', + defaultRpcEndpointIndex: 0, name: 'Goerli', nativeCurrency: 'GoerliETH', rpcEndpoints: [ @@ -417,7 +416,7 @@ describe('NetworkController', () => { "networkConfigurationsByChainId": Object { "0x5": Object { "chainId": "0x5", - "defaultRpcEndpointUrl": "https://goerli.infura.io/v3/{infuraProjectId}", + "defaultRpcEndpointIndex": 0, "name": "Goerli", "nativeCurrency": "GoerliETH", "rpcEndpoints": Array [ @@ -3127,12 +3126,12 @@ describe('NetworkController', () => { ); }); - it('throws if defaultRpcEndpointUrl does not refer to an entry in rpcEndpoints', async () => { + it('throws if defaultRpcEndpointIndex does not refer to an entry in rpcEndpoints', async () => { await withController(({ controller }) => { expect(() => controller.addNetwork( buildAddNetworkFields({ - defaultRpcEndpointUrl: 'https://non-existent.com', + defaultRpcEndpointIndex: 99999, rpcEndpoints: [ buildUpdateNetworkCustomRpcEndpointFields({ url: 'https://foo.com', @@ -3145,7 +3144,7 @@ describe('NetworkController', () => { ), ).toThrow( new Error( - "Cannot add network: `defaultRpcEndpointUrl` 'https://non-existent.com' must match an entry in `rpcEndpoints` ([ 'https://foo.com', 'https://bar.com' ])", + 'Cannot add network: `defaultRpcEndpointIndex` must refer to an entry in `rpcEndpoints`', ), ); }); @@ -3253,7 +3252,7 @@ describe('NetworkController', () => { controller.addNetwork({ chainId: infuraChainId, - defaultRpcEndpointUrl: 'https://test.endpoint/2', + defaultRpcEndpointIndex: 1, name: infuraNetworkType, nativeCurrency: infuraNativeTokenName, rpcEndpoints: [ @@ -3350,7 +3349,7 @@ describe('NetworkController', () => { ({ controller }) => { controller.addNetwork({ chainId: infuraChainId, - defaultRpcEndpointUrl: 'https://test.endpoint/2', + defaultRpcEndpointIndex: 0, name: 'Some Network', nativeCurrency: 'TOKEN', rpcEndpoints: [ @@ -3377,7 +3376,7 @@ describe('NetworkController', () => { controller.state.networkConfigurationsByChainId[infuraChainId], ).toStrictEqual({ chainId: infuraChainId, - defaultRpcEndpointUrl: 'https://test.endpoint/2', + defaultRpcEndpointIndex: 0, name: 'Some Network', nativeCurrency: 'TOKEN', rpcEndpoints: [ @@ -3428,9 +3427,7 @@ describe('NetworkController', () => { controller.addNetwork({ chainId: infuraChainId, - // False negative - this is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - defaultRpcEndpointUrl: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`, + defaultRpcEndpointIndex: 0, name: 'Some Network', nativeCurrency: 'TOKEN', rpcEndpoints: [ @@ -3447,9 +3444,7 @@ describe('NetworkController', () => { expect(networkAddedEventListener).toHaveBeenCalledWith({ chainId: infuraChainId, - // False negative - this is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - defaultRpcEndpointUrl: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`, + defaultRpcEndpointIndex: 0, name: 'Some Network', nativeCurrency: 'TOKEN', rpcEndpoints: [ @@ -3488,9 +3483,7 @@ describe('NetworkController', () => { ({ controller }) => { const newNetworkConfiguration = controller.addNetwork({ chainId: infuraChainId, - // False negative - this is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - defaultRpcEndpointUrl: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`, + defaultRpcEndpointIndex: 0, name: 'Some Network', nativeCurrency: 'TOKEN', rpcEndpoints: [ @@ -3507,9 +3500,7 @@ describe('NetworkController', () => { expect(newNetworkConfiguration).toStrictEqual({ chainId: infuraChainId, - // False negative - this is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - defaultRpcEndpointUrl: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`, + defaultRpcEndpointIndex: 0, name: 'Some Network', nativeCurrency: 'TOKEN', rpcEndpoints: [ @@ -3553,7 +3544,7 @@ describe('NetworkController', () => { expect(() => controller.addNetwork({ chainId: '0x1337', - defaultRpcEndpointUrl: 'https://test.endpoint/1', + defaultRpcEndpointIndex: 0, name: 'Some Network', nativeCurrency: 'TOKEN', rpcEndpoints: [ @@ -3582,7 +3573,7 @@ describe('NetworkController', () => { await withController(({ controller }) => { controller.addNetwork({ chainId: '0x1337', - defaultRpcEndpointUrl: 'https://test.endpoint/1', + defaultRpcEndpointIndex: 0, name: 'Some Network', nativeCurrency: 'TOKEN', rpcEndpoints: [ @@ -3630,7 +3621,7 @@ describe('NetworkController', () => { await withController(({ controller }) => { controller.addNetwork({ chainId: '0x1337', - defaultRpcEndpointUrl: 'https://test.endpoint/1', + defaultRpcEndpointIndex: 0, name: 'Some Network', nativeCurrency: 'TOKEN', rpcEndpoints: [ @@ -3651,7 +3642,7 @@ describe('NetworkController', () => { controller.state.networkConfigurationsByChainId['0x1337'], ).toStrictEqual({ chainId: '0x1337', - defaultRpcEndpointUrl: 'https://test.endpoint/1', + defaultRpcEndpointIndex: 0, name: 'Some Network', nativeCurrency: 'TOKEN', rpcEndpoints: [ @@ -3684,7 +3675,7 @@ describe('NetworkController', () => { controller.addNetwork({ chainId: '0x1337', - defaultRpcEndpointUrl: 'https://test.endpoint', + defaultRpcEndpointIndex: 0, name: 'Some Network', nativeCurrency: 'TOKEN', rpcEndpoints: [ @@ -3698,7 +3689,7 @@ describe('NetworkController', () => { expect(networkAddedEventListener).toHaveBeenCalledWith({ chainId: '0x1337', - defaultRpcEndpointUrl: 'https://test.endpoint', + defaultRpcEndpointIndex: 0, name: 'Some Network', nativeCurrency: 'TOKEN', rpcEndpoints: [ @@ -3725,7 +3716,7 @@ describe('NetworkController', () => { const newNetworkConfiguration = controller.addNetwork({ chainId: '0x1337', - defaultRpcEndpointUrl: 'https://test.endpoint', + defaultRpcEndpointIndex: 0, name: 'Some Network', nativeCurrency: 'TOKEN', rpcEndpoints: [ @@ -3739,7 +3730,7 @@ describe('NetworkController', () => { expect(newNetworkConfiguration).toStrictEqual({ chainId: '0x1337', - defaultRpcEndpointUrl: 'https://test.endpoint', + defaultRpcEndpointIndex: 0, name: 'Some Network', nativeCurrency: 'TOKEN', rpcEndpoints: [ @@ -4039,7 +4030,7 @@ describe('NetworkController', () => { expect(() => controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, - defaultRpcEndpointUrl: 'https://foo.com/bar', + defaultRpcEndpointIndex: 0, rpcEndpoints: [ buildUpdateNetworkCustomRpcEndpointFields({ url: 'https://foo.com/bar', @@ -4161,7 +4152,7 @@ describe('NetworkController', () => { expect(() => controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, - defaultRpcEndpointUrl: rpcEndpoint.url, + defaultRpcEndpointIndex: 0, rpcEndpoints: [rpcEndpoint, rpcEndpoint], }), ).toThrow( @@ -4250,7 +4241,7 @@ describe('NetworkController', () => { ); }); - it('throws if the new defaultRpcEndpointUrl does not refer to an entry in rpcEndpoints', async () => { + it('throws if the new defaultRpcEndpointIndex does not refer to an entry in rpcEndpoints', async () => { const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ chainId: '0x1337', rpcEndpoints: [ @@ -4275,11 +4266,11 @@ describe('NetworkController', () => { expect(() => controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, - defaultRpcEndpointUrl: 'https://non-existent.com', + defaultRpcEndpointIndex: 99999, }), ).toThrow( new Error( - "Cannot update network: `defaultRpcEndpointUrl` 'https://non-existent.com' must match an entry in `rpcEndpoints` ([ 'https://foo.com', 'https://bar.com' ])", + 'Cannot update network: `defaultRpcEndpointIndex` must refer to an entry in `rpcEndpoints`', ), ); }, @@ -4331,7 +4322,7 @@ describe('NetworkController', () => { ]; controller.updateNetwork(infuraChainId, { ...networkConfigurationToUpdate, - defaultRpcEndpointUrl: rpcEndpoint1.url, + defaultRpcEndpointIndex: 0, rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], }); @@ -4403,7 +4394,7 @@ describe('NetworkController', () => { ({ controller }) => { controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, - defaultRpcEndpointUrl: rpcEndpoint1.url, + defaultRpcEndpointIndex: 0, rpcEndpoints: [ rpcEndpoint1, buildUpdateNetworkCustomRpcEndpointFields({ @@ -4471,7 +4462,7 @@ describe('NetworkController', () => { '0x1337', { ...networkConfigurationToUpdate, - defaultRpcEndpointUrl: rpcEndpoint1.url, + defaultRpcEndpointIndex: 0, rpcEndpoints: [ rpcEndpoint1, buildUpdateNetworkCustomRpcEndpointFields({ @@ -4548,7 +4539,7 @@ describe('NetworkController', () => { controller.updateNetwork(infuraChainId, { ...networkConfigurationToUpdate, - defaultRpcEndpointUrl: rpcEndpoint2.url, + defaultRpcEndpointIndex: 0, rpcEndpoints: [rpcEndpoint2], }); @@ -4594,7 +4585,7 @@ describe('NetworkController', () => { ({ controller }) => { controller.updateNetwork(infuraChainId, { ...networkConfigurationToUpdate, - defaultRpcEndpointUrl: rpcEndpoint2.url, + defaultRpcEndpointIndex: 0, rpcEndpoints: [rpcEndpoint2], }); @@ -4602,7 +4593,7 @@ describe('NetworkController', () => { controller.state.networkConfigurationsByChainId[infuraChainId], ).toStrictEqual({ ...networkConfigurationToUpdate, - defaultRpcEndpointUrl: rpcEndpoint2.url, + defaultRpcEndpointIndex: 0, rpcEndpoints: [rpcEndpoint2], }); }, @@ -4643,14 +4634,14 @@ describe('NetworkController', () => { infuraChainId, { ...networkConfigurationToUpdate, - defaultRpcEndpointUrl: rpcEndpoint2.url, + defaultRpcEndpointIndex: 0, rpcEndpoints: [rpcEndpoint2], }, ); expect(updatedNetworkConfiguration).toStrictEqual({ ...networkConfigurationToUpdate, - defaultRpcEndpointUrl: rpcEndpoint2.url, + defaultRpcEndpointIndex: 0, rpcEndpoints: [rpcEndpoint2], }); }, @@ -4994,7 +4985,7 @@ describe('NetworkController', () => { ({ controller }) => { controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, - defaultRpcEndpointUrl: rpcEndpoint1.url, + defaultRpcEndpointIndex: 0, rpcEndpoints: [ rpcEndpoint1, buildUpdateNetworkCustomRpcEndpointFields({ @@ -5075,7 +5066,7 @@ describe('NetworkController', () => { ({ controller }) => { controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, - defaultRpcEndpointUrl: rpcEndpoint1.url, + defaultRpcEndpointIndex: 0, rpcEndpoints: [ rpcEndpoint1, buildUpdateNetworkCustomRpcEndpointFields({ @@ -5141,7 +5132,7 @@ describe('NetworkController', () => { '0x1337', { ...networkConfigurationToUpdate, - defaultRpcEndpointUrl: rpcEndpoint1.url, + defaultRpcEndpointIndex: 0, rpcEndpoints: [ rpcEndpoint1, buildUpdateNetworkCustomRpcEndpointFields({ @@ -5215,7 +5206,7 @@ describe('NetworkController', () => { controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, - defaultRpcEndpointUrl: rpcEndpoint2.url, + defaultRpcEndpointIndex: 0, rpcEndpoints: [rpcEndpoint2], }); @@ -5259,7 +5250,7 @@ describe('NetworkController', () => { ({ controller }) => { controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, - defaultRpcEndpointUrl: rpcEndpoint2.url, + defaultRpcEndpointIndex: 0, rpcEndpoints: [rpcEndpoint2], }); @@ -5267,7 +5258,7 @@ describe('NetworkController', () => { controller.state.networkConfigurationsByChainId['0x1337'], ).toStrictEqual({ ...networkConfigurationToUpdate, - defaultRpcEndpointUrl: rpcEndpoint2.url, + defaultRpcEndpointIndex: 0, rpcEndpoints: [rpcEndpoint2], }); }, @@ -5306,14 +5297,14 @@ describe('NetworkController', () => { '0x1337', { ...networkConfigurationToUpdate, - defaultRpcEndpointUrl: rpcEndpoint2.url, + defaultRpcEndpointIndex: 0, rpcEndpoints: [rpcEndpoint2], }, ); expect(updatedNetworkConfiguration).toStrictEqual({ ...networkConfigurationToUpdate, - defaultRpcEndpointUrl: rpcEndpoint2.url, + defaultRpcEndpointIndex: 0, rpcEndpoints: [rpcEndpoint2], }); }, @@ -5357,7 +5348,7 @@ describe('NetworkController', () => { controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, - defaultRpcEndpointUrl: rpcEndpoint2.url, + defaultRpcEndpointIndex: 1, rpcEndpoints: [ rpcEndpoint1, { ...rpcEndpoint2, networkClientId: undefined }, @@ -5392,7 +5383,7 @@ describe('NetworkController', () => { ]; const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ chainId: '0x1337', - defaultRpcEndpointUrl: 'https://test.endpoint/1', + defaultRpcEndpointIndex: 0, nativeCurrency: 'TOKEN', rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], }); @@ -5458,7 +5449,7 @@ describe('NetworkController', () => { ]; const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ chainId: '0x1337', - defaultRpcEndpointUrl: 'https://test.endpoint/1', + defaultRpcEndpointIndex: 0, rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], }); @@ -5510,7 +5501,7 @@ describe('NetworkController', () => { ]; const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ chainId: '0x1337', - defaultRpcEndpointUrl: 'https://test.endpoint/1', + defaultRpcEndpointIndex: 0, rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], }); @@ -6055,7 +6046,7 @@ describe('NetworkController', () => { controller.updateNetwork(infuraChainId, { ...networkConfigurationToUpdate, chainId: '0x1337', - defaultRpcEndpointUrl: customRpcEndpoint1.url, + defaultRpcEndpointIndex: 0, rpcEndpoints: [customRpcEndpoint1, customRpcEndpoint2], }); @@ -6070,7 +6061,7 @@ describe('NetworkController', () => { ).toStrictEqual({ ...networkConfigurationToUpdate, chainId: '0x1337', - defaultRpcEndpointUrl: customRpcEndpoint1.url, + defaultRpcEndpointIndex: 0, rpcEndpoints: [ { ...customRpcEndpoint1, @@ -6138,7 +6129,7 @@ describe('NetworkController', () => { controller.updateNetwork(infuraChainId, { ...networkConfigurationToUpdate, chainId: '0x1337', - defaultRpcEndpointUrl: customRpcEndpoint1.url, + defaultRpcEndpointIndex: 0, rpcEndpoints: [customRpcEndpoint1, customRpcEndpoint2], }); @@ -6203,7 +6194,7 @@ describe('NetworkController', () => { controller.updateNetwork(infuraChainId, { ...networkConfigurationToUpdate, chainId: '0x1337', - defaultRpcEndpointUrl: customRpcEndpoint1.url, + defaultRpcEndpointIndex: 0, rpcEndpoints: [customRpcEndpoint1, customRpcEndpoint2], }); @@ -6285,7 +6276,7 @@ describe('NetworkController', () => { { ...networkConfigurationToUpdate, chainId: '0x1337', - defaultRpcEndpointUrl: customRpcEndpoint1.url, + defaultRpcEndpointIndex: 0, rpcEndpoints: [customRpcEndpoint1, customRpcEndpoint2], }, ); @@ -6293,7 +6284,7 @@ describe('NetworkController', () => { expect(updatedNetworkConfiguration).toStrictEqual({ ...networkConfigurationToUpdate, chainId: '0x1337', - defaultRpcEndpointUrl: customRpcEndpoint1.url, + defaultRpcEndpointIndex: 0, rpcEndpoints: [ { ...customRpcEndpoint1, @@ -6421,7 +6412,7 @@ describe('NetworkController', () => { controller.updateNetwork(infuraChainId, { ...networkConfigurationToUpdate, chainId: anotherInfuraChainId, - defaultRpcEndpointUrl: customRpcEndpoint1.url, + defaultRpcEndpointIndex: 0, rpcEndpoints: [customRpcEndpoint1, customRpcEndpoint2], }); @@ -6438,7 +6429,7 @@ describe('NetworkController', () => { ).toStrictEqual({ ...networkConfigurationToUpdate, chainId: anotherInfuraChainId, - defaultRpcEndpointUrl: customRpcEndpoint1.url, + defaultRpcEndpointIndex: 0, rpcEndpoints: [ { ...customRpcEndpoint1, @@ -6506,7 +6497,7 @@ describe('NetworkController', () => { controller.updateNetwork(infuraChainId, { ...networkConfigurationToUpdate, chainId: anotherInfuraChainId, - defaultRpcEndpointUrl: customRpcEndpoint1.url, + defaultRpcEndpointIndex: 0, rpcEndpoints: [customRpcEndpoint1, customRpcEndpoint2], }); @@ -6571,7 +6562,7 @@ describe('NetworkController', () => { controller.updateNetwork(infuraChainId, { ...networkConfigurationToUpdate, chainId: anotherInfuraChainId, - defaultRpcEndpointUrl: customRpcEndpoint1.url, + defaultRpcEndpointIndex: 0, rpcEndpoints: [customRpcEndpoint1, customRpcEndpoint2], }); @@ -6653,7 +6644,7 @@ describe('NetworkController', () => { { ...networkConfigurationToUpdate, chainId: anotherInfuraChainId, - defaultRpcEndpointUrl: customRpcEndpoint1.url, + defaultRpcEndpointIndex: 0, rpcEndpoints: [customRpcEndpoint1, customRpcEndpoint2], }, ); @@ -6661,7 +6652,7 @@ describe('NetworkController', () => { expect(updatedNetworkConfiguration).toStrictEqual({ ...networkConfigurationToUpdate, chainId: anotherInfuraChainId, - defaultRpcEndpointUrl: customRpcEndpoint1.url, + defaultRpcEndpointIndex: 0, rpcEndpoints: [ { ...customRpcEndpoint1, @@ -8686,7 +8677,7 @@ describe('NetworkController', () => { networkConfigurationsByChainId: { '0x1337': { chainId: '0x1337' as const, - defaultRpcEndpointUrl: 'https://test.network/1', + defaultRpcEndpointIndex: 0, name: 'Test Network 1', nativeCurrency: 'TOKEN1', rpcEndpoints: [ @@ -8706,7 +8697,7 @@ describe('NetworkController', () => { networkConfigurationsByChainId: { '0x2448': { chainId: '0x2448' as const, - defaultRpcEndpointUrl: 'https://test.network/2', + defaultRpcEndpointIndex: 0, name: 'Test Network 2', nativeCurrency: 'TOKEN2', rpcEndpoints: [ @@ -8725,7 +8716,7 @@ describe('NetworkController', () => { { '0x1337': { chainId: '0x1337' as const, - defaultRpcEndpointUrl: 'https://test.network/1', + defaultRpcEndpointIndex: 0, name: 'Test Network 1', nativeCurrency: 'TOKEN1', rpcEndpoints: [ @@ -8739,7 +8730,7 @@ describe('NetworkController', () => { }, '0x2448': { chainId: '0x2448' as const, - defaultRpcEndpointUrl: 'https://test.network/2', + defaultRpcEndpointIndex: 0, name: 'Test Network 2', nativeCurrency: 'TOKEN2', rpcEndpoints: [ diff --git a/packages/network-controller/tests/helpers.ts b/packages/network-controller/tests/helpers.ts index bf5de4ca3ab..e4feaf42513 100644 --- a/packages/network-controller/tests/helpers.ts +++ b/packages/network-controller/tests/helpers.ts @@ -200,7 +200,7 @@ export function buildNetworkConfiguration( { chainId: () => '0x1337', // @ts-expect-error We will make sure that this property is set below. - defaultRpcEndpointUrl: () => undefined, + defaultRpcEndpointIndex: () => undefined, name: () => 'Some Network', nativeCurrency: () => 'TOKEN', rpcEndpoints: () => [ @@ -212,12 +212,12 @@ export function buildNetworkConfiguration( overrides, (object) => { if ( - object.defaultRpcEndpointUrl === undefined && + object.defaultRpcEndpointIndex === undefined && object.rpcEndpoints.length > 0 ) { return { ...object, - defaultRpcEndpointUrl: object.rpcEndpoints[0].url, + defaultRpcEndpointIndex: 0, }; } return object; @@ -241,7 +241,7 @@ export function buildCustomNetworkConfiguration( { chainId: () => '0x1337' as const, // @ts-expect-error We will make sure that this property is set below. - defaultRpcEndpointUrl: () => undefined, + defaultRpcEndpointIndex: () => undefined, name: () => 'Some Network', nativeCurrency: () => 'TOKEN', rpcEndpoints: () => [ @@ -253,12 +253,12 @@ export function buildCustomNetworkConfiguration( overrides, (object) => { if ( - object.defaultRpcEndpointUrl === undefined && + object.defaultRpcEndpointIndex === undefined && object.rpcEndpoints.length > 0 ) { return { ...object, - defaultRpcEndpointUrl: object.rpcEndpoints[0].url, + defaultRpcEndpointIndex: 0, }; } return object; @@ -286,7 +286,7 @@ export function buildInfuraNetworkConfiguration( { chainId: () => ChainId[infuraNetworkType], // @ts-expect-error We will make sure that this property is set below. - defaultRpcEndpointUrl: () => undefined, + defaultRpcEndpointIndex: () => undefined, name: () => NetworkNickname[infuraNetworkType], nativeCurrency: () => NetworksTicker[infuraNetworkType], rpcEndpoints: () => [defaultRpcEndpoint], @@ -294,12 +294,12 @@ export function buildInfuraNetworkConfiguration( overrides, (object) => { if ( - object.defaultRpcEndpointUrl === undefined && + object.defaultRpcEndpointIndex === undefined && object.rpcEndpoints.length > 0 ) { return { ...object, - defaultRpcEndpointUrl: object.rpcEndpoints[0].url, + defaultRpcEndpointIndex: 0, }; } return object; @@ -362,7 +362,7 @@ export function buildAddNetworkFields( { chainId: () => '0x1337' as const, // @ts-expect-error We will make sure that this property is set below. - defaultRpcEndpointUrl: () => undefined, + defaultRpcEndpointIndex: () => undefined, name: () => 'Some Network', nativeCurrency: () => 'TOKEN', rpcEndpoints: () => [ @@ -374,12 +374,12 @@ export function buildAddNetworkFields( overrides, (object) => { if ( - object.defaultRpcEndpointUrl === undefined && + object.defaultRpcEndpointIndex === undefined && object.rpcEndpoints.length > 0 ) { return { ...object, - defaultRpcEndpointUrl: object.rpcEndpoints[0].url, + defaultRpcEndpointIndex: 0, }; } return object; From f2bcf43c19cb4133106094fd3bd9d127242c0ffd Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Tue, 2 Jul 2024 17:15:15 -0600 Subject: [PATCH 18/49] blockExplorerUrl -> blockExplorerUrls; add defaultBlockExplorerUrlIndex --- packages/network-controller/CHANGELOG.md | 2 +- .../src/NetworkController.ts | 51 ++++-- .../tests/NetworkController.test.ts | 146 ++++++++++++------ packages/network-controller/tests/helpers.ts | 4 + 4 files changed, 143 insertions(+), 60 deletions(-) diff --git a/packages/network-controller/CHANGELOG.md b/packages/network-controller/CHANGELOG.md index 4555d95278c..9d1027470a7 100644 --- a/packages/network-controller/CHANGELOG.md +++ b/packages/network-controller/CHANGELOG.md @@ -28,9 +28,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The only property that has been retained on this type is `chainId`. - `ticker` has been renamed to `nativeCurrency`. - `nickname` has been renamed to `name`. - - `blockExplorerUrl` has been pulled out of `rpcPrefs`. - `rpcEndpoints` has been added as well. This is an an array of objects, where each object has properties `name`, `networkClientId` (optional), `type`, and `url`. - `defaultRpcEndpointIndex` has been added. This must point to an entry in `rpcEndpoints`. + - The block explorer URL is no longer located in `rpcPrefs` and is no longer restricted to one: `blockExplorerUrls` has been added along with a corresponding property `defaultRpcEndpointIndex`, which must point to an entry in `blockExplorerUrls`. - `id` has been removed. Previously, this represented the ID of the network client associated with the network configuration. Since network clients are now created from RPC endpoints, the equivalent to this is the `networkClientId` property on an `RpcEndpoint`. - **BREAKING:** The network controller messenger must now allow the action `NetworkController:getNetworkConfigurationByChainId` ([#4268](https://github.com/MetaMask/core/pull/4286)) - **BREAKING:** The network controller messenger must now allow the event `NetworkController:networkAdded` ([#4268](https://github.com/MetaMask/core/pull/4286)) diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index 5443561a779..e59fc411fe8 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -166,16 +166,22 @@ export type RpcEndpoint = InfuraRpcEndpoint | CustomRpcEndpoint; */ export type NetworkConfiguration = { /** - * An optional URL that allows the user to view blocks and transactions that - * have occurred on the chain. + * A set of URLs that allows the user to view activity that has occurred on + * the chain. */ - blockExplorerUrl?: string; + blockExplorerUrls: string[]; /** * The ID of the chain. Represented in hexadecimal format with a leading "0x" * instead of decimal format so that when viewed out of context it can be * unambiguously interpreted. */ chainId: Hex; + /** + * A reference to a URL that the client will use by default to allow the user + * to view activity that has occurred on the chain. This index must refer to + * an item in `blockExplorerUrls`. + */ + defaultBlockExplorerUrlIndex?: number; /** * A reference to an RPC endpoint that all requests will use by default in order to * interact with the chain. This index must refer to an item in @@ -511,6 +517,7 @@ function getDefaultNetworkConfigurationsByChainId(): Record< const rpcEndpointUrl = `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`; const networkConfiguration: NetworkConfiguration = { + blockExplorerUrls: [], chainId, defaultRpcEndpointIndex: 0, name: NetworkNickname[infuraNetworkType], @@ -649,6 +656,18 @@ function validateNetworkControllerState(state: NetworkState) { ); } + if ( + networkConfiguration.blockExplorerUrls.length > 0 && + (networkConfiguration.defaultBlockExplorerUrlIndex === undefined || + networkConfiguration.blockExplorerUrls[ + networkConfiguration.defaultBlockExplorerUrlIndex + ] === undefined) + ) { + throw new Error( + `NetworkController state has invalid \`networkConfigurationsByChainId\`: Network configuration '${networkConfiguration.name}' has a \`defaultBlockExplorerUrlIndex\` that does not refer to an entry in \`blockExplorerUrls\``, + ); + } + if ( networkConfiguration.rpcEndpoints[ networkConfiguration.defaultRpcEndpointIndex @@ -1363,8 +1382,9 @@ export class NetworkController extends BaseController< */ addNetwork(fields: AddNetworkFields): NetworkConfiguration { const { - blockExplorerUrl, + blockExplorerUrls, chainId, + defaultBlockExplorerUrlIndex, defaultRpcEndpointIndex, nativeCurrency, rpcEndpoints: setOfRpcEndpointFields, @@ -1397,11 +1417,13 @@ export class NetworkController extends BaseController< ); } - if (blockExplorerUrl !== undefined && !isValidUrl(blockExplorerUrl)) { + if ( + blockExplorerUrls.length > 0 && + (defaultBlockExplorerUrlIndex === undefined || + blockExplorerUrls[defaultBlockExplorerUrlIndex] === undefined) + ) { throw new Error( - `Cannot add network: \`blockExplorerUrl\` ${inspect( - blockExplorerUrl, - )} is an invalid URL`, + `Cannot add network: \`defaultBlockExplorerUrlIndex\` must refer to an entry in \`blockExplorerUrls\``, ); } @@ -1613,8 +1635,9 @@ export class NetworkController extends BaseController< const existingChainId = chainId; const { - blockExplorerUrl: newBlockExplorerUrl, + blockExplorerUrls: newBlockExplorerUrls, chainId: newChainId, + defaultBlockExplorerUrlIndex: newDefaultBlockExplorerUrlIndex, defaultRpcEndpointIndex: newDefaultRpcEndpointIndex, nativeCurrency: newNativeTokenName, rpcEndpoints: setOfNewRpcEndpointFields, @@ -1655,11 +1678,13 @@ export class NetworkController extends BaseController< } } - if (newBlockExplorerUrl !== undefined && !isValidUrl(newBlockExplorerUrl)) { + if ( + newBlockExplorerUrls.length > 0 && + (newDefaultBlockExplorerUrlIndex === undefined || + newBlockExplorerUrls[newDefaultBlockExplorerUrlIndex] === undefined) + ) { throw new Error( - `Cannot update network: \`blockExplorerUrl\` ${inspect( - newBlockExplorerUrl, - )} is an invalid URL`, + `Cannot update network: \`defaultBlockExplorerUrlIndex\` must refer to an entry in \`blockExplorerUrls\``, ); } diff --git a/packages/network-controller/tests/NetworkController.test.ts b/packages/network-controller/tests/NetworkController.test.ts index 528263e3c3b..1436b8a64d1 100644 --- a/packages/network-controller/tests/NetworkController.test.ts +++ b/packages/network-controller/tests/NetworkController.test.ts @@ -177,6 +177,63 @@ describe('NetworkController', () => { ); }); + it('throws if a network configuration has an invalid defaultBlockExplorerUrlIndex', () => { + const messenger = buildMessenger(); + const restrictedMessenger = buildNetworkControllerMessenger(messenger); + expect( + () => + new NetworkController({ + messenger: restrictedMessenger, + state: { + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + blockExplorerUrls: ['https://block.explorer'], + defaultBlockExplorerUrlIndex: 99999, + chainId: '0x1337', + name: 'Test Network', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + url: 'https://some.endpoint', + }), + ], + }), + }, + }, + infuraProjectId: 'infura-project-id', + }), + ).toThrow( + "NetworkController state has invalid `networkConfigurationsByChainId`: Network configuration 'Test Network' has a `defaultBlockExplorerUrlIndex` that does not refer to an entry in `blockExplorerUrls`", + ); + }); + + it('throws if a network configuration has a non-empty blockExplorerUrls but not a defaultBlockExplorerUrlIndex', () => { + const messenger = buildMessenger(); + const restrictedMessenger = buildNetworkControllerMessenger(messenger); + expect( + () => + new NetworkController({ + messenger: restrictedMessenger, + state: { + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + blockExplorerUrls: ['https://block.explorer'], + chainId: '0x1337', + name: 'Test Network', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + url: 'https://some.endpoint', + }), + ], + }), + }, + }, + infuraProjectId: 'infura-project-id', + }), + ).toThrow( + "NetworkController state has invalid `networkConfigurationsByChainId`: Network configuration 'Test Network' has a `defaultBlockExplorerUrlIndex` that does not refer to an entry in `blockExplorerUrls`", + ); + }); + it('throws if a network configuration has an invalid defaultRpcEndpointIndex', () => { const messenger = buildMessenger(); const restrictedMessenger = buildNetworkControllerMessenger(messenger); @@ -290,6 +347,7 @@ describe('NetworkController', () => { Object { "networkConfigurationsByChainId": Object { "0x1": Object { + "blockExplorerUrls": Array [], "chainId": "0x1", "defaultRpcEndpointIndex": 0, "name": "Mainnet", @@ -304,6 +362,7 @@ describe('NetworkController', () => { ], }, "0x5": Object { + "blockExplorerUrls": Array [], "chainId": "0x5", "defaultRpcEndpointIndex": 0, "name": "Goerli", @@ -318,6 +377,7 @@ describe('NetworkController', () => { ], }, "0xaa36a7": Object { + "blockExplorerUrls": Array [], "chainId": "0xaa36a7", "defaultRpcEndpointIndex": 0, "name": "Sepolia", @@ -332,6 +392,7 @@ describe('NetworkController', () => { ], }, "0xe704": Object { + "blockExplorerUrls": Array [], "chainId": "0xe704", "defaultRpcEndpointIndex": 0, "name": "Linea Goerli", @@ -346,6 +407,7 @@ describe('NetworkController', () => { ], }, "0xe705": Object { + "blockExplorerUrls": Array [], "chainId": "0xe705", "defaultRpcEndpointIndex": 0, "name": "Linea Sepolia", @@ -360,6 +422,7 @@ describe('NetworkController', () => { ], }, "0xe708": Object { + "blockExplorerUrls": Array [], "chainId": "0xe708", "defaultRpcEndpointIndex": 0, "name": "Linea Mainnet", @@ -388,7 +451,9 @@ describe('NetworkController', () => { selectedNetworkClientId: InfuraNetworkType.goerli, networkConfigurationsByChainId: { [ChainId.goerli]: { + blockExplorerUrls: ['https://block.explorer'], chainId: ChainId.goerli, + defaultBlockExplorerUrlIndex: 0, defaultRpcEndpointIndex: 0, name: 'Goerli', nativeCurrency: 'GoerliETH', @@ -415,7 +480,11 @@ describe('NetworkController', () => { Object { "networkConfigurationsByChainId": Object { "0x5": Object { + "blockExplorerUrls": Array [ + "https://block.explorer", + ], "chainId": "0x5", + "defaultBlockExplorerUrlIndex": 0, "defaultRpcEndpointIndex": 0, "name": "Goerli", "nativeCurrency": "GoerliETH", @@ -2882,22 +2951,6 @@ describe('NetworkController', () => { }); }); - it('throws if the given blockExplorerUrl field is an invalid URL', async () => { - await withController(({ controller }) => { - expect(() => - controller.addNetwork( - buildAddNetworkFields({ - blockExplorerUrl: 'clearly-not-a-url', - }), - ), - ).toThrow( - new Error( - "Cannot add network: `blockExplorerUrl` 'clearly-not-a-url' is an invalid URL", - ), - ); - }); - }); - it('throws if the rpcEndpoints field is an empty array', async () => { await withController(({ controller }) => { expect(() => @@ -3251,6 +3304,7 @@ describe('NetworkController', () => { buildInfuraRpcEndpoint(infuraNetworkType); controller.addNetwork({ + blockExplorerUrls: [], chainId: infuraChainId, defaultRpcEndpointIndex: 1, name: infuraNetworkType, @@ -3348,7 +3402,9 @@ describe('NetworkController', () => { }, ({ controller }) => { controller.addNetwork({ + blockExplorerUrls: ['https://block.explorer'], chainId: infuraChainId, + defaultBlockExplorerUrlIndex: 0, defaultRpcEndpointIndex: 0, name: 'Some Network', nativeCurrency: 'TOKEN', @@ -3375,7 +3431,9 @@ describe('NetworkController', () => { expect( controller.state.networkConfigurationsByChainId[infuraChainId], ).toStrictEqual({ + blockExplorerUrls: ['https://block.explorer'], chainId: infuraChainId, + defaultBlockExplorerUrlIndex: 0, defaultRpcEndpointIndex: 0, name: 'Some Network', nativeCurrency: 'TOKEN', @@ -3426,7 +3484,9 @@ describe('NetworkController', () => { ); controller.addNetwork({ + blockExplorerUrls: ['https://block.explorer'], chainId: infuraChainId, + defaultBlockExplorerUrlIndex: 0, defaultRpcEndpointIndex: 0, name: 'Some Network', nativeCurrency: 'TOKEN', @@ -3443,7 +3503,9 @@ describe('NetworkController', () => { }); expect(networkAddedEventListener).toHaveBeenCalledWith({ + blockExplorerUrls: ['https://block.explorer'], chainId: infuraChainId, + defaultBlockExplorerUrlIndex: 0, defaultRpcEndpointIndex: 0, name: 'Some Network', nativeCurrency: 'TOKEN', @@ -3482,7 +3544,9 @@ describe('NetworkController', () => { }, ({ controller }) => { const newNetworkConfiguration = controller.addNetwork({ + blockExplorerUrls: ['https://block.explorer'], chainId: infuraChainId, + defaultBlockExplorerUrlIndex: 0, defaultRpcEndpointIndex: 0, name: 'Some Network', nativeCurrency: 'TOKEN', @@ -3499,7 +3563,9 @@ describe('NetworkController', () => { }); expect(newNetworkConfiguration).toStrictEqual({ + blockExplorerUrls: ['https://block.explorer'], chainId: infuraChainId, + defaultBlockExplorerUrlIndex: 0, defaultRpcEndpointIndex: 0, name: 'Some Network', nativeCurrency: 'TOKEN', @@ -3543,6 +3609,7 @@ describe('NetworkController', () => { ({ controller }) => { expect(() => controller.addNetwork({ + blockExplorerUrls: [], chainId: '0x1337', defaultRpcEndpointIndex: 0, name: 'Some Network', @@ -3572,6 +3639,7 @@ describe('NetworkController', () => { await withController(({ controller }) => { controller.addNetwork({ + blockExplorerUrls: [], chainId: '0x1337', defaultRpcEndpointIndex: 0, name: 'Some Network', @@ -3591,7 +3659,6 @@ describe('NetworkController', () => { }); const networkClient1 = controller.getNetworkClientById( - // 'mm:ncli:custom:https:443:test.endpoint/1', 'AAAA-AAAA-AAAA-AAAA', ); expect(networkClient1.configuration).toStrictEqual({ @@ -3601,7 +3668,6 @@ describe('NetworkController', () => { type: NetworkClientType.Custom, }); const networkClient2 = controller.getNetworkClientById( - // 'mm:ncli:custom:https:443:test.endpoint/2', 'BBBB-BBBB-BBBB-BBBB', ); expect(networkClient2.configuration).toStrictEqual({ @@ -3620,7 +3686,9 @@ describe('NetworkController', () => { await withController(({ controller }) => { controller.addNetwork({ + blockExplorerUrls: ['https://block.explorer'], chainId: '0x1337', + defaultBlockExplorerUrlIndex: 0, defaultRpcEndpointIndex: 0, name: 'Some Network', nativeCurrency: 'TOKEN', @@ -3641,7 +3709,9 @@ describe('NetworkController', () => { expect( controller.state.networkConfigurationsByChainId['0x1337'], ).toStrictEqual({ + blockExplorerUrls: ['https://block.explorer'], chainId: '0x1337', + defaultBlockExplorerUrlIndex: 0, defaultRpcEndpointIndex: 0, name: 'Some Network', nativeCurrency: 'TOKEN', @@ -3674,7 +3744,9 @@ describe('NetworkController', () => { ); controller.addNetwork({ + blockExplorerUrls: ['https://block.explorer'], chainId: '0x1337', + defaultBlockExplorerUrlIndex: 0, defaultRpcEndpointIndex: 0, name: 'Some Network', nativeCurrency: 'TOKEN', @@ -3688,7 +3760,9 @@ describe('NetworkController', () => { }); expect(networkAddedEventListener).toHaveBeenCalledWith({ + blockExplorerUrls: ['https://block.explorer'], chainId: '0x1337', + defaultBlockExplorerUrlIndex: 0, defaultRpcEndpointIndex: 0, name: 'Some Network', nativeCurrency: 'TOKEN', @@ -3715,7 +3789,9 @@ describe('NetworkController', () => { ); const newNetworkConfiguration = controller.addNetwork({ + blockExplorerUrls: ['https://block.explorer'], chainId: '0x1337', + defaultBlockExplorerUrlIndex: 0, defaultRpcEndpointIndex: 0, name: 'Some Network', nativeCurrency: 'TOKEN', @@ -3729,7 +3805,9 @@ describe('NetworkController', () => { }); expect(newNetworkConfiguration).toStrictEqual({ + blockExplorerUrls: ['https://block.explorer'], chainId: '0x1337', + defaultBlockExplorerUrlIndex: 0, defaultRpcEndpointIndex: 0, name: 'Some Network', nativeCurrency: 'TOKEN', @@ -3826,34 +3904,6 @@ describe('NetworkController', () => { ); }); - it('throws if the new blockExplorerUrl field is an invalid URL', async () => { - const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ - chainId: '0x1337', - }); - - await withController( - { - state: buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ - networkConfigurationsByChainId: { - '0x1337': networkConfigurationToUpdate, - }, - }), - }, - ({ controller }) => { - expect(() => - controller.updateNetwork('0x1337', { - ...networkConfigurationToUpdate, - blockExplorerUrl: 'clearly-not-a-url', - }), - ).toThrow( - new Error( - "Cannot update network: `blockExplorerUrl` 'clearly-not-a-url' is an invalid URL", - ), - ); - }, - ); - }); - it('throws if the new rpcEndpoints field is an empty array', async () => { const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ chainId: '0x1337', @@ -8676,6 +8726,7 @@ describe('NetworkController', () => { state: buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ networkConfigurationsByChainId: { '0x1337': { + blockExplorerUrls: [], chainId: '0x1337' as const, defaultRpcEndpointIndex: 0, name: 'Test Network 1', @@ -8696,6 +8747,7 @@ describe('NetworkController', () => { controller.loadBackup({ networkConfigurationsByChainId: { '0x2448': { + blockExplorerUrls: [], chainId: '0x2448' as const, defaultRpcEndpointIndex: 0, name: 'Test Network 2', @@ -8715,6 +8767,7 @@ describe('NetworkController', () => { expect(controller.state.networkConfigurationsByChainId).toStrictEqual( { '0x1337': { + blockExplorerUrls: [], chainId: '0x1337' as const, defaultRpcEndpointIndex: 0, name: 'Test Network 1', @@ -8729,6 +8782,7 @@ describe('NetworkController', () => { ], }, '0x2448': { + blockExplorerUrls: [], chainId: '0x2448' as const, defaultRpcEndpointIndex: 0, name: 'Test Network 2', diff --git a/packages/network-controller/tests/helpers.ts b/packages/network-controller/tests/helpers.ts index e4feaf42513..fc49d54458a 100644 --- a/packages/network-controller/tests/helpers.ts +++ b/packages/network-controller/tests/helpers.ts @@ -198,6 +198,7 @@ export function buildNetworkConfiguration( ): NetworkConfiguration { return buildTestObject( { + blockExplorerUrls: () => [], chainId: () => '0x1337', // @ts-expect-error We will make sure that this property is set below. defaultRpcEndpointIndex: () => undefined, @@ -239,6 +240,7 @@ export function buildCustomNetworkConfiguration( ): NetworkConfiguration { return buildTestObject( { + blockExplorerUrls: () => [], chainId: () => '0x1337' as const, // @ts-expect-error We will make sure that this property is set below. defaultRpcEndpointIndex: () => undefined, @@ -284,6 +286,7 @@ export function buildInfuraNetworkConfiguration( const defaultRpcEndpoint = buildInfuraRpcEndpoint(infuraNetworkType); return buildTestObject( { + blockExplorerUrls: () => [], chainId: () => ChainId[infuraNetworkType], // @ts-expect-error We will make sure that this property is set below. defaultRpcEndpointIndex: () => undefined, @@ -360,6 +363,7 @@ export function buildAddNetworkFields( ): AddNetworkFields { return buildTestObject( { + blockExplorerUrls: () => [], chainId: () => '0x1337' as const, // @ts-expect-error We will make sure that this property is set below. defaultRpcEndpointIndex: () => undefined, From 1c4ca62eb1cd2f794dcf27232aa3afdd0e757f9e Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Tue, 2 Jul 2024 17:20:08 -0600 Subject: [PATCH 19/49] Make 'name' on an RPC endpoint optional --- packages/network-controller/CHANGELOG.md | 2 +- packages/network-controller/src/NetworkController.ts | 9 ++++----- .../network-controller/tests/NetworkController.test.ts | 6 ------ packages/network-controller/tests/helpers.ts | 4 ---- 4 files changed, 5 insertions(+), 16 deletions(-) diff --git a/packages/network-controller/CHANGELOG.md b/packages/network-controller/CHANGELOG.md index 9d1027470a7..53b9bc49358 100644 --- a/packages/network-controller/CHANGELOG.md +++ b/packages/network-controller/CHANGELOG.md @@ -28,7 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The only property that has been retained on this type is `chainId`. - `ticker` has been renamed to `nativeCurrency`. - `nickname` has been renamed to `name`. - - `rpcEndpoints` has been added as well. This is an an array of objects, where each object has properties `name`, `networkClientId` (optional), `type`, and `url`. + - `rpcEndpoints` has been added as well. This is an an array of objects, where each object has properties `name` (optional), `networkClientId` (optional), `type`, and `url`. - `defaultRpcEndpointIndex` has been added. This must point to an entry in `rpcEndpoints`. - The block explorer URL is no longer located in `rpcPrefs` and is no longer restricted to one: `blockExplorerUrls` has been added along with a corresponding property `defaultRpcEndpointIndex`, which must point to an entry in `blockExplorerUrls`. - `id` has been removed. Previously, this represented the ID of the network client associated with the network configuration. Since network clients are now created from RPC endpoints, the equivalent to this is the `networkClientId` property on an `RpcEndpoint`. diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index e59fc411fe8..0113f273195 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -99,9 +99,9 @@ export enum RpcEndpointType { */ export type InfuraRpcEndpoint = { /** - * The user-facing name of the endpoint. + * The optional user-facing nickname of the endpoint. */ - name: string; + name?: string; /** * The identifier for the network client that has been created for this RPC * endpoint. @@ -124,9 +124,9 @@ export type InfuraRpcEndpoint = { */ export type CustomRpcEndpoint = { /** - * The user-facing name of the endpoint. + * The optional user-facing nickname of the endpoint. */ - name: string; + name?: string; /** * The identifier for the network client that has been created for this RPC * endpoint. @@ -524,7 +524,6 @@ function getDefaultNetworkConfigurationsByChainId(): Record< nativeCurrency: NetworksTicker[infuraNetworkType], rpcEndpoints: [ { - name: `Infura ${NetworkNickname[infuraNetworkType] as string}`, networkClientId: infuraNetworkType, type: RpcEndpointType.Infura, url: rpcEndpointUrl, diff --git a/packages/network-controller/tests/NetworkController.test.ts b/packages/network-controller/tests/NetworkController.test.ts index 1436b8a64d1..2c175ed7509 100644 --- a/packages/network-controller/tests/NetworkController.test.ts +++ b/packages/network-controller/tests/NetworkController.test.ts @@ -354,7 +354,6 @@ describe('NetworkController', () => { "nativeCurrency": "ETH", "rpcEndpoints": Array [ Object { - "name": "Infura Mainnet", "networkClientId": "mainnet", "type": "infura", "url": "https://mainnet.infura.io/v3/{infuraProjectId}", @@ -369,7 +368,6 @@ describe('NetworkController', () => { "nativeCurrency": "GoerliETH", "rpcEndpoints": Array [ Object { - "name": "Infura Goerli", "networkClientId": "goerli", "type": "infura", "url": "https://goerli.infura.io/v3/{infuraProjectId}", @@ -384,7 +382,6 @@ describe('NetworkController', () => { "nativeCurrency": "SepoliaETH", "rpcEndpoints": Array [ Object { - "name": "Infura Sepolia", "networkClientId": "sepolia", "type": "infura", "url": "https://sepolia.infura.io/v3/{infuraProjectId}", @@ -399,7 +396,6 @@ describe('NetworkController', () => { "nativeCurrency": "LineaETH", "rpcEndpoints": Array [ Object { - "name": "Infura Linea Goerli", "networkClientId": "linea-goerli", "type": "infura", "url": "https://linea-goerli.infura.io/v3/{infuraProjectId}", @@ -414,7 +410,6 @@ describe('NetworkController', () => { "nativeCurrency": "LineaETH", "rpcEndpoints": Array [ Object { - "name": "Infura Linea Sepolia", "networkClientId": "linea-sepolia", "type": "infura", "url": "https://linea-sepolia.infura.io/v3/{infuraProjectId}", @@ -429,7 +424,6 @@ describe('NetworkController', () => { "nativeCurrency": "ETH", "rpcEndpoints": Array [ Object { - "name": "Infura Linea Mainnet", "networkClientId": "linea-mainnet", "type": "infura", "url": "https://linea-mainnet.infura.io/v3/{infuraProjectId}", diff --git a/packages/network-controller/tests/helpers.ts b/packages/network-controller/tests/helpers.ts index fc49d54458a..709f5c4388a 100644 --- a/packages/network-controller/tests/helpers.ts +++ b/packages/network-controller/tests/helpers.ts @@ -321,7 +321,6 @@ export function buildInfuraRpcEndpoint( infuraNetworkType: InfuraNetworkType, ): InfuraRpcEndpoint { return { - name: NetworkNickname[infuraNetworkType], networkClientId: infuraNetworkType, type: RpcEndpointType.Infura as const, // False negative - this is a string. @@ -342,7 +341,6 @@ export function buildCustomRpcEndpoint( ): CustomRpcEndpoint { return buildTestObject( { - name: () => 'Test Endpoint', networkClientId: () => uuidV4(), type: () => RpcEndpointType.Custom as const, url: () => 'https://test.endpoint', @@ -404,7 +402,6 @@ export function buildAddNetworkCustomRpcEndpointFields( ): AddNetworkCustomRpcEndpointFields { return buildTestObject( { - name: () => 'Test Endpoint', type: () => RpcEndpointType.Custom as const, url: () => 'https://test.endpoint', }, @@ -425,7 +422,6 @@ export function buildUpdateNetworkCustomRpcEndpointFields( ): UpdateNetworkCustomRpcEndpointFields { return buildTestObject( { - name: () => 'Test Endpoint', type: () => RpcEndpointType.Custom as const, url: () => 'https://test.endpoint', }, From 3109357e982adc53b6b6a293bac94aa4673ae1aa Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Fri, 5 Jul 2024 17:02:15 -0600 Subject: [PATCH 20/49] Adjust JSDoc for nativeCurrency to be more accurate --- packages/network-controller/src/NetworkController.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index 0113f273195..d5b41335dc8 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -193,8 +193,7 @@ export type NetworkConfiguration = { */ name: string; /** - * The name of the token that represents the native currency for the chain - * (i.e. the ticker symbol). + * The name of the currency to use for the chain. */ nativeCurrency: string; /** From 1093077ddecf962a99372b55afc79a6073e5d6bb Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Wed, 10 Jul 2024 15:18:03 -0600 Subject: [PATCH 21/49] Extract validation logic --- .../src/NetworkController.ts | 507 ++++++++---------- .../tests/NetworkController.test.ts | 64 +-- 2 files changed, 261 insertions(+), 310 deletions(-) diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index 366038f0ebe..f3d0a399bed 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -1426,140 +1426,23 @@ export class NetworkController extends BaseController< */ addNetwork(fields: AddNetworkFields): NetworkConfiguration { const { - blockExplorerUrls, chainId, - defaultBlockExplorerUrlIndex, - defaultRpcEndpointIndex, nativeCurrency, rpcEndpoints: setOfRpcEndpointFields, } = fields; const rpcEndpointUrls = setOfRpcEndpointFields.map( (rpcEndpointFields) => rpcEndpointFields.url, ); - const infuraRpcEndpoints = setOfRpcEndpointFields.filter( - (rpcEndpointFields): rpcEndpointFields is InfuraRpcEndpoint => - rpcEndpointFields.type === RpcEndpointType.Infura, - ); - const networkClientIds = infuraRpcEndpoints.map( - (rpcEndpointFields) => rpcEndpointFields.networkClientId, - ); - - if (!isStrictHexString(chainId) || !isSafeChainId(chainId)) { - throw new Error( - `Cannot add network: Invalid \`chainId\` ${inspect( - chainId, - )} (must start with "0x" and not exceed the maximum)`, - ); - } - const existingNetworkConfigurationViaChain = - this.state.networkConfigurationsByChainId[chainId]; - if (existingNetworkConfigurationViaChain !== undefined) { - throw new Error( - // False negative - this is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - `Cannot add network for chain ${chainId} as another network for that chain already exists ('${existingNetworkConfigurationViaChain.name}')`, - ); - } - - if ( - blockExplorerUrls.length > 0 && - (defaultBlockExplorerUrlIndex === undefined || - blockExplorerUrls[defaultBlockExplorerUrlIndex] === undefined) - ) { - throw new Error( - `Cannot add network: \`defaultBlockExplorerUrlIndex\` must refer to an entry in \`blockExplorerUrls\``, - ); - } - - if (setOfRpcEndpointFields.length === 0) { - throw new Error( - 'Cannot add network: `rpcEndpoints` must be a non-empty array', - ); - } - for (const rpcEndpointFields of setOfRpcEndpointFields) { - const { url } = rpcEndpointFields; - - if (!isValidUrl(url)) { - throw new Error( - `Cannot add network: An entry in \`rpcEndpoints\` has invalid URL ${inspect( - url, - )}`, - ); - } - - if ( - setOfRpcEndpointFields.some( - (otherNewRpcEndpointFields) => - otherNewRpcEndpointFields !== rpcEndpointFields && - URI.equal(otherNewRpcEndpointFields.url, rpcEndpointFields.url), - ) - ) { - throw new Error( - `Cannot add network: Each entry in rpcEndpoints must have a unique URL`, - ); - } - for (const networkConfiguration of Object.values( - this.state.networkConfigurationsByChainId, - )) { - const rpcEndpoint = networkConfiguration.rpcEndpoints.find( - (existingRpcEndpoint) => - URI.equal(rpcEndpointFields.url, existingRpcEndpoint.url), - ); - if (rpcEndpoint) { - throw new Error( - // False negative - these are strings. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - `Cannot add network that points to same RPC endpoint as existing network for chain ${networkConfiguration.chainId} ('${networkConfiguration.name}')`, - ); - } - } - } - - if ( - [...new Set(setOfRpcEndpointFields)].length < - setOfRpcEndpointFields.length - ) { - throw new Error( - 'Cannot add network: Each entry in rpcEndpoints must be unique', - ); - } - - if ([...new Set(networkClientIds)].length < networkClientIds.length) { - throw new Error( - 'Cannot add network: Each entry in rpcEndpoints must have a unique networkClientId', - ); - } - - if (infuraRpcEndpoints.length > 1) { - throw new Error( - 'Cannot add network: There cannot be more than one Infura RPC endpoint', - ); - } - - const infuraRpcEndpoint = setOfRpcEndpointFields.find( - (rpcEndpointFields) => rpcEndpointFields.type === RpcEndpointType.Infura, - ); - if (infuraRpcEndpoint) { - const infuraNetworkName = deriveInfuraNetworkNameFromRpcEndpointUrl( - infuraRpcEndpoint.url, - ); - const infuraNetworkNickname = NetworkNickname[infuraNetworkName]; - const infuraChainId = ChainId[infuraNetworkName]; - if (chainId !== infuraChainId) { - throw new Error( - // This is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - `Cannot add network with chain ID ${chainId} and Infura RPC endpoint for '${infuraNetworkNickname}' which represents ${infuraChainId}, as the two conflict`, - ); - } - } + const autoManagedNetworkClientRegistry = + this.#ensureAutoManagedNetworkClientRegistryPopulated(); - if (setOfRpcEndpointFields[defaultRpcEndpointIndex] === undefined) { - throw new Error( - `Cannot add network: \`defaultRpcEndpointIndex\` must refer to an entry in \`rpcEndpoints\``, - ); - } + this.#validateNetworkFields({ + mode: 'add', + errorMessagePrefix: 'Could not add network', + networkFields: fields, + autoManagedNetworkClientRegistry, + }); let conflict: | { @@ -1583,7 +1466,7 @@ export class NetworkController extends BaseController< } if (conflict !== undefined) { throw new Error( - `Cannot add network that points to same RPC endpoint as existing network for chain ${conflict.networkConfiguration.chainId} ("${conflict.networkConfiguration.name}")`, + `Could not add network that points to same RPC endpoint as existing network for chain ${conflict.networkConfiguration.chainId} ("${conflict.networkConfiguration.name}")`, ); } @@ -1599,8 +1482,6 @@ export class NetworkController extends BaseController< }, ); - const autoManagedNetworkClientRegistry = - this.#ensureAutoManagedNetworkClientRegistryPopulated(); for (const rpcEndpoint of newRpcEndpoints) { if (rpcEndpoint.type === RpcEndpointType.Infura) { autoManagedNetworkClientRegistry[NetworkClientType.Infura][ @@ -1679,164 +1560,21 @@ export class NetworkController extends BaseController< const existingChainId = chainId; const { - blockExplorerUrls: newBlockExplorerUrls, chainId: newChainId, - defaultBlockExplorerUrlIndex: newDefaultBlockExplorerUrlIndex, - defaultRpcEndpointIndex: newDefaultRpcEndpointIndex, nativeCurrency: newNativeTokenName, rpcEndpoints: setOfNewRpcEndpointFields, } = fields; - const infuraRpcEndpoints = setOfNewRpcEndpointFields.filter( - (newRpcEndpointFields): newRpcEndpointFields is InfuraRpcEndpoint => - newRpcEndpointFields.type === RpcEndpointType.Infura, - ); - const newNetworkClientIds = setOfNewRpcEndpointFields - .map((newRpcEndpointFields) => newRpcEndpointFields.networkClientId) - .filter((networkClientId) => networkClientId !== undefined); - const networkConfigurationsForOtherChains = Object.values( - this.state.networkConfigurationsByChainId, - ).filter( - (networkConfiguration) => - networkConfiguration.chainId !== existingChainId, - ); const autoManagedNetworkClientRegistry = this.#ensureAutoManagedNetworkClientRegistryPopulated(); - if (!isStrictHexString(newChainId) || !isSafeChainId(newChainId)) { - throw new Error( - `Cannot update network: New \`chainId\` ${inspect( - newChainId, - )} is invalid (must start with "0x" and not exceed the maximum)`, - ); - } - if (newChainId !== existingChainId) { - const existingNetworkConfigurationViaChain = - this.state.networkConfigurationsByChainId[newChainId]; - if (existingNetworkConfigurationViaChain !== undefined) { - throw new Error( - // False negative - these are strings. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - `Cannot move network from chain ${existingChainId} to ${newChainId} as another network for that chain already exists ('${existingNetworkConfigurationViaChain.name}')`, - ); - } - } - - if ( - newBlockExplorerUrls.length > 0 && - (newDefaultBlockExplorerUrlIndex === undefined || - newBlockExplorerUrls[newDefaultBlockExplorerUrlIndex] === undefined) - ) { - throw new Error( - `Cannot update network: \`defaultBlockExplorerUrlIndex\` must refer to an entry in \`blockExplorerUrls\``, - ); - } - - if (setOfNewRpcEndpointFields.length === 0) { - throw new Error( - 'Cannot update network: `rpcEndpoints` must be a non-empty array', - ); - } - for (const newRpcEndpointFields of setOfNewRpcEndpointFields) { - const { url, networkClientId } = newRpcEndpointFields; - - if (!isValidUrl(url)) { - throw new Error( - `Cannot update network: An entry in \`rpcEndpoints\` has invalid URL ${inspect( - url, - )}`, - ); - } - - if ( - networkClientId !== undefined && - !Object.values(autoManagedNetworkClientRegistry).some( - (networkClientsById) => networkClientId in networkClientsById, - ) - ) { - throw new Error( - `Cannot update network: RPC endpoint '${ - // This is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - url - }' refers to network client ${inspect( - networkClientId, - )} that does not exist`, - ); - } - - if ( - setOfNewRpcEndpointFields.some( - (otherNewRpcEndpointFields) => - otherNewRpcEndpointFields !== newRpcEndpointFields && - URI.equal(otherNewRpcEndpointFields.url, newRpcEndpointFields.url), - ) - ) { - throw new Error( - `Cannot update network: Each entry in rpcEndpoints must have a unique URL`, - ); - } - - for (const networkConfiguration of networkConfigurationsForOtherChains) { - const rpcEndpoint = networkConfiguration.rpcEndpoints.find( - (existingRpcEndpoint) => - URI.equal(newRpcEndpointFields.url, existingRpcEndpoint.url), - ); - if (rpcEndpoint) { - throw new Error( - // False negative - these are strings. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - `Cannot update network to point to same RPC endpoint as existing network for chain ${networkConfiguration.chainId} ('${networkConfiguration.name}')`, - ); - } - } - } - - if ( - [...new Set(setOfNewRpcEndpointFields)].length < - setOfNewRpcEndpointFields.length - ) { - throw new Error( - 'Cannot update network: Each entry in rpcEndpoints must be unique', - ); - } - - if ([...new Set(newNetworkClientIds)].length < newNetworkClientIds.length) { - throw new Error( - 'Cannot update network: Each entry in rpcEndpoints must have a unique networkClientId', - ); - } - - if (infuraRpcEndpoints.length > 1) { - throw new Error( - 'Cannot update network: There cannot be more than one Infura RPC endpoint', - ); - } - - const infuraRpcEndpoint = setOfNewRpcEndpointFields.find( - (newRpcEndpointFields) => - newRpcEndpointFields.type === RpcEndpointType.Infura, - ); - if (infuraRpcEndpoint) { - const infuraNetworkName = deriveInfuraNetworkNameFromRpcEndpointUrl( - infuraRpcEndpoint.url, - ); - const infuraNetworkNickname = NetworkNickname[infuraNetworkName]; - const infuraChainId = ChainId[infuraNetworkName]; - if (newChainId !== infuraChainId) { - throw new Error( - // This is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - `Cannot update network with chain ID ${newChainId} and Infura RPC endpoint for '${infuraNetworkNickname}' which represents ${infuraChainId}, as the two conflict`, - ); - } - } - - if (setOfNewRpcEndpointFields[newDefaultRpcEndpointIndex] === undefined) { - throw new Error( - `Cannot update network: \`defaultRpcEndpointIndex\` must refer to an entry in \`rpcEndpoints\``, - ); - } + this.#validateNetworkFields({ + mode: 'update', + errorMessagePrefix: 'Could not update network', + networkFields: fields, + existingNetworkConfiguration, + autoManagedNetworkClientRegistry, + }); const operations: { type: 'add' | 'remove' | 'noop'; @@ -2073,6 +1811,219 @@ export class NetworkController extends BaseController< return networkClientEntry[0]; } + /** + * Ensure that the given fields which will be used to either add or update a + * network are valid. + * + * @param args - The arguments. + */ + #validateNetworkFields( + args: { + errorMessagePrefix: string; + autoManagedNetworkClientRegistry: AutoManagedNetworkClientRegistry; + } & ( + | { + mode: 'add'; + networkFields: AddNetworkFields; + } + | { + mode: 'update'; + existingNetworkConfiguration: NetworkConfiguration; + networkFields: UpdateNetworkFields; + } + ), + ) { + const { + mode, + networkFields, + errorMessagePrefix, + autoManagedNetworkClientRegistry, + } = args; + const existingNetworkConfiguration = + 'existingNetworkConfiguration' in args + ? args.existingNetworkConfiguration + : null; + + if ( + !isStrictHexString(networkFields.chainId) || + !isSafeChainId(networkFields.chainId) + ) { + throw new Error( + `${errorMessagePrefix}: Invalid \`chainId\` ${inspect( + networkFields.chainId, + )} (must start with "0x" and not exceed the maximum)`, + ); + } + + if ( + existingNetworkConfiguration === null || + networkFields.chainId !== existingNetworkConfiguration.chainId + ) { + const existingNetworkConfigurationViaChainId = + this.state.networkConfigurationsByChainId[networkFields.chainId]; + if (existingNetworkConfigurationViaChainId !== undefined) { + throw new Error( + // False negative - these are strings. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + existingNetworkConfiguration === null + ? `Could not add network for chain ${args.networkFields.chainId} as another network for that chain already exists ('${existingNetworkConfigurationViaChainId.name}')` + : `Cannot move network from chain ${existingNetworkConfiguration.chainId} to ${networkFields.chainId} as another network for that chain already exists ('${existingNetworkConfigurationViaChainId.name}')`, + ); + } + } + + if ( + networkFields.blockExplorerUrls.length > 0 && + (networkFields.defaultBlockExplorerUrlIndex === undefined || + networkFields.blockExplorerUrls[ + networkFields.defaultBlockExplorerUrlIndex + ] === undefined) + ) { + throw new Error( + `${errorMessagePrefix}: \`defaultBlockExplorerUrlIndex\` must refer to an entry in \`blockExplorerUrls\``, + ); + } + + if (networkFields.rpcEndpoints.length === 0) { + throw new Error( + `${errorMessagePrefix}: \`rpcEndpoints\` must be a non-empty array`, + ); + } + for (const rpcEndpointFields of networkFields.rpcEndpoints) { + if (!isValidUrl(rpcEndpointFields.url)) { + throw new Error( + `${errorMessagePrefix}: An entry in \`rpcEndpoints\` has invalid URL ${inspect( + rpcEndpointFields.url, + )}`, + ); + } + const networkClientId = + 'networkClientId' in rpcEndpointFields + ? rpcEndpointFields.networkClientId + : undefined; + + if ( + mode === 'update' && + networkClientId !== undefined && + !Object.values(autoManagedNetworkClientRegistry).some( + (networkClientsById) => networkClientId in networkClientsById, + ) + ) { + throw new Error( + `Could not update network: RPC endpoint '${ + // This is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + rpcEndpointFields.url + }' refers to network client ${inspect( + networkClientId, + )} that does not exist`, + ); + } + + if ( + networkFields.rpcEndpoints.some( + (otherRpcEndpointFields) => + otherRpcEndpointFields !== rpcEndpointFields && + URI.equal(otherRpcEndpointFields.url, rpcEndpointFields.url), + ) + ) { + throw new Error( + `${errorMessagePrefix}: Each entry in rpcEndpoints must have a unique URL`, + ); + } + + const networkConfigurationsForOtherChains = Object.values( + this.state.networkConfigurationsByChainId, + ).filter((networkConfiguration) => + existingNetworkConfiguration + ? networkConfiguration.chainId !== + existingNetworkConfiguration.chainId + : true, + ); + for (const networkConfiguration of networkConfigurationsForOtherChains) { + const rpcEndpoint = networkConfiguration.rpcEndpoints.find( + (existingRpcEndpoint) => + URI.equal(rpcEndpointFields.url, existingRpcEndpoint.url), + ); + if (rpcEndpoint) { + throw new Error( + mode === 'update' + ? // False negative - these are strings. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `Could not update network to point to same RPC endpoint as existing network for chain ${networkConfiguration.chainId} ('${networkConfiguration.name}')` + : // False negative - these are strings. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `Could not add network that points to same RPC endpoint as existing network for chain ${networkConfiguration.chainId} ('${networkConfiguration.name}')`, + ); + } + } + } + + if ( + [...new Set(networkFields.rpcEndpoints)].length < + networkFields.rpcEndpoints.length + ) { + throw new Error( + `${errorMessagePrefix}: Each entry in rpcEndpoints must be unique`, + ); + } + + const networkClientIds = networkFields.rpcEndpoints + .map((rpcEndpoint) => + 'networkClientId' in rpcEndpoint + ? rpcEndpoint.networkClientId + : undefined, + ) + .filter( + (networkClientId): networkClientId is NetworkClientId => + networkClientId !== undefined, + ); + if ([...new Set(networkClientIds)].length < networkClientIds.length) { + throw new Error( + `${errorMessagePrefix}: Each entry in rpcEndpoints must have a unique networkClientId`, + ); + } + + const infuraRpcEndpoints = networkFields.rpcEndpoints.filter( + (rpcEndpointFields): rpcEndpointFields is InfuraRpcEndpoint => + rpcEndpointFields.type === RpcEndpointType.Infura, + ); + if (infuraRpcEndpoints.length > 1) { + throw new Error( + `${errorMessagePrefix}: There cannot be more than one Infura RPC endpoint`, + ); + } + + const soleInfuraRpcEndpoint = infuraRpcEndpoints[0]; + if (soleInfuraRpcEndpoint) { + const infuraNetworkName = deriveInfuraNetworkNameFromRpcEndpointUrl( + soleInfuraRpcEndpoint.url, + ); + const infuraNetworkNickname = NetworkNickname[infuraNetworkName]; + const infuraChainId = ChainId[infuraNetworkName]; + if (networkFields.chainId !== infuraChainId) { + throw new Error( + mode === 'add' + ? // This is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `Could not add network with chain ID ${networkFields.chainId} and Infura RPC endpoint for '${infuraNetworkNickname}' which represents ${infuraChainId}, as the two conflict` + : // This is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `Could not update network with chain ID ${networkFields.chainId} and Infura RPC endpoint for '${infuraNetworkNickname}' which represents ${infuraChainId}, as the two conflict`, + ); + } + } + + if ( + networkFields.rpcEndpoints[networkFields.defaultRpcEndpointIndex] === + undefined + ) { + throw new Error( + `${errorMessagePrefix}: \`defaultRpcEndpointIndex\` must refer to an entry in \`rpcEndpoints\``, + ); + } + } + /** * Before accessing or switching the network, the registry of network clients * needs to be populated. Otherwise, `#applyNetworkSelection` and diff --git a/packages/network-controller/tests/NetworkController.test.ts b/packages/network-controller/tests/NetworkController.test.ts index 0823e1ec2f3..ca56a620e65 100644 --- a/packages/network-controller/tests/NetworkController.test.ts +++ b/packages/network-controller/tests/NetworkController.test.ts @@ -2907,7 +2907,7 @@ describe('NetworkController', () => { ), ).toThrow( new Error( - `Cannot add network: Invalid \`chainId\` '12345' (must start with "0x" and not exceed the maximum)`, + `Could not add network: Invalid \`chainId\` '12345' (must start with "0x" and not exceed the maximum)`, ), ); }); @@ -2925,7 +2925,7 @@ describe('NetworkController', () => { ), ).toThrow( new Error( - `Cannot add network: Invalid \`chainId\` '0xfffffffffffed' (must start with "0x" and not exceed the maximum)`, + `Could not add network: Invalid \`chainId\` '0xfffffffffffed' (must start with "0x" and not exceed the maximum)`, ), ); }); @@ -2941,7 +2941,7 @@ describe('NetworkController', () => { ), ).toThrow( new Error( - 'Cannot add network: `rpcEndpoints` must be a non-empty array', + 'Could not add network: `rpcEndpoints` must be a non-empty array', ), ); }); @@ -2961,7 +2961,7 @@ describe('NetworkController', () => { ), ).toThrow( new Error( - "Cannot add network: An entry in `rpcEndpoints` has invalid URL 'clearly-not-a-url'", + "Could not add network: An entry in `rpcEndpoints` has invalid URL 'clearly-not-a-url'", ), ); }); @@ -2984,7 +2984,7 @@ describe('NetworkController', () => { ), ).toThrow( new Error( - 'Cannot add network: Each entry in rpcEndpoints must have a unique URL', + 'Could not add network: Each entry in rpcEndpoints must have a unique URL', ), ); }); @@ -3007,7 +3007,7 @@ describe('NetworkController', () => { ), ).toThrow( new Error( - 'Cannot add network: Each entry in rpcEndpoints must have a unique URL', + 'Could not add network: Each entry in rpcEndpoints must have a unique URL', ), ); }); @@ -3066,7 +3066,7 @@ describe('NetworkController', () => { ).toThrow( // This is a string. // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - `Cannot add network that points to same RPC endpoint as existing network for chain ${infuraChainId} ('${infuraNetworkNickname}')`, + `Could not add network that points to same RPC endpoint as existing network for chain ${infuraChainId} ('${infuraNetworkNickname}')`, ); }, ); @@ -3105,7 +3105,7 @@ describe('NetworkController', () => { }), ), ).toThrow( - "Cannot add network that points to same RPC endpoint as existing network for chain 0x2448 ('Some Network')", + "Could not add network that points to same RPC endpoint as existing network for chain 0x2448 ('Some Network')", ); }, ); @@ -3122,7 +3122,7 @@ describe('NetworkController', () => { }), ), ).toThrow( - 'Cannot add network: Each entry in rpcEndpoints must be unique', + 'Could not add network: Each entry in rpcEndpoints must be unique', ); }); }); @@ -3153,7 +3153,7 @@ describe('NetworkController', () => { }), ), ).toThrow( - 'Cannot add network: There cannot be more than one Infura RPC endpoint', + 'Could not add network: There cannot be more than one Infura RPC endpoint', ); }, ); @@ -3177,7 +3177,7 @@ describe('NetworkController', () => { ), ).toThrow( new Error( - 'Cannot add network: `defaultRpcEndpointIndex` must refer to an entry in `rpcEndpoints`', + 'Could not add network: `defaultRpcEndpointIndex` must refer to an entry in `rpcEndpoints`', ), ); }); @@ -3210,7 +3210,7 @@ describe('NetworkController', () => { ).toThrow( // This is a string. // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - `Cannot add network for chain ${infuraChainId} as another network for that chain already exists ('${infuraNetworkNickname}')`, + `Could not add network for chain ${infuraChainId} as another network for that chain already exists ('${infuraNetworkNickname}')`, ); }, ); @@ -3237,7 +3237,7 @@ describe('NetworkController', () => { }), ), ).toThrow( - `Cannot add network for chain 0x1337 as another network for that chain already exists ('Some Network')`, + `Could not add network for chain 0x1337 as another network for that chain already exists ('Some Network')`, ); }, ); @@ -3605,7 +3605,7 @@ describe('NetworkController', () => { }), ).toThrow( new Error( - "Cannot add network with chain ID 0x1337 and Infura RPC endpoint for 'Mainnet' which represents 0x1, as the two conflict", + "Could not add network with chain ID 0x1337 and Infura RPC endpoint for 'Mainnet' which represents 0x1, as the two conflict", ), ); }, @@ -3845,7 +3845,7 @@ describe('NetworkController', () => { ), ).toThrow( new Error( - `Cannot update network: New \`chainId\` '12345' is invalid (must start with "0x" and not exceed the maximum)`, + `Could not update network: Invalid \`chainId\` '12345' (must start with "0x" and not exceed the maximum)`, ), ); }, @@ -3877,7 +3877,7 @@ describe('NetworkController', () => { ), ).toThrow( new Error( - `Cannot update network: New \`chainId\` '0xfffffffffffed' is invalid (must start with "0x" and not exceed the maximum)`, + `Could not update network: Invalid \`chainId\` '0xfffffffffffed' (must start with "0x" and not exceed the maximum)`, ), ); }, @@ -3907,7 +3907,7 @@ describe('NetworkController', () => { ), ).toThrow( new Error( - 'Cannot update network: `rpcEndpoints` must be a non-empty array', + 'Could not update network: `rpcEndpoints` must be a non-empty array', ), ); }, @@ -3939,7 +3939,7 @@ describe('NetworkController', () => { }), ).toThrow( new Error( - "Cannot update network: An entry in `rpcEndpoints` has invalid URL 'clearly-not-a-url'", + "Could not update network: An entry in `rpcEndpoints` has invalid URL 'clearly-not-a-url'", ), ); }, @@ -3972,7 +3972,7 @@ describe('NetworkController', () => { }), ).toThrow( new Error( - "Cannot update network: RPC endpoint 'https://foo.com' refers to network client 'not-a-real-network-client-id' that does not exist", + "Could not update network: RPC endpoint 'https://foo.com' refers to network client 'not-a-real-network-client-id' that does not exist", ), ); }, @@ -4005,7 +4005,7 @@ describe('NetworkController', () => { }), ).toThrow( new Error( - 'Cannot update network: Each entry in rpcEndpoints must have a unique URL', + 'Could not update network: Each entry in rpcEndpoints must have a unique URL', ), ); }, @@ -4038,7 +4038,7 @@ describe('NetworkController', () => { }), ).toThrow( new Error( - 'Cannot update network: Each entry in rpcEndpoints must have a unique URL', + 'Could not update network: Each entry in rpcEndpoints must have a unique URL', ), ); }, @@ -4111,7 +4111,7 @@ describe('NetworkController', () => { ).toThrow( // This is a string. // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - `Cannot update network to point to same RPC endpoint as existing network for chain ${infuraChainId} ('${infuraNetworkNickname}')`, + `Could not update network to point to same RPC endpoint as existing network for chain ${infuraChainId} ('${infuraNetworkNickname}')`, ); }, ); @@ -4159,7 +4159,7 @@ describe('NetworkController', () => { }), ).toThrow( new Error( - "Cannot update network to point to same RPC endpoint as existing network for chain 0x2448 ('Some Network')", + "Could not update network to point to same RPC endpoint as existing network for chain 0x2448 ('Some Network')", ), ); }, @@ -4187,7 +4187,7 @@ describe('NetworkController', () => { }), ).toThrow( new Error( - 'Cannot update network: Each entry in rpcEndpoints must be unique', + 'Could not update network: Each entry in rpcEndpoints must be unique', ), ); }, @@ -4225,7 +4225,7 @@ describe('NetworkController', () => { }), ).toThrow( new Error( - 'Cannot update network: Each entry in rpcEndpoints must have a unique networkClientId', + 'Could not update network: Each entry in rpcEndpoints must have a unique networkClientId', ), ); }, @@ -4264,7 +4264,7 @@ describe('NetworkController', () => { }), ).toThrow( new Error( - "Cannot update network to point to same RPC endpoint as existing network for chain 0x5 ('Goerli')", + "Could not update network to point to same RPC endpoint as existing network for chain 0x5 ('Goerli')", ), ); }, @@ -4300,7 +4300,7 @@ describe('NetworkController', () => { }), ).toThrow( new Error( - 'Cannot update network: `defaultRpcEndpointIndex` must refer to an entry in `rpcEndpoints`', + 'Could not update network: `defaultRpcEndpointIndex` must refer to an entry in `rpcEndpoints`', ), ); }, @@ -4977,7 +4977,7 @@ describe('NetworkController', () => { ], }), ).toThrow( - "Cannot update network to point to same RPC endpoint as existing network for chain 0x1 ('Mainnet')", + "Could not update network to point to same RPC endpoint as existing network for chain 0x1 ('Mainnet')", ); }, ); @@ -5693,7 +5693,7 @@ describe('NetworkController', () => { new Error( // This is a string. // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - `Cannot update network to point to same RPC endpoint as existing network for chain ${anotherInfuraChainId} ('${anotherInfuraNetworkNickname}')`, + `Could not update network to point to same RPC endpoint as existing network for chain ${anotherInfuraChainId} ('${anotherInfuraNetworkNickname}')`, ), ); }, @@ -6028,7 +6028,7 @@ describe('NetworkController', () => { new Error( // This is a string. // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - `Cannot update network with chain ID 0x1337 and Infura RPC endpoint for '${infuraNetworkNickname}' which represents ${infuraChainId}, as the two conflict`, + `Could not update network with chain ID 0x1337 and Infura RPC endpoint for '${infuraNetworkNickname}' which represents ${infuraChainId}, as the two conflict`, ), ); }, @@ -6394,7 +6394,7 @@ describe('NetworkController', () => { new Error( // This is a string. // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - `Cannot update network with chain ID ${anotherInfuraChainId} and Infura RPC endpoint for '${infuraNetworkNickname}' which represents ${infuraChainId}, as the two conflict`, + `Could not update network with chain ID ${anotherInfuraChainId} and Infura RPC endpoint for '${infuraNetworkNickname}' which represents ${infuraChainId}, as the two conflict`, ), ); }, @@ -6759,7 +6759,7 @@ describe('NetworkController', () => { }), ).toThrow( new Error( - "Cannot update network to point to same RPC endpoint as existing network for chain 0x5 ('Goerli')", + "Could not update network to point to same RPC endpoint as existing network for chain 0x5 ('Goerli')", ), ); }, From 14f44a977f01e8523985d0d6bed55c7b33da4a0a Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Wed, 10 Jul 2024 16:32:52 -0600 Subject: [PATCH 22/49] Extract creating network configurations and network clients --- .../src/NetworkController.ts | 328 ++++++++++-------- 1 file changed, 192 insertions(+), 136 deletions(-) diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index f3d0a399bed..b9bad983bdb 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -1425,11 +1425,7 @@ export class NetworkController extends BaseController< * @see {@link NetworkConfiguration} */ addNetwork(fields: AddNetworkFields): NetworkConfiguration { - const { - chainId, - nativeCurrency, - rpcEndpoints: setOfRpcEndpointFields, - } = fields; + const { rpcEndpoints: setOfRpcEndpointFields } = fields; const rpcEndpointUrls = setOfRpcEndpointFields.map( (rpcEndpointFields) => rpcEndpointFields.url, ); @@ -1470,55 +1466,29 @@ export class NetworkController extends BaseController< ); } - const newRpcEndpoints = setOfRpcEndpointFields.map( + const rpcEndpointOperations = setOfRpcEndpointFields.map( (defaultOrCustomRpcEndpointFields) => { - if (defaultOrCustomRpcEndpointFields.type === RpcEndpointType.Custom) { - return { - ...defaultOrCustomRpcEndpointFields, - networkClientId: uuidV4(), - }; - } - return defaultOrCustomRpcEndpointFields; + const rpcEndpoint = + defaultOrCustomRpcEndpointFields.type === RpcEndpointType.Custom + ? { + ...defaultOrCustomRpcEndpointFields, + networkClientId: uuidV4(), + } + : defaultOrCustomRpcEndpointFields; + return { + type: 'add' as const, + value: rpcEndpoint, + }; }, ); - for (const rpcEndpoint of newRpcEndpoints) { - if (rpcEndpoint.type === RpcEndpointType.Infura) { - autoManagedNetworkClientRegistry[NetworkClientType.Infura][ - rpcEndpoint.networkClientId - ] = createAutoManagedNetworkClient({ - chainId, - infuraProjectId: this.#infuraProjectId, - network: rpcEndpoint.networkClientId, - ticker: nativeCurrency, - type: NetworkClientType.Infura, - }); - } else { - autoManagedNetworkClientRegistry[NetworkClientType.Custom][ - rpcEndpoint.networkClientId - ] = createAutoManagedNetworkClient({ - chainId, - rpcUrl: rpcEndpoint.url, - ticker: nativeCurrency, - type: NetworkClientType.Custom, - }); - } - } - - const newNetworkConfiguration = { - ...fields, - rpcEndpoints: newRpcEndpoints, - }; - - this.update((state) => { - state.networkConfigurationsByChainId[chainId] = newNetworkConfiguration; + const newNetworkConfiguration = this.#applyNetworkConfigurationChanges({ + mode: 'add', + networkFields: fields, + rpcEndpointOperations, + autoManagedNetworkClientRegistry, }); - this.#networkConfigurationsByNetworkClientId = - buildNetworkConfigurationsByNetworkClientId( - this.state.networkConfigurationsByChainId, - ); - this.messagingSystem.publish( `${controllerName}:networkAdded`, newNetworkConfiguration, @@ -1559,11 +1529,8 @@ export class NetworkController extends BaseController< } const existingChainId = chainId; - const { - chainId: newChainId, - nativeCurrency: newNativeTokenName, - rpcEndpoints: setOfNewRpcEndpointFields, - } = fields; + const { chainId: newChainId, rpcEndpoints: setOfNewRpcEndpointFields } = + fields; const autoManagedNetworkClientRegistry = this.#ensureAutoManagedNetworkClientRegistryPopulated(); @@ -1576,7 +1543,7 @@ export class NetworkController extends BaseController< autoManagedNetworkClientRegistry, }); - const operations: { + const rpcEndpointOperations: { type: 'add' | 'remove' | 'noop'; value: RpcEndpoint; }[] = []; @@ -1591,7 +1558,7 @@ export class NetworkController extends BaseController< newRpcEndpointFields.url === existingRpcEndpoint.url, ) ) { - operations.push({ + rpcEndpointOperations.push({ type: 'remove', value: existingRpcEndpoint, }); @@ -1611,12 +1578,12 @@ export class NetworkController extends BaseController< newRpcEndpointFields.type === RpcEndpointType.Infura ? newRpcEndpointFields : { ...newRpcEndpointFields, networkClientId: uuidV4() }; - operations.push({ + rpcEndpointOperations.push({ type: 'add', value: newRpcEndpoint, }); } else { - operations.push({ + rpcEndpointOperations.push({ type: 'noop', value: existingRpcEndpoint, }); @@ -1624,7 +1591,7 @@ export class NetworkController extends BaseController< } } else { for (const existingRpcEndpoint of existingNetworkConfiguration.rpcEndpoints) { - operations.push({ + rpcEndpointOperations.push({ type: 'remove', value: existingRpcEndpoint, }); @@ -1634,70 +1601,20 @@ export class NetworkController extends BaseController< newRpcEndpointFields.type === RpcEndpointType.Infura ? newRpcEndpointFields : { ...newRpcEndpointFields, networkClientId: uuidV4() }; - operations.push({ + rpcEndpointOperations.push({ type: 'add', value: newRpcEndpoint, }); } } - const newRpcEndpoints: RpcEndpoint[] = []; - for (const operation of operations) { - if (operation.type === 'remove') { - const networkClient = this.getNetworkClientById( - operation.value.networkClientId, - ); - networkClient.destroy(); - delete autoManagedNetworkClientRegistry[ - networkClient.configuration.type - ][operation.value.networkClientId]; - } else { - if (operation.type === 'add') { - if (operation.value.type === RpcEndpointType.Infura) { - autoManagedNetworkClientRegistry[NetworkClientType.Infura][ - operation.value.networkClientId - ] = createAutoManagedNetworkClient({ - type: NetworkClientType.Infura, - chainId: newChainId, - network: operation.value.networkClientId, - infuraProjectId: this.#infuraProjectId, - ticker: newNativeTokenName, - }); - } else { - autoManagedNetworkClientRegistry[NetworkClientType.Custom][ - operation.value.networkClientId - ] = createAutoManagedNetworkClient({ - type: NetworkClientType.Custom, - chainId: newChainId, - rpcUrl: operation.value.url, - ticker: newNativeTokenName, - }); - } - } - newRpcEndpoints.push(operation.value); - } - } - - const updatedNetworkConfiguration = { - ...fields, - rpcEndpoints: newRpcEndpoints, - }; - - this.update((state) => { - if (newChainId !== existingChainId) { - delete state.networkConfigurationsByChainId[existingChainId]; - } - - state.networkConfigurationsByChainId[newChainId] = - updatedNetworkConfiguration; + return this.#applyNetworkConfigurationChanges({ + mode: 'update', + networkFields: fields, + rpcEndpointOperations, + autoManagedNetworkClientRegistry, + existingNetworkConfiguration, }); - - this.#networkConfigurationsByNetworkClientId = - buildNetworkConfigurationsByNetworkClientId( - this.state.networkConfigurationsByChainId, - ); - - return updatedNetworkConfiguration; } /** @@ -1731,28 +1648,20 @@ export class NetworkController extends BaseController< const autoManagedNetworkClientRegistry = this.#ensureAutoManagedNetworkClientRegistryPopulated(); - for (const rpcEndpoint of existingNetworkConfiguration.rpcEndpoints) { - if (rpcEndpoint.type === RpcEndpointType.Infura) { - autoManagedNetworkClientRegistry[NetworkClientType.Infura][ - rpcEndpoint.networkClientId - ].destroy(); - delete autoManagedNetworkClientRegistry[NetworkClientType.Infura][ - rpcEndpoint.networkClientId - ]; - } else { - autoManagedNetworkClientRegistry[NetworkClientType.Custom][ - rpcEndpoint.networkClientId - ].destroy(); - delete autoManagedNetworkClientRegistry[NetworkClientType.Custom][ - rpcEndpoint.networkClientId - ]; - } - } + const rpcEndpointOperations = existingNetworkConfiguration.rpcEndpoints.map( + (rpcEndpoint) => { + return { + type: 'remove' as const, + value: rpcEndpoint, + }; + }, + ); - this.update((state) => { - delete state.networkConfigurationsByChainId[ - existingNetworkConfiguration.chainId - ]; + this.#applyNetworkConfigurationChanges({ + mode: 'remove', + rpcEndpointOperations, + autoManagedNetworkClientRegistry, + existingNetworkConfiguration, }); } @@ -2024,6 +1933,153 @@ export class NetworkController extends BaseController< } } + /** + * Carries out the work necessary to add or update a network. + * + * - When adding a new network, a set of fields is used to register the new + * network configuration in state and a set of operations is used to register + * network clients for new RPC endpoints. + * - When updating an existing network, a set of fields is used to register + * the new network configuration (removing the existing network configuration + * if the chain ID has changed) and a set of operations is used to register + * network clients for new RPC endpoints and destroy network clients for + * removed RPC endpoints. + * + * @param args - The arguments to this function. + * @returns The new network configuration when `args.mode` is 'add', or the + * updated network configuration when `args.mode` is 'update'. + */ + #applyNetworkConfigurationChanges( + args: + | { + mode: 'add'; + autoManagedNetworkClientRegistry: AutoManagedNetworkClientRegistry; + rpcEndpointOperations: { type: 'add'; value: RpcEndpoint }[]; + networkFields: AddNetworkFields; + } + | { + mode: 'update'; + autoManagedNetworkClientRegistry: AutoManagedNetworkClientRegistry; + rpcEndpointOperations: { + type: 'add' | 'remove' | 'noop'; + value: RpcEndpoint; + }[]; + networkFields: UpdateNetworkFields; + existingNetworkConfiguration: NetworkConfiguration; + }, + ): NetworkConfiguration; + + /** + * Carries out the work necessary to remove a network. + * + * - When removing an existing network, the corresponding network + * configuration is removed from state, and a set of operations is used to + * destroy all network clients for that network configuration. + * + * @param args - The arguments to this function. + * @returns null. + */ + #applyNetworkConfigurationChanges(args: { + mode: 'remove'; + autoManagedNetworkClientRegistry: AutoManagedNetworkClientRegistry; + rpcEndpointOperations: { type: 'remove'; value: RpcEndpoint }[]; + existingNetworkConfiguration: NetworkConfiguration; + }): null; + + #applyNetworkConfigurationChanges( + args: { + rpcEndpointOperations: { + type: 'add' | 'remove' | 'noop'; + value: RpcEndpoint; + }[]; + autoManagedNetworkClientRegistry: AutoManagedNetworkClientRegistry; + } & ( + | { mode: 'add'; networkFields: AddNetworkFields } + | { + mode: 'update'; + networkFields: UpdateNetworkFields; + existingNetworkConfiguration: NetworkConfiguration; + } + | { + mode: 'remove'; + existingNetworkConfiguration: NetworkConfiguration; + } + ), + ): NetworkConfiguration | null { + const { mode, rpcEndpointOperations, autoManagedNetworkClientRegistry } = + args; + + for (const rpcEndpointOperation of rpcEndpointOperations) { + if (rpcEndpointOperation.type === 'remove') { + const networkClient = this.getNetworkClientById( + rpcEndpointOperation.value.networkClientId, + ); + networkClient.destroy(); + delete autoManagedNetworkClientRegistry[ + networkClient.configuration.type + ][rpcEndpointOperation.value.networkClientId]; + } else if (mode !== 'remove' && rpcEndpointOperation.type === 'add') { + if (rpcEndpointOperation.value.type === RpcEndpointType.Infura) { + autoManagedNetworkClientRegistry[NetworkClientType.Infura][ + rpcEndpointOperation.value.networkClientId + ] = createAutoManagedNetworkClient({ + type: NetworkClientType.Infura, + chainId: args.networkFields.chainId, + network: rpcEndpointOperation.value.networkClientId, + infuraProjectId: this.#infuraProjectId, + ticker: args.networkFields.nativeCurrency, + }); + } else { + autoManagedNetworkClientRegistry[NetworkClientType.Custom][ + rpcEndpointOperation.value.networkClientId + ] = createAutoManagedNetworkClient({ + type: NetworkClientType.Custom, + chainId: args.networkFields.chainId, + rpcUrl: rpcEndpointOperation.value.url, + ticker: args.networkFields.nativeCurrency, + }); + } + } + } + + const newRpcEndpoints = rpcEndpointOperations + .filter((rpcEndpointOperation) => rpcEndpointOperation.type !== 'remove') + .map((rpcEndpointOperation) => rpcEndpointOperation.value); + + const updatedNetworkConfiguration = + mode === 'remove' + ? null + : { + ...args.networkFields, + rpcEndpoints: newRpcEndpoints, + }; + + this.update((state) => { + if ( + mode === 'remove' || + (mode === 'update' && + args.networkFields.chainId !== + args.existingNetworkConfiguration.chainId) + ) { + delete state.networkConfigurationsByChainId[ + args.existingNetworkConfiguration.chainId + ]; + } + + if (mode !== 'remove' && updatedNetworkConfiguration !== null) { + state.networkConfigurationsByChainId[args.networkFields.chainId] = + updatedNetworkConfiguration; + } + }); + + this.#networkConfigurationsByNetworkClientId = + buildNetworkConfigurationsByNetworkClientId( + this.state.networkConfigurationsByChainId, + ); + + return updatedNetworkConfiguration; + } + /** * Before accessing or switching the network, the registry of network clients * needs to be populated. Otherwise, `#applyNetworkSelection` and From cb28629bc29043a8434c7a7286c7eafa1c98f421 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Thu, 11 Jul 2024 21:46:01 -0600 Subject: [PATCH 23/49] Add ability to specify replacement selected RPC endpoint --- packages/network-controller/CHANGELOG.md | 1 + .../src/NetworkController.ts | 839 ++- .../tests/NetworkController.test.ts | 5120 ++++++++++++++--- 3 files changed, 4869 insertions(+), 1091 deletions(-) diff --git a/packages/network-controller/CHANGELOG.md b/packages/network-controller/CHANGELOG.md index 8eadfbd6820..c7c40c16bf6 100644 --- a/packages/network-controller/CHANGELOG.md +++ b/packages/network-controller/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `addNetwork`, which replaces one half of `upsertNetworkConfiguration` and can be used to add new network clients for a chain ([#4268](https://github.com/MetaMask/core/pull/4286)) - It's worth noting that this method now publishes a `NetworkController:networkAdded` event instead of calling a `trackMetaMetricsEvent` callback. It is expected that you will subscribe to this event and create a MetaMetrics event yourself. - Add `updateNetwork`, which replaces one half of `upsertNetworkConfiguration` and can be used to recreate the network clients for an existing chain based on an updated configuration ([#4268](https://github.com/MetaMask/core/pull/4286)) + - Note that it is not possible to remove the RPC endpoint from a network configuration that is currently represented by the globally selected network client. To prevent an error, you'll need to detect when such a removal is occurring and pass the `replacementSelectedRpcEndpointIndex` to `updateNetwork`. It will then switch to the designated RPC endpoint's network client on your behalf. - Add `removeNetwork`, which replaces `removeNetworkConfiguration` and can be used to remove existing network clients for a chain ([#4268](https://github.com/MetaMask/core/pull/4286)) - Add `getDefaultNetworkControllerState` function, which replaces `defaultState` and matches patterns in other controllers ([#4268](https://github.com/MetaMask/core/pull/4286)) - Add `RpcEndpointType` type ([#4268](https://github.com/MetaMask/core/pull/4286)) diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index b9bad983bdb..148e37e9aed 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -27,6 +27,7 @@ import { isPlainObject, } from '@metamask/utils'; import { strict as assert } from 'assert'; +import type { Draft } from 'immer'; import type { Logger } from 'loglevel'; import * as URI from 'uri-js'; import { inspect } from 'util'; @@ -96,7 +97,7 @@ export enum RpcEndpointType { * representing it in the URL as `{infuraProjectId}`, which we replace this when * create network clients. But we need to know somehow that we only need to do * this replacement for Infura endpoints and not custom endpoints — hence the - * type. + * separate type. */ export type InfuraRpcEndpoint = { /** @@ -105,18 +106,22 @@ export type InfuraRpcEndpoint = { name?: string; /** * The identifier for the network client that has been created for this RPC - * endpoint. + * endpoint. This is also used to uniquely identify the RPC endpoint in a + * set of RPC endpoints as well: once assigned, it is used to determine + * whether the `name`, `type`, or `url` of the RPC endpoint has changed. */ - networkClientId: InfuraNetworkType; + networkClientId: BuiltInNetworkClientId; /** * The type of this endpoint, always "default". */ type: RpcEndpointType.Infura; /** - * The URL of the endpoint. Expected to be a sort of template with the text - * `{infuraProjectId}`, which will get replaced with the Infura project ID. + * The URL of the endpoint. Expected to be a template with the string + * `{infuraProjectId}`, which will get replaced with the Infura project ID + * when the network client is created. */ - url: string; + // TODO: Link this to networkClientId + url: `https://${InfuraNetworkType}.infura.io/v3/{infuraProjectId}`; }; /** @@ -130,9 +135,11 @@ export type CustomRpcEndpoint = { name?: string; /** * The identifier for the network client that has been created for this RPC - * endpoint. + * endpoint. This is also used to uniquely identify the RPC endpoint in a + * set of RPC endpoints as well: once assigned, it is used to determine + * whether the `name`, `type`, or `url` of the RPC endpoint has changed. */ - networkClientId: string; + networkClientId: CustomNetworkClientId; /** * The type of this endpoint, always "custom". */ @@ -515,7 +522,8 @@ function getDefaultNetworkConfigurationsByChainId(): Record< const chainId = ChainId[infuraNetworkType]; // False negative - this is a string. // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - const rpcEndpointUrl = `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`; + const rpcEndpointUrl = + `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}` as const; const networkConfiguration: NetworkConfiguration = { blockExplorerUrls: [], @@ -579,6 +587,76 @@ export type AutoManagedNetworkClientRegistry = { [NetworkClientType.Custom]: AutoManagedCustomNetworkClientRegistry; }; +/** + * Instructs `addNetwork` and `updateNetwork` to create a network client for an + * RPC endpoint. + * + * @see {@link NetworkClientOperation} + */ +type AddNetworkClientOperation = { + type: 'add'; + rpcEndpoint: RpcEndpoint; +}; + +/** + * Instructs `updateNetwork` and `removeNetwork` to remove a network client for + * an RPC endpoint. + * + * @see {@link NetworkClientOperation} + */ +type RemoveNetworkClientOperation = { + type: 'remove'; + rpcEndpoint: RpcEndpoint; +}; + +/** + * Instructs `addNetwork` and `updateNetwork` to replace the network client for + * an RPC endpoint. + * + * @see {@link NetworkClientOperation} + */ +type ReplaceNetworkClientOperation = { + type: 'replace'; + oldRpcEndpoint: RpcEndpoint; + newRpcEndpoint: RpcEndpoint; +}; + +/** + * Instructs `addNetwork` and `updateNetwork` not to do anything with an RPC + * endpoint, as far as the network client registry is concerned. + * + * @see {@link NetworkClientOperation} + */ +type NoopNetworkClientOperation = { + type: 'noop'; + rpcEndpoint: RpcEndpoint; +}; + +/* eslint-disable jsdoc/check-indentation */ +/** + * Instructs `addNetwork`, `updateNetwork`, and `removeNetwork` how to + * update the network client registry. + * + * - When `addNetwork` is called, represents a network client that should be + * created for a new RPC endpoint. + * - When `removeNetwork` is called, represents a network client that should be + * destroyed for a previously existing RPC endpoint. + * - When `updateNetwork` is called, represents either: + * - a network client that should be added for a new RPC endpoint + * - a network client that should be removed for a previously existing RPC + * endpoint + * - a network client that should be replaced for an RPC endpoint that was + * changed in a non-major way, or + * - a network client that should be unchanged for an RPC endpoint that was + * also unchanged. + */ +/* eslint-enable jsdoc/check-indentation */ +type NetworkClientOperation = + | AddNetworkClientOperation + | RemoveNetworkClientOperation + | ReplaceNetworkClientOperation + | NoopNetworkClientOperation; + /** * Determines whether the given URL is valid by attempting to parse it. * @@ -656,13 +734,15 @@ function validateNetworkControllerState(state: NetworkState) { ); } - if ( - networkConfiguration.blockExplorerUrls.length > 0 && - (networkConfiguration.defaultBlockExplorerUrlIndex === undefined || - networkConfiguration.blockExplorerUrls[ - networkConfiguration.defaultBlockExplorerUrlIndex - ] === undefined) - ) { + const isInvalidDefaultBlockExplorerUrlIndex = + networkConfiguration.blockExplorerUrls.length > 0 + ? networkConfiguration.defaultBlockExplorerUrlIndex === undefined || + networkConfiguration.blockExplorerUrls[ + networkConfiguration.defaultBlockExplorerUrlIndex + ] === undefined + : networkConfiguration.defaultBlockExplorerUrlIndex !== undefined; + + if (isInvalidDefaultBlockExplorerUrlIndex) { throw new Error( `NetworkController state has invalid \`networkConfigurationsByChainId\`: Network configuration '${networkConfiguration.name}' has a \`defaultBlockExplorerUrlIndex\` that does not refer to an entry in \`blockExplorerUrls\``, ); @@ -991,13 +1071,20 @@ export class NetworkController extends BaseController< * @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). + * @param options - Options for this method. + * @param options.updateState - Allows for updating state. */ - async #refreshNetwork(networkClientId: string) { + async #refreshNetwork( + networkClientId: string, + options: { + updateState?: (state: Draft) => void; + } = {}, + ) { this.messagingSystem.publish( 'NetworkController:networkWillChange', this.state, ); - this.#applyNetworkSelection(networkClientId); + this.#applyNetworkSelection(networkClientId, options); this.messagingSystem.publish( 'NetworkController:networkDidChange', this.state, @@ -1268,14 +1355,21 @@ export class NetworkController extends BaseController< * * @param networkClientId - The ID of a network client that will be used to * make requests. + * @param options - Options for this method. + * @param options.updateState - Allows for updating state. * @throws if no network client is associated with the given * network client ID. */ - async setActiveNetwork(networkClientId: string) { + async setActiveNetwork( + networkClientId: string, + options: { + updateState?: (state: Draft) => void; + } = {}, + ) { this.#previouslySelectedNetworkClientId = this.state.selectedNetworkClientId; - await this.#refreshNetwork(networkClientId); + await this.#refreshNetwork(networkClientId, options); } /** @@ -1426,9 +1520,6 @@ export class NetworkController extends BaseController< */ addNetwork(fields: AddNetworkFields): NetworkConfiguration { const { rpcEndpoints: setOfRpcEndpointFields } = fields; - const rpcEndpointUrls = setOfRpcEndpointFields.map( - (rpcEndpointFields) => rpcEndpointFields.url, - ); const autoManagedNetworkClientRegistry = this.#ensureAutoManagedNetworkClientRegistryPopulated(); @@ -1440,33 +1531,7 @@ export class NetworkController extends BaseController< autoManagedNetworkClientRegistry, }); - let conflict: - | { - networkConfiguration: NetworkConfiguration; - rpcEndpoint: RpcEndpoint; - } - | undefined; - for (const existingNetworkConfiguration of Object.values( - this.state.networkConfigurationsByChainId, - )) { - const existingRpcEndpoint = - existingNetworkConfiguration.rpcEndpoints.find((rpcEndpoint) => - rpcEndpointUrls.includes(rpcEndpoint.url), - ); - if (existingRpcEndpoint) { - conflict = { - networkConfiguration: existingNetworkConfiguration, - rpcEndpoint: existingRpcEndpoint, - }; - } - } - if (conflict !== undefined) { - throw new Error( - `Could not add network that points to same RPC endpoint as existing network for chain ${conflict.networkConfiguration.chainId} ("${conflict.networkConfiguration.name}")`, - ); - } - - const rpcEndpointOperations = setOfRpcEndpointFields.map( + const networkClientOperations = setOfRpcEndpointFields.map( (defaultOrCustomRpcEndpointFields) => { const rpcEndpoint = defaultOrCustomRpcEndpointFields.type === RpcEndpointType.Custom @@ -1477,17 +1542,29 @@ export class NetworkController extends BaseController< : defaultOrCustomRpcEndpointFields; return { type: 'add' as const, - value: rpcEndpoint, + rpcEndpoint, }; }, ); - const newNetworkConfiguration = this.#applyNetworkConfigurationChanges({ - mode: 'add', + const newNetworkConfiguration = + this.#determineNetworkConfigurationToPersist({ + networkFields: fields, + networkClientOperations, + }); + this.#registerNetworkClientsAsNeeded({ networkFields: fields, - rpcEndpointOperations, + networkClientOperations, autoManagedNetworkClientRegistry, }); + this.update((state) => { + this.#updateNetworkConfigurations({ + state, + mode: 'add', + networkFields: fields, + networkConfigurationToPersist: newNetworkConfiguration, + }); + }); this.messagingSystem.publish( `${controllerName}:networkAdded`, @@ -1499,9 +1576,9 @@ export class NetworkController extends BaseController< /** * Updates the configuration for a previously stored network filed under the - * given chain ID, creating and registering new network clients to represent - * RPC endpoints that have been added and destroying and unregistering - * existing network clients for RPC endpoints that have been removed. + * given chain ID, creating + registering new network clients to represent RPC + * endpoints that have been added and destroying + unregistering existing + * network clients for RPC endpoints that have been removed. * * Note that if `chainId` is changed, then all network clients associated with * that chain will be removed and re-added, even if none of the RPC endpoints @@ -1510,21 +1587,32 @@ export class NetworkController extends BaseController< * @param chainId - The chain ID associated with an existing network. * @param fields - The object that describes the updates to the network/chain, * including the new set of RPC endpoints which should front that chain. + * @param options - Options to provide. + * @param options.replacementSelectedRpcEndpointIndex - Usually you cannot + * remove an RPC endpoint that is being represented by the currently selected + * network client. This option allows you to specify another RPC endpoint + * (either an existing one or a new one) that should be used to select a new + * network instead. * @returns The updated network configuration. * @throws if `chainId` does not refer to an existing network configuration, - * or if any part of `fields` would produce invalid state. + * if any part of `fields` would produce invalid state, etc. * @see {@link NetworkConfiguration} */ - updateNetwork( + async updateNetwork( chainId: Hex, fields: UpdateNetworkFields, - ): NetworkConfiguration { + { + replacementSelectedRpcEndpointIndex, + }: { replacementSelectedRpcEndpointIndex?: number } = {}, + ): Promise { const existingNetworkConfiguration = this.state.networkConfigurationsByChainId[chainId]; if (existingNetworkConfiguration === undefined) { throw new Error( - `Cannot find network configuration for chain ${inspect(chainId)}`, + `Could not update network: Cannot find network configuration for chain ${inspect( + chainId, + )}`, ); } @@ -1543,78 +1631,226 @@ export class NetworkController extends BaseController< autoManagedNetworkClientRegistry, }); - const rpcEndpointOperations: { - type: 'add' | 'remove' | 'noop'; - value: RpcEndpoint; - }[] = []; - if (newChainId === existingChainId) { - for (const existingRpcEndpoint of existingNetworkConfiguration.rpcEndpoints) { - if ( - !setOfNewRpcEndpointFields.some( - (newRpcEndpointFields) => - newRpcEndpointFields.networkClientId === - existingRpcEndpoint.networkClientId && - newRpcEndpointFields.type === existingRpcEndpoint.type && - newRpcEndpointFields.url === existingRpcEndpoint.url, - ) - ) { - rpcEndpointOperations.push({ - type: 'remove', - value: existingRpcEndpoint, - }); - } - } - for (const newRpcEndpointFields of setOfNewRpcEndpointFields) { - const existingRpcEndpoint = - existingNetworkConfiguration.rpcEndpoints.find( - (rpcEndpoint) => + const networkClientOperations: NetworkClientOperation[] = []; + + for (const newRpcEndpointFields of setOfNewRpcEndpointFields) { + const existingRpcEndpointForNoop = + existingNetworkConfiguration.rpcEndpoints.find((rpcEndpoint) => { + return ( + rpcEndpoint.type === newRpcEndpointFields.type && + rpcEndpoint.url === newRpcEndpointFields.url && + (rpcEndpoint.networkClientId === + newRpcEndpointFields.networkClientId || + newRpcEndpointFields.networkClientId === undefined) + ); + }); + const existingRpcEndpointForReplaceWhenChainChanged = + existingNetworkConfiguration.rpcEndpoints.find((rpcEndpoint) => { + return ( + (rpcEndpoint.type === RpcEndpointType.Infura && + newRpcEndpointFields.type === RpcEndpointType.Infura) || + (rpcEndpoint.type === newRpcEndpointFields.type && rpcEndpoint.networkClientId === newRpcEndpointFields.networkClientId && - rpcEndpoint.type === newRpcEndpointFields.type && - rpcEndpoint.url === newRpcEndpointFields.url, + rpcEndpoint.url === newRpcEndpointFields.url) ); - if (existingRpcEndpoint === undefined) { - const newRpcEndpoint = - newRpcEndpointFields.type === RpcEndpointType.Infura - ? newRpcEndpointFields - : { ...newRpcEndpointFields, networkClientId: uuidV4() }; - rpcEndpointOperations.push({ - type: 'add', - value: newRpcEndpoint, - }); + }); + const existingRpcEndpointForReplaceWhenChainNotChanged = + existingNetworkConfiguration.rpcEndpoints.find((rpcEndpoint) => { + return ( + rpcEndpoint.type === newRpcEndpointFields.type && + (rpcEndpoint.url === newRpcEndpointFields.url || + rpcEndpoint.networkClientId === + newRpcEndpointFields.networkClientId) + ); + }); + + if ( + newChainId !== existingChainId && + existingRpcEndpointForReplaceWhenChainChanged !== undefined + ) { + const newRpcEndpoint = + newRpcEndpointFields.type === RpcEndpointType.Infura + ? newRpcEndpointFields + : { ...newRpcEndpointFields, networkClientId: uuidV4() }; + + networkClientOperations.push({ + type: 'replace' as const, + oldRpcEndpoint: existingRpcEndpointForReplaceWhenChainChanged, + newRpcEndpoint, + }); + } else if (existingRpcEndpointForNoop !== undefined) { + let newRpcEndpoint; + if (existingRpcEndpointForNoop.type === RpcEndpointType.Infura) { + newRpcEndpoint = existingRpcEndpointForNoop; } else { - rpcEndpointOperations.push({ - type: 'noop', - value: existingRpcEndpoint, + // `networkClientId` shouldn't be missing at this point; if it is, + // that's a mistake, so fill it back in + newRpcEndpoint = Object.assign({}, newRpcEndpointFields, { + networkClientId: existingRpcEndpointForNoop.networkClientId, }); } - } - } else { - for (const existingRpcEndpoint of existingNetworkConfiguration.rpcEndpoints) { - rpcEndpointOperations.push({ - type: 'remove', - value: existingRpcEndpoint, + networkClientOperations.push({ + type: 'noop' as const, + rpcEndpoint: newRpcEndpoint, }); - } - for (const newRpcEndpointFields of setOfNewRpcEndpointFields) { + } else if ( + existingRpcEndpointForReplaceWhenChainNotChanged !== undefined + ) { + let newRpcEndpoint; + /* istanbul ignore if */ + if (newRpcEndpointFields.type === RpcEndpointType.Infura) { + // This case can't actually happen. If we're here, it means that some + // part of the RPC endpoint changed. But there is no part of an Infura + // RPC endpoint that can be changed (as it would immediately make that + // RPC endpoint self-inconsistent). This is just here to appease + // TypeScript. + newRpcEndpoint = newRpcEndpointFields; + } else { + newRpcEndpoint = { + ...newRpcEndpointFields, + networkClientId: uuidV4(), + }; + } + + networkClientOperations.push({ + type: 'replace' as const, + oldRpcEndpoint: existingRpcEndpointForReplaceWhenChainNotChanged, + newRpcEndpoint, + }); + } else { const newRpcEndpoint = newRpcEndpointFields.type === RpcEndpointType.Infura ? newRpcEndpointFields : { ...newRpcEndpointFields, networkClientId: uuidV4() }; - rpcEndpointOperations.push({ - type: 'add', - value: newRpcEndpoint, - }); + const networkClientOperation = { + type: 'add' as const, + rpcEndpoint: newRpcEndpoint, + }; + networkClientOperations.push(networkClientOperation); } } - return this.#applyNetworkConfigurationChanges({ - mode: 'update', + for (const existingRpcEndpoint of existingNetworkConfiguration.rpcEndpoints) { + if ( + !networkClientOperations.some((networkClientOperation) => { + const otherRpcEndpoint = + networkClientOperation.type === 'replace' + ? networkClientOperation.oldRpcEndpoint + : networkClientOperation.rpcEndpoint; + return ( + otherRpcEndpoint.type === existingRpcEndpoint.type && + otherRpcEndpoint.networkClientId === + existingRpcEndpoint.networkClientId && + otherRpcEndpoint.url === existingRpcEndpoint.url + ); + }) + ) { + const networkClientOperation = { + type: 'remove' as const, + rpcEndpoint: existingRpcEndpoint, + }; + networkClientOperations.push(networkClientOperation); + } + } + + const updatedNetworkConfiguration = + this.#determineNetworkConfigurationToPersist({ + networkFields: fields, + networkClientOperations, + }); + + if ( + replacementSelectedRpcEndpointIndex === undefined && + !updatedNetworkConfiguration.rpcEndpoints.some((rpcEndpoint) => { + return ( + rpcEndpoint.networkClientId === this.state.selectedNetworkClientId + ); + }) && + !networkClientOperations.some((networkClientOperation) => { + return ( + networkClientOperation.type === 'replace' && + networkClientOperation.oldRpcEndpoint.networkClientId === + this.state.selectedNetworkClientId + ); + }) + ) { + throw new Error( + `Could not update network: Cannot update RPC endpoints in such a way that the selected network '${this.state.selectedNetworkClientId}' would be removed without a replacement. Choose a different RPC endpoint as the selected network via the \`replacementSelectedRpcEndpointIndex\` option.`, + ); + } + + this.#registerNetworkClientsAsNeeded({ networkFields: fields, - rpcEndpointOperations, + networkClientOperations, + autoManagedNetworkClientRegistry, + }); + + const replacementSelectedRpcEndpointWithIndex = networkClientOperations + .map( + (networkClientOperation, index) => + [networkClientOperation, index] as const, + ) + .find(([networkClientOperation, _index]) => { + return ( + networkClientOperation.type === 'replace' && + networkClientOperation.oldRpcEndpoint.networkClientId === + this.state.selectedNetworkClientId + ); + }); + const correctedReplacementSelectedRpcEndpointIndex = + replacementSelectedRpcEndpointIndex ?? + replacementSelectedRpcEndpointWithIndex?.[1]; + + let rpcEndpointToSelect: RpcEndpoint | undefined; + if (correctedReplacementSelectedRpcEndpointIndex !== undefined) { + rpcEndpointToSelect = + updatedNetworkConfiguration.rpcEndpoints[ + correctedReplacementSelectedRpcEndpointIndex + ]; + + // TODO: Test + if (rpcEndpointToSelect === undefined) { + throw new Error( + `Could not update network: \`replacementSelectedRpcEndpointIndex\` ${correctedReplacementSelectedRpcEndpointIndex} does not refer to an entry in \`rpcEndpoints\``, + ); + } + } + + // TODO: Test + if ( + rpcEndpointToSelect && + rpcEndpointToSelect.networkClientId !== this.state.selectedNetworkClientId + ) { + await this.setActiveNetwork(rpcEndpointToSelect.networkClientId, { + updateState: (state) => { + this.#updateNetworkConfigurations({ + state, + mode: 'update', + networkFields: fields, + networkConfigurationToPersist: updatedNetworkConfiguration, + existingNetworkConfiguration, + }); + }, + }); + } else { + this.update((state) => { + this.#updateNetworkConfigurations({ + state, + mode: 'update', + networkFields: fields, + networkConfigurationToPersist: updatedNetworkConfiguration, + existingNetworkConfiguration, + }); + }); + } + + this.#unregisterNetworkClientsAsNeeded({ + networkClientOperations, autoManagedNetworkClientRegistry, - existingNetworkConfiguration, }); + + return updatedNetworkConfiguration; } /** @@ -1648,20 +1884,24 @@ export class NetworkController extends BaseController< const autoManagedNetworkClientRegistry = this.#ensureAutoManagedNetworkClientRegistryPopulated(); - const rpcEndpointOperations = existingNetworkConfiguration.rpcEndpoints.map( - (rpcEndpoint) => { + const networkClientOperations = + existingNetworkConfiguration.rpcEndpoints.map((rpcEndpoint) => { return { type: 'remove' as const, - value: rpcEndpoint, + rpcEndpoint, }; - }, - ); + }); - this.#applyNetworkConfigurationChanges({ - mode: 'remove', - rpcEndpointOperations, + this.#unregisterNetworkClientsAsNeeded({ + networkClientOperations, autoManagedNetworkClientRegistry, - existingNetworkConfiguration, + }); + this.update((state) => { + this.#updateNetworkConfigurations({ + state, + mode: 'remove', + existingNetworkConfiguration, + }); }); } @@ -1781,13 +2021,15 @@ export class NetworkController extends BaseController< } } - if ( - networkFields.blockExplorerUrls.length > 0 && - (networkFields.defaultBlockExplorerUrlIndex === undefined || - networkFields.blockExplorerUrls[ - networkFields.defaultBlockExplorerUrlIndex - ] === undefined) - ) { + const isInvalidDefaultBlockExplorerUrlIndex = + networkFields.blockExplorerUrls.length > 0 + ? networkFields.defaultBlockExplorerUrlIndex === undefined || + networkFields.blockExplorerUrls[ + networkFields.defaultBlockExplorerUrlIndex + ] === undefined + : networkFields.defaultBlockExplorerUrlIndex !== undefined; + + if (isInvalidDefaultBlockExplorerUrlIndex) { throw new Error( `${errorMessagePrefix}: \`defaultBlockExplorerUrlIndex\` must refer to an entry in \`blockExplorerUrls\``, ); @@ -1811,15 +2053,30 @@ export class NetworkController extends BaseController< ? rpcEndpointFields.networkClientId : undefined; + if ( + rpcEndpointFields.type === RpcEndpointType.Custom && + networkClientId !== undefined && + isInfuraNetworkType(networkClientId) + ) { + throw new Error( + `${errorMessagePrefix}: Custom RPC endpoint '${ + // This is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + rpcEndpointFields.url + }' has invalid network client ID '${networkClientId}'`, + ); + } + if ( mode === 'update' && networkClientId !== undefined && + rpcEndpointFields.type === RpcEndpointType.Custom && !Object.values(autoManagedNetworkClientRegistry).some( (networkClientsById) => networkClientId in networkClientsById, ) ) { throw new Error( - `Could not update network: RPC endpoint '${ + `${errorMessagePrefix}: RPC endpoint '${ // This is a string. // eslint-disable-next-line @typescript-eslint/restrict-template-expressions rpcEndpointFields.url @@ -1934,70 +2191,194 @@ export class NetworkController extends BaseController< } /** - * Carries out the work necessary to add or update a network. - * - * - When adding a new network, a set of fields is used to register the new - * network configuration in state and a set of operations is used to register - * network clients for new RPC endpoints. - * - When updating an existing network, a set of fields is used to register - * the new network configuration (removing the existing network configuration - * if the chain ID has changed) and a set of operations is used to register - * network clients for new RPC endpoints and destroy network clients for - * removed RPC endpoints. + * Constructs a network configuration that will be persisted to state when + * adding or updating a network. * * @param args - The arguments to this function. - * @returns The new network configuration when `args.mode` is 'add', or the - * updated network configuration when `args.mode` is 'update'. + * @param args.networkFields - The fields used to add or update a network. + * @param args.networkClientOperations - Operations which were calculated for + * updating the network client registry but which also map back to RPC + * endpoints (and so can be used to save those RPC endpoints). + * @returns The network configuration to persist. */ - #applyNetworkConfigurationChanges( - args: - | { - mode: 'add'; - autoManagedNetworkClientRegistry: AutoManagedNetworkClientRegistry; - rpcEndpointOperations: { type: 'add'; value: RpcEndpoint }[]; - networkFields: AddNetworkFields; - } - | { - mode: 'update'; - autoManagedNetworkClientRegistry: AutoManagedNetworkClientRegistry; - rpcEndpointOperations: { - type: 'add' | 'remove' | 'noop'; - value: RpcEndpoint; - }[]; - networkFields: UpdateNetworkFields; - existingNetworkConfiguration: NetworkConfiguration; + #determineNetworkConfigurationToPersist({ + networkFields, + networkClientOperations, + }: { + networkFields: AddNetworkFields | UpdateNetworkFields; + networkClientOperations: NetworkClientOperation[]; + }): NetworkConfiguration { + const rpcEndpointsToPersist = networkClientOperations + .filter( + ( + networkClientOperation, + ): networkClientOperation is + | AddNetworkClientOperation + | NoopNetworkClientOperation => { + return ( + networkClientOperation.type === 'add' || + networkClientOperation.type === 'noop' + ); }, - ): NetworkConfiguration; + ) + .map((networkClientOperation) => networkClientOperation.rpcEndpoint) + .concat( + networkClientOperations + .filter( + ( + networkClientOperation, + ): networkClientOperation is ReplaceNetworkClientOperation => { + return networkClientOperation.type === 'replace'; + }, + ) + .map( + (networkClientOperation) => networkClientOperation.newRpcEndpoint, + ), + ); + + return { ...networkFields, rpcEndpoints: rpcEndpointsToPersist }; + } /** - * Carries out the work necessary to remove a network. + * Creates and registers network clients using the given operations calculated + * as a part of adding or updating a network. * - * - When removing an existing network, the corresponding network - * configuration is removed from state, and a set of operations is used to - * destroy all network clients for that network configuration. + * @param args - The arguments to this function. + * @param args.networkFields - The fields used to add or update a network. + * @param args.networkClientOperations - Dictate which network clients need to + * be created. + * @param args.autoManagedNetworkClientRegistry - The network client registry + * to update. + */ + #registerNetworkClientsAsNeeded({ + networkFields, + networkClientOperations, + autoManagedNetworkClientRegistry, + }: { + networkFields: AddNetworkFields | UpdateNetworkFields; + networkClientOperations: NetworkClientOperation[]; + autoManagedNetworkClientRegistry: AutoManagedNetworkClientRegistry; + }) { + const addedRpcEndpoints = networkClientOperations + .filter( + ( + networkClientOperation, + ): networkClientOperation is AddNetworkClientOperation => { + return networkClientOperation.type === 'add'; + }, + ) + .map((networkClientOperation) => networkClientOperation.rpcEndpoint) + .concat( + networkClientOperations + .filter( + ( + networkClientOperation, + ): networkClientOperation is ReplaceNetworkClientOperation => { + return networkClientOperation.type === 'replace'; + }, + ) + .map( + (networkClientOperation) => networkClientOperation.newRpcEndpoint, + ), + ); + + for (const addedRpcEndpoint of addedRpcEndpoints) { + if (addedRpcEndpoint.type === RpcEndpointType.Infura) { + autoManagedNetworkClientRegistry[NetworkClientType.Infura][ + addedRpcEndpoint.networkClientId + ] = createAutoManagedNetworkClient({ + type: NetworkClientType.Infura, + chainId: networkFields.chainId, + network: addedRpcEndpoint.networkClientId, + infuraProjectId: this.#infuraProjectId, + ticker: networkFields.nativeCurrency, + }); + } else { + autoManagedNetworkClientRegistry[NetworkClientType.Custom][ + addedRpcEndpoint.networkClientId + ] = createAutoManagedNetworkClient({ + type: NetworkClientType.Custom, + chainId: networkFields.chainId, + rpcUrl: addedRpcEndpoint.url, + ticker: networkFields.nativeCurrency, + }); + } + } + } + + /** + * Destroys and removes network clients using the given operations calculated + * as a part of updating or removing a network. * * @param args - The arguments to this function. - * @returns null. + * @param args.networkClientOperations - Dictate which network clients to + * remove. + * @param args.autoManagedNetworkClientRegistry - The network client registry + * to update. */ - #applyNetworkConfigurationChanges(args: { - mode: 'remove'; + #unregisterNetworkClientsAsNeeded({ + networkClientOperations, + autoManagedNetworkClientRegistry, + }: { + networkClientOperations: NetworkClientOperation[]; autoManagedNetworkClientRegistry: AutoManagedNetworkClientRegistry; - rpcEndpointOperations: { type: 'remove'; value: RpcEndpoint }[]; - existingNetworkConfiguration: NetworkConfiguration; - }): null; + }) { + const removedRpcEndpoints = networkClientOperations + .filter( + ( + networkClientOperation, + ): networkClientOperation is RemoveNetworkClientOperation => { + return networkClientOperation.type === 'remove'; + }, + ) + .map((networkClientOperation) => networkClientOperation.rpcEndpoint) + .concat( + networkClientOperations + .filter( + ( + networkClientOperation, + ): networkClientOperation is ReplaceNetworkClientOperation => { + return networkClientOperation.type === 'replace'; + }, + ) + .map( + (networkClientOperation) => networkClientOperation.oldRpcEndpoint, + ), + ); - #applyNetworkConfigurationChanges( - args: { - rpcEndpointOperations: { - type: 'add' | 'remove' | 'noop'; - value: RpcEndpoint; - }[]; - autoManagedNetworkClientRegistry: AutoManagedNetworkClientRegistry; - } & ( - | { mode: 'add'; networkFields: AddNetworkFields } + for (const rpcEndpoint of removedRpcEndpoints) { + const networkClient = this.getNetworkClientById( + rpcEndpoint.networkClientId, + ); + networkClient.destroy(); + delete autoManagedNetworkClientRegistry[networkClient.configuration.type][ + rpcEndpoint.networkClientId + ]; + } + } + + /** + * Updates `networkConfigurationsByChainId` in state depending on whether a + * network is being added, updated, or removed. + * + * - The existing network configuration will be removed when a network is + * being filed under a different chain or removed. + * - A network configuration will be stored when a network is being added or + * when a network is being updated. + * + * @param args - The arguments to this function. + */ + #updateNetworkConfigurations( + args: { state: Draft } & ( + | { + mode: 'add'; + networkFields: AddNetworkFields; + networkConfigurationToPersist: NetworkConfiguration; + } | { mode: 'update'; networkFields: UpdateNetworkFields; + networkConfigurationToPersist: NetworkConfiguration; existingNetworkConfiguration: NetworkConfiguration; } | { @@ -2005,79 +2386,29 @@ export class NetworkController extends BaseController< existingNetworkConfiguration: NetworkConfiguration; } ), - ): NetworkConfiguration | null { - const { mode, rpcEndpointOperations, autoManagedNetworkClientRegistry } = - args; - - for (const rpcEndpointOperation of rpcEndpointOperations) { - if (rpcEndpointOperation.type === 'remove') { - const networkClient = this.getNetworkClientById( - rpcEndpointOperation.value.networkClientId, - ); - networkClient.destroy(); - delete autoManagedNetworkClientRegistry[ - networkClient.configuration.type - ][rpcEndpointOperation.value.networkClientId]; - } else if (mode !== 'remove' && rpcEndpointOperation.type === 'add') { - if (rpcEndpointOperation.value.type === RpcEndpointType.Infura) { - autoManagedNetworkClientRegistry[NetworkClientType.Infura][ - rpcEndpointOperation.value.networkClientId - ] = createAutoManagedNetworkClient({ - type: NetworkClientType.Infura, - chainId: args.networkFields.chainId, - network: rpcEndpointOperation.value.networkClientId, - infuraProjectId: this.#infuraProjectId, - ticker: args.networkFields.nativeCurrency, - }); - } else { - autoManagedNetworkClientRegistry[NetworkClientType.Custom][ - rpcEndpointOperation.value.networkClientId - ] = createAutoManagedNetworkClient({ - type: NetworkClientType.Custom, - chainId: args.networkFields.chainId, - rpcUrl: rpcEndpointOperation.value.url, - ticker: args.networkFields.nativeCurrency, - }); - } - } - } - - const newRpcEndpoints = rpcEndpointOperations - .filter((rpcEndpointOperation) => rpcEndpointOperation.type !== 'remove') - .map((rpcEndpointOperation) => rpcEndpointOperation.value); - - const updatedNetworkConfiguration = - mode === 'remove' - ? null - : { - ...args.networkFields, - rpcEndpoints: newRpcEndpoints, - }; + ) { + const { state, mode } = args; - this.update((state) => { - if ( - mode === 'remove' || - (mode === 'update' && - args.networkFields.chainId !== - args.existingNetworkConfiguration.chainId) - ) { - delete state.networkConfigurationsByChainId[ - args.existingNetworkConfiguration.chainId - ]; - } + if ( + mode === 'remove' || + (mode === 'update' && + args.networkFields.chainId !== + args.existingNetworkConfiguration.chainId) + ) { + delete state.networkConfigurationsByChainId[ + args.existingNetworkConfiguration.chainId + ]; + } - if (mode !== 'remove' && updatedNetworkConfiguration !== null) { - state.networkConfigurationsByChainId[args.networkFields.chainId] = - updatedNetworkConfiguration; - } - }); + if (mode === 'add' || mode === 'update') { + state.networkConfigurationsByChainId[args.networkFields.chainId] = + args.networkConfigurationToPersist; + } this.#networkConfigurationsByNetworkClientId = buildNetworkConfigurationsByNetworkClientId( this.state.networkConfigurationsByChainId, ); - - return updatedNetworkConfiguration; } /** @@ -2174,9 +2505,18 @@ export class NetworkController extends BaseController< * @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). + * @param options - Options for this method. + * @param options.updateState - Allows for updating state. * @throws if no network client could be found matching the given ID. */ - #applyNetworkSelection(networkClientId: string) { + #applyNetworkSelection( + networkClientId: string, + { + updateState, + }: { + updateState?: (state: Draft) => void; + } = {}, + ) { const autoManagedNetworkClientRegistry = this.#ensureAutoManagedNetworkClientRegistryPopulated(); @@ -2224,6 +2564,7 @@ export class NetworkController extends BaseController< EIPS: {}, }; } + updateState?.(state); }); if (this.#providerProxy) { diff --git a/packages/network-controller/tests/NetworkController.test.ts b/packages/network-controller/tests/NetworkController.test.ts index ca56a620e65..9a54fe5e64c 100644 --- a/packages/network-controller/tests/NetworkController.test.ts +++ b/packages/network-controller/tests/NetworkController.test.ts @@ -177,7 +177,7 @@ describe('NetworkController', () => { ); }); - it('throws if a network configuration has an invalid defaultBlockExplorerUrlIndex', () => { + it('throws if a network configuration has a defaultBlockExplorerUrlIndex that does not refer to an entry in blockExplorerUrls', () => { const messenger = buildMessenger(); const restrictedMessenger = buildNetworkControllerMessenger(messenger); expect( @@ -187,7 +187,7 @@ describe('NetworkController', () => { state: { networkConfigurationsByChainId: { '0x1337': buildCustomNetworkConfiguration({ - blockExplorerUrls: ['https://block.explorer'], + blockExplorerUrls: [], defaultBlockExplorerUrlIndex: 99999, chainId: '0x1337', name: 'Test Network', @@ -206,7 +206,7 @@ describe('NetworkController', () => { ); }); - it('throws if a network configuration has a non-empty blockExplorerUrls but not a defaultBlockExplorerUrlIndex', () => { + it('throws if a network configuration has a non-empty blockExplorerUrls but an absent defaultBlockExplorerUrlIndex', () => { const messenger = buildMessenger(); const restrictedMessenger = buildNetworkControllerMessenger(messenger); expect( @@ -539,6 +539,7 @@ describe('NetworkController', () => { describe('initializeProvider', () => { for (const infuraNetworkType of Object.values(InfuraNetworkType)) { const infuraChainId = ChainId[infuraNetworkType]; + // TODO: Update these names const infuraNativeTokenName = NetworksTicker[infuraNetworkType]; // False negative - this is a string. @@ -2931,6 +2932,39 @@ describe('NetworkController', () => { }); }); + it('throws if defaultBlockExplorerUrlIndex does not refer to an entry in blockExplorerUrls', async () => { + await withController(({ controller }) => { + expect(() => + controller.addNetwork( + buildAddNetworkFields({ + blockExplorerUrls: [], + defaultBlockExplorerUrlIndex: 99999, + }), + ), + ).toThrow( + new Error( + 'Could not add network: `defaultBlockExplorerUrlIndex` must refer to an entry in `blockExplorerUrls`', + ), + ); + }); + }); + + it('throws if blockExplorerUrls is non-empty, but defaultBlockExplorerUrlIndex is missing', async () => { + await withController(({ controller }) => { + expect(() => + controller.addNetwork( + buildAddNetworkFields({ + blockExplorerUrls: ['https://block.explorer'], + }), + ), + ).toThrow( + new Error( + 'Could not add network: `defaultBlockExplorerUrlIndex` must refer to an entry in `blockExplorerUrls`', + ), + ); + }); + }); + it('throws if the rpcEndpoints field is an empty array', async () => { await withController(({ controller }) => { expect(() => @@ -3250,7 +3284,7 @@ describe('NetworkController', () => { // This is a string. // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - describe(`when the given chain ID is the Infura-supported chain ${infuraChainId}`, () => { + describe(`given the ID of the Infura-supported chain ${infuraChainId}`, () => { it('creates a new network client for not only each custom RPC endpoint, but also the Infura RPC endpoint', async () => { uuidV4Mock .mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB') @@ -3566,7 +3600,7 @@ describe('NetworkController', () => { }); } - describe('when the given chain ID is not an Infura-supported chain', () => { + describe('given the ID of a non-Infura-supported chain', () => { it('throws (albeit for a different reason) if rpcEndpoints contains an Infura RPC endpoint that represents a different chain that the one being added', async () => { uuidV4Mock .mockReturnValueOnce('AAAA-AAAA-AAAA-AAAA') @@ -3807,20 +3841,86 @@ describe('NetworkController', () => { describe('updateNetwork', () => { it('throws if the given chain ID does not refer to an existing network configuration', async () => { - await withController(({ controller }) => { - expect(() => + await withController(async ({ controller }) => { + await expect( controller.updateNetwork( '0x1337', buildCustomNetworkConfiguration({ chainId: '0x1337', }), ), - ).toThrow( - new Error("Cannot find network configuration for chain '0x1337'"), + ).rejects.toThrow( + new Error( + "Could not update network: Cannot find network configuration for chain '0x1337'", + ), ); }); }); + it('throws if defaultBlockExplorerUrlIndex does not refer to an entry in blockExplorerUrls', async () => { + const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ + chainId: '0x1337', + }); + + await withController( + { + state: buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }), + }, + async ({ controller }) => { + await expect(() => + controller.updateNetwork( + '0x1337', + buildCustomNetworkConfiguration({ + blockExplorerUrls: [], + defaultBlockExplorerUrlIndex: 99999, + }), + ), + ).rejects.toThrow( + new Error( + 'Could not update network: `defaultBlockExplorerUrlIndex` must refer to an entry in `blockExplorerUrls`', + ), + ); + }, + ); + }); + + it('throws if blockExplorerUrls is non-empty, but defaultBlockExplorerUrlIndex is cleared', async () => { + const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ + blockExplorerUrls: ['https://block.explorer'], + chainId: '0x1337', + defaultBlockExplorerUrlIndex: 0, + }); + + await withController( + { + state: buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }), + }, + async ({ controller }) => { + await expect(() => + controller.updateNetwork( + '0x1337', + buildCustomNetworkConfiguration({ + ...networkConfigurationToUpdate, + defaultBlockExplorerUrlIndex: undefined, + }), + ), + ).rejects.toThrow( + new Error( + 'Could not update network: `defaultBlockExplorerUrlIndex` must refer to an entry in `blockExplorerUrls`', + ), + ); + }, + ); + }); + it('throws if the new chainId field is a string, but not a 0x-prefixed hex number', async () => { const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ chainId: '0x1337', @@ -3834,8 +3934,8 @@ describe('NetworkController', () => { }, }), }, - ({ controller }) => { - expect(() => + async ({ controller }) => { + await expect( controller.updateNetwork( '0x1337', buildCustomNetworkConfiguration({ @@ -3843,7 +3943,7 @@ describe('NetworkController', () => { chainId: '12345', }), ), - ).toThrow( + ).rejects.toThrow( new Error( `Could not update network: Invalid \`chainId\` '12345' (must start with "0x" and not exceed the maximum)`, ), @@ -3865,8 +3965,8 @@ describe('NetworkController', () => { }, }), }, - ({ controller }) => { - expect(() => + async ({ controller }) => { + await expect( controller.updateNetwork( '0x1337', buildCustomNetworkConfiguration({ @@ -3875,7 +3975,7 @@ describe('NetworkController', () => { chainId: toHex(MAX_SAFE_CHAIN_ID + 1), }), ), - ).toThrow( + ).rejects.toThrow( new Error( `Could not update network: Invalid \`chainId\` '0xfffffffffffed' (must start with "0x" and not exceed the maximum)`, ), @@ -3897,15 +3997,15 @@ describe('NetworkController', () => { }, }), }, - ({ controller }) => { - expect(() => + async ({ controller }) => { + await expect( controller.updateNetwork( '0x1337', buildNetworkConfiguration({ rpcEndpoints: [], }), ), - ).toThrow( + ).rejects.toThrow( new Error( 'Could not update network: `rpcEndpoints` must be a non-empty array', ), @@ -3914,6 +4014,39 @@ describe('NetworkController', () => { ); }); + it('throws if one of the new rpcEndpoints is custom and uses an Infura network name for networkClientId', async () => { + const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ + chainId: '0x1337', + }); + + await withController( + { + state: buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }), + }, + async ({ controller }) => { + await expect( + controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + buildUpdateNetworkCustomRpcEndpointFields({ + networkClientId: InfuraNetworkType.mainnet, + url: 'https://test.network', + }), + ], + }), + ).rejects.toThrow( + new Error( + "Could not update network: Custom RPC endpoint 'https://test.network' has invalid network client ID 'mainnet'", + ), + ); + }, + ); + }); + it('throws if one of the new rpcEndpoints has an invalid url property', async () => { const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ chainId: '0x1337', @@ -3927,8 +4060,8 @@ describe('NetworkController', () => { }, }), }, - ({ controller }) => { - expect(() => + async ({ controller }) => { + await expect( controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, rpcEndpoints: [ @@ -3937,7 +4070,7 @@ describe('NetworkController', () => { }), ], }), - ).toThrow( + ).rejects.toThrow( new Error( "Could not update network: An entry in `rpcEndpoints` has invalid URL 'clearly-not-a-url'", ), @@ -3959,8 +4092,8 @@ describe('NetworkController', () => { }, }), }, - ({ controller }) => { - expect(() => + async ({ controller }) => { + await expect( controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, rpcEndpoints: [ @@ -3970,7 +4103,7 @@ describe('NetworkController', () => { }), ], }), - ).toThrow( + ).rejects.toThrow( new Error( "Could not update network: RPC endpoint 'https://foo.com' refers to network client 'not-a-real-network-client-id' that does not exist", ), @@ -3990,8 +4123,8 @@ describe('NetworkController', () => { }, }), }, - ({ controller }) => { - expect(() => + async ({ controller }) => { + await expect( controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, rpcEndpoints: [ @@ -4003,7 +4136,7 @@ describe('NetworkController', () => { }), ], }), - ).toThrow( + ).rejects.toThrow( new Error( 'Could not update network: Each entry in rpcEndpoints must have a unique URL', ), @@ -4023,8 +4156,8 @@ describe('NetworkController', () => { }, }), }, - ({ controller }) => { - expect(() => + async ({ controller }) => { + await expect( controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, rpcEndpoints: [ @@ -4036,7 +4169,7 @@ describe('NetworkController', () => { }), ], }), - ).toThrow( + ).rejects.toThrow( new Error( 'Could not update network: Each entry in rpcEndpoints must have a unique URL', ), @@ -4046,7 +4179,14 @@ describe('NetworkController', () => { }); it('does not throw if the URLs of two or more RPC endpoints have similar paths (comparing case-insensitively)', async () => { - const networkConfigurationToUpdate = buildNetworkConfiguration(); + const networkConfigurationToUpdate = buildNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + url: 'https://foo.com/bar', + }), + ], + }); await withController( { @@ -4056,21 +4196,19 @@ describe('NetworkController', () => { }, }), }, - ({ controller }) => { - expect(() => - controller.updateNetwork('0x1337', { - ...networkConfigurationToUpdate, - defaultRpcEndpointIndex: 0, - rpcEndpoints: [ - buildUpdateNetworkCustomRpcEndpointFields({ - url: 'https://foo.com/bar', - }), - buildUpdateNetworkCustomRpcEndpointFields({ - url: 'https://foo.com/BAR', - }), - ], - }), - ).not.toThrow(); + async ({ controller }) => { + const result = await controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + defaultRpcEndpointIndex: 0, + rpcEndpoints: [ + networkConfigurationToUpdate.rpcEndpoints[0], + buildUpdateNetworkCustomRpcEndpointFields({ + url: 'https://foo.com/BAR', + }), + ], + }); + + expect(result).toBeDefined(); }, ); }); @@ -4102,13 +4240,13 @@ describe('NetworkController', () => { }, }), }, - ({ controller }) => { - expect(() => + async ({ controller }) => { + await expect( controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, rpcEndpoints: [infuraRpcEndpoint], }), - ).toThrow( + ).rejects.toThrow( // This is a string. // eslint-disable-next-line @typescript-eslint/restrict-template-expressions `Could not update network to point to same RPC endpoint as existing network for chain ${infuraChainId} ('${infuraNetworkNickname}')`, @@ -4144,8 +4282,8 @@ describe('NetworkController', () => { }, }), }, - ({ controller }) => { - expect(() => + async ({ controller }) => { + await expect( controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, rpcEndpoints: [ @@ -4157,7 +4295,7 @@ describe('NetworkController', () => { }), ], }), - ).toThrow( + ).rejects.toThrow( new Error( "Could not update network to point to same RPC endpoint as existing network for chain 0x2448 ('Some Network')", ), @@ -4177,15 +4315,15 @@ describe('NetworkController', () => { }, }), }, - ({ controller }) => { + async ({ controller }) => { const rpcEndpoint = buildUpdateNetworkCustomRpcEndpointFields(); - expect(() => + await expect( controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, defaultRpcEndpointIndex: 0, rpcEndpoints: [rpcEndpoint, rpcEndpoint], }), - ).toThrow( + ).rejects.toThrow( new Error( 'Could not update network: Each entry in rpcEndpoints must be unique', ), @@ -4211,8 +4349,8 @@ describe('NetworkController', () => { }, }), }, - ({ controller }) => { - expect(() => + async ({ controller }) => { + await expect( controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, rpcEndpoints: [ @@ -4223,7 +4361,7 @@ describe('NetworkController', () => { }), ], }), - ).toThrow( + ).rejects.toThrow( new Error( 'Could not update network: Each entry in rpcEndpoints must have a unique networkClientId', ), @@ -4256,13 +4394,13 @@ describe('NetworkController', () => { }, }), }, - ({ controller }) => { - expect(() => + async ({ controller }) => { + await expect( controller.updateNetwork(ChainId.mainnet, { ...networkConfigurationToUpdate, rpcEndpoints: [mainnetRpcEndpoint, goerliRpcEndpoint], }), - ).toThrow( + ).rejects.toThrow( new Error( "Could not update network to point to same RPC endpoint as existing network for chain 0x5 ('Goerli')", ), @@ -4292,13 +4430,13 @@ describe('NetworkController', () => { }, }), }, - ({ controller }) => { - expect(() => + async ({ controller }) => { + await expect( controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, defaultRpcEndpointIndex: 99999, }), - ).toThrow( + ).rejects.toThrow( new Error( 'Could not update network: `defaultRpcEndpointIndex` must refer to an entry in `rpcEndpoints`', ), @@ -4307,39 +4445,303 @@ describe('NetworkController', () => { ); }); - for (const infuraNetworkType of Object.values(InfuraNetworkType)) { - const infuraChainId = ChainId[infuraNetworkType]; - const infuraNativeTokenName = NetworksTicker[infuraNetworkType]; - - // False negative - this is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - describe(`if the existing chain ID is the Infura-supported chain ${infuraChainId} and is not being changed`, () => { - describe('when new custom RPC endpoints are being added', () => { - it('creates and registers new network clients for each RPC endpoint', async () => { - uuidV4Mock - .mockReturnValueOnce('AAAA-AAAA-AAAA-AAAA') - .mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB'); - const createAutoManagedNetworkClientSpy = jest.spyOn( - createAutoManagedNetworkClientModule, - 'createAutoManagedNetworkClient', - ); - const networkConfigurationToUpdate = - buildInfuraNetworkConfiguration(infuraNetworkType, { - rpcEndpoints: [buildInfuraRpcEndpoint(infuraNetworkType)], - }); + it('throws if a RPC endpoint being removed is represented by the selected network client, and replacementSelectedRpcEndpointIndex is not specified', async () => { + const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://foo.com', + }), + buildCustomRpcEndpoint({ + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://bar.com', + }), + ], + }); - await withController( - { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId( - { - networkConfigurationsByChainId: { - [infuraChainId]: networkConfigurationToUpdate, - }, - }, - ), + await withController( + { + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }, + }, + async ({ controller }) => { + await expect( + controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + rpcEndpoints: [networkConfigurationToUpdate.rpcEndpoints[1]], + }), + ).rejects.toThrow( + new Error( + "Could not update network: Cannot update RPC endpoints in such a way that the selected network 'AAAA-AAAA-AAAA-AAAA' would be removed without a replacement. Choose a different RPC endpoint as the selected network via the `replacementSelectedRpcEndpointIndex` option.", + ), + ); + }, + ); + }); + + it('throws if a RPC endpoint being removed is represented by the selected network client, and an invalid replacementSelectedRpcEndpointIndex is not specified', async () => { + const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://foo.com', + }), + buildCustomRpcEndpoint({ + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://bar.com', + }), + ], + }); + + await withController( + { + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }, + }, + async ({ controller }) => { + await expect( + controller.updateNetwork( + '0x1337', + { + ...networkConfigurationToUpdate, + rpcEndpoints: [networkConfigurationToUpdate.rpcEndpoints[1]], + }, + { replacementSelectedRpcEndpointIndex: 9999 }, + ), + ).rejects.toThrow( + new Error( + `Could not update network: \`replacementSelectedRpcEndpointIndex\` 9999 does not refer to an entry in \`rpcEndpoints\``, + ), + ); + }, + ); + }); + + for (const infuraNetworkType of Object.values(InfuraNetworkType)) { + const infuraChainId = ChainId[infuraNetworkType]; + const infuraNativeTokenName = NetworksTicker[infuraNetworkType]; + + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + describe(`if the existing chain ID is the Infura-supported chain ${infuraChainId} and is not being changed`, () => { + describe('when a new Infura RPC endpoint is being added', () => { + it('creates and registers a new network client for the RPC endpoint', async () => { + const createAutoManagedNetworkClientSpy = jest.spyOn( + createAutoManagedNetworkClientModule, + 'createAutoManagedNetworkClient', + ); + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.network', + }), + ], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId( + { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + ), + infuraProjectId: 'some-infura-project-id', + }, + async ({ controller }) => { + const infuraRpcEndpoint = + buildInfuraRpcEndpoint(infuraNetworkType); + + await controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + ...networkConfigurationToUpdate.rpcEndpoints, + infuraRpcEndpoint, + ], + }); + + // Skipping the 1st call because it's for the custom RPC + // endpoint + expect( + createAutoManagedNetworkClientSpy, + ).toHaveBeenNthCalledWith(2, { + chainId: infuraChainId, + infuraProjectId: 'some-infura-project-id', + network: infuraNetworkType, + ticker: infuraNativeTokenName, + type: NetworkClientType.Infura, + }); + + expect( + getNetworkConfigurationsByNetworkClientId( + controller.getNetworkClientRegistry(), + ), + ).toStrictEqual({ + 'AAAA-AAAA-AAAA-AAAA': { + chainId: infuraChainId, + rpcUrl: 'https://rpc.network', + ticker: infuraNativeTokenName, + type: NetworkClientType.Custom, + }, + [infuraNetworkType]: { + chainId: infuraChainId, + infuraProjectId: 'some-infura-project-id', + network: infuraNetworkType, + ticker: infuraNativeTokenName, + type: NetworkClientType.Infura, + }, + }); + }, + ); + }); + + it('stores the network configuration with the new RPC endpoint in state', async () => { + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.network', + }), + ], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId( + { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + ), + infuraProjectId: 'some-infura-project-id', + }, + async ({ controller }) => { + const infuraRpcEndpoint = + buildInfuraRpcEndpoint(infuraNetworkType); + + await controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + ...networkConfigurationToUpdate.rpcEndpoints, + infuraRpcEndpoint, + ], + }); + + expect( + controller.state.networkConfigurationsByChainId[ + infuraChainId + ], + ).toStrictEqual({ + ...networkConfigurationToUpdate, + rpcEndpoints: [ + ...networkConfigurationToUpdate.rpcEndpoints, + { + networkClientId: infuraNetworkType, + type: RpcEndpointType.Infura, + url: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`, + }, + ], + }); + }, + ); + }); + + it('returns the updated network configuration', async () => { + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.network', + }), + ], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId( + { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + ), + infuraProjectId: 'some-infura-project-id', + }, + async ({ controller }) => { + const infuraRpcEndpoint = + buildInfuraRpcEndpoint(infuraNetworkType); + + const updatedNetworkConfiguration = + await controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + ...networkConfigurationToUpdate.rpcEndpoints, + infuraRpcEndpoint, + ], + }); + + expect(updatedNetworkConfiguration).toStrictEqual({ + ...networkConfigurationToUpdate, + rpcEndpoints: [ + ...networkConfigurationToUpdate.rpcEndpoints, + { + networkClientId: infuraNetworkType, + type: RpcEndpointType.Infura, + url: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`, + }, + ], + }); + }, + ); + }); + }); + + describe('when new custom RPC endpoints are being added', () => { + it('creates and registers new network clients for each RPC endpoint', async () => { + uuidV4Mock + .mockReturnValueOnce('AAAA-AAAA-AAAA-AAAA') + .mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB'); + const createAutoManagedNetworkClientSpy = jest.spyOn( + createAutoManagedNetworkClientModule, + 'createAutoManagedNetworkClient', + ); + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + rpcEndpoints: [buildInfuraRpcEndpoint(infuraNetworkType)], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId( + { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + ), + infuraProjectId: 'some-infura-project-id', }, - ({ controller }) => { + async ({ controller }) => { const [rpcEndpoint1, rpcEndpoint2] = [ buildUpdateNetworkCustomRpcEndpointFields({ name: 'Endpoint 1', @@ -4350,10 +4752,14 @@ describe('NetworkController', () => { url: 'https://rpc.endpoint/2', }), ]; - controller.updateNetwork(infuraChainId, { + await controller.updateNetwork(infuraChainId, { ...networkConfigurationToUpdate, defaultRpcEndpointIndex: 0, - rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], + rpcEndpoints: [ + ...networkConfigurationToUpdate.rpcEndpoints, + rpcEndpoint1, + rpcEndpoint2, + ], }); // Skipping the 1st call because it's for the Infura network @@ -4378,7 +4784,14 @@ describe('NetworkController', () => { getNetworkConfigurationsByNetworkClientId( controller.getNetworkClientRegistry(), ), - ).toMatchObject({ + ).toStrictEqual({ + [infuraNetworkType]: { + chainId: infuraChainId, + infuraProjectId: 'some-infura-project-id', + network: infuraNetworkType, + ticker: infuraNativeTokenName, + type: NetworkClientType.Infura, + }, 'AAAA-AAAA-AAAA-AAAA': { chainId: infuraChainId, rpcUrl: 'https://rpc.endpoint/1', @@ -4421,8 +4834,8 @@ describe('NetworkController', () => { }, ), }, - ({ controller }) => { - controller.updateNetwork('0x1337', { + async ({ controller }) => { + await controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, defaultRpcEndpointIndex: 0, rpcEndpoints: [ @@ -4487,10 +4900,9 @@ describe('NetworkController', () => { }, ), }, - ({ controller }) => { - const updatedNetworkConfiguration = controller.updateNetwork( - '0x1337', - { + async ({ controller }) => { + const updatedNetworkConfiguration = + await controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, defaultRpcEndpointIndex: 0, rpcEndpoints: [ @@ -4504,8 +4916,7 @@ describe('NetworkController', () => { url: 'https://rpc.endpoint/3', }), ], - }, - ); + }); expect(updatedNetworkConfiguration).toStrictEqual({ ...networkConfigurationToUpdate, @@ -4529,157 +4940,1658 @@ describe('NetworkController', () => { ); }); }); - }); - describe('when some custom RPC endpoints are being removed', () => { - it('destroys and unregisters existing network clients for the RPC endpoints', async () => { - const [rpcEndpoint1, rpcEndpoint2] = [ - buildCustomRpcEndpoint({ - name: 'Endpoint 1', - networkClientId: 'AAAA-AAAA-AAAA-AAAA', - url: 'https://rpc.endpoint/1', - }), - buildCustomRpcEndpoint({ - name: 'Endpoint 2', - networkClientId: 'BBBB-BBBB-BBBB-BBBB', - url: 'https://rpc.endpoint/2', - }), - ]; - const networkConfigurationToUpdate = buildInfuraNetworkConfiguration( - infuraNetworkType, - { - rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], - }, + describe('when some custom RPC endpoints are being removed', () => { + it('destroys and unregisters existing network clients for the RPC endpoints', async () => { + const [rpcEndpoint1, rpcEndpoint2] = [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint/1', + }), + buildCustomRpcEndpoint({ + name: 'Endpoint 2', + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://rpc.endpoint/2', + }), + ]; + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], + }); + + await withController( + { + state: { + selectedNetworkClientId: 'BBBB-BBBB-BBBB-BBBB', + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + }, + async ({ controller }) => { + const existingNetworkClient = controller.getNetworkClientById( + 'AAAA-AAAA-AAAA-AAAA', + ); + const destroySpy = jest.spyOn(existingNetworkClient, 'destroy'); + + await controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + defaultRpcEndpointIndex: 0, + rpcEndpoints: [rpcEndpoint2], + }); + + expect(destroySpy).toHaveBeenCalled(); + const networkClientRegistry = + controller.getNetworkClientRegistry(); + expect(networkClientRegistry).not.toHaveProperty( + 'AAAA-AAAA-AAAA-AAAA', + ); + }, + ); + }); + + it('updates the network configuration in state', async () => { + const [rpcEndpoint1, rpcEndpoint2] = [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint/1', + }), + buildCustomRpcEndpoint({ + name: 'Endpoint 2', + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://rpc.endpoint/2', + }), + ]; + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], + }); + + await withController( + { + state: { + selectedNetworkClientId: 'BBBB-BBBB-BBBB-BBBB', + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + }, + async ({ controller }) => { + await controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + defaultRpcEndpointIndex: 0, + rpcEndpoints: [rpcEndpoint2], + }); + + expect( + controller.state.networkConfigurationsByChainId[ + infuraChainId + ], + ).toStrictEqual({ + ...networkConfigurationToUpdate, + defaultRpcEndpointIndex: 0, + rpcEndpoints: [rpcEndpoint2], + }); + }, + ); + }); + + it('returns the updated network configuration', async () => { + const [rpcEndpoint1, rpcEndpoint2] = [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint/1', + }), + buildCustomRpcEndpoint({ + name: 'Endpoint 2', + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://rpc.endpoint/2', + }), + ]; + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], + }); + + await withController( + { + state: { + selectedNetworkClientId: 'BBBB-BBBB-BBBB-BBBB', + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + }, + async ({ controller }) => { + const updatedNetworkConfiguration = + await controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + defaultRpcEndpointIndex: 0, + rpcEndpoints: [rpcEndpoint2], + }); + + expect(updatedNetworkConfiguration).toStrictEqual({ + ...networkConfigurationToUpdate, + defaultRpcEndpointIndex: 0, + rpcEndpoints: [rpcEndpoint2], + }); + }, + ); + }); + + describe('when one is represented by the selected network client (and a replacement is specified)', () => { + describe('if the new replacement RPC endpoint already exists', () => { + it('selects the network client that represents the replacement RPC endpoint', async () => { + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network/1', + }), + buildCustomRpcEndpoint({ + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://test.network/2', + }), + ], + }); + + await withController( + { + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + }, + async ({ controller }) => { + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 1', + }, + }, + ]), + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 2', + }, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + chainId: infuraChainId, + rpcUrl: 'https://test.network/1', + ticker: infuraNativeTokenName, + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + chainId: infuraChainId, + rpcUrl: 'https://test.network/2', + ticker: infuraNativeTokenName, + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); + expect(controller.state.selectedNetworkClientId).toBe( + 'AAAA-AAAA-AAAA-AAAA', + ); + const networkClient1 = + controller.getSelectedNetworkClient(); + assert(networkClient1, 'Network client is somehow unset'); + const result1 = await networkClient1.provider.request({ + method: 'test', + }); + expect(result1).toBe('test response from 1'); + + await controller.updateNetwork( + infuraChainId, + { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + networkConfigurationToUpdate.rpcEndpoints[1], + ], + }, + { + replacementSelectedRpcEndpointIndex: 0, + }, + ); + expect(controller.state.selectedNetworkClientId).toBe( + 'BBBB-BBBB-BBBB-BBBB', + ); + const networkClient2 = + controller.getSelectedNetworkClient(); + assert(networkClient2, 'Network client is somehow unset'); + const result2 = await networkClient2.provider.request({ + method: 'test', + }); + expect(result2).toBe('test response from 2'); + }, + ); + }); + + it('updates selectedNetworkClientId and networkConfigurationsByChainId at the same time', async () => { + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network/1', + }), + buildCustomRpcEndpoint({ + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://test.network/2', + }), + ], + }); + + await withController( + { + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + }, + async ({ controller, messenger }) => { + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 1', + }, + }, + ]), + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 2', + }, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + chainId: infuraChainId, + rpcUrl: 'https://test.network/1', + ticker: infuraNativeTokenName, + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + chainId: infuraChainId, + rpcUrl: 'https://test.network/2', + ticker: infuraNativeTokenName, + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); + + const promiseForStateChanges = waitForStateChanges({ + messenger, + count: 1, + }); + + await controller.updateNetwork( + infuraChainId, + { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + networkConfigurationToUpdate.rpcEndpoints[1], + ], + }, + { + replacementSelectedRpcEndpointIndex: 0, + }, + ); + const stateChanges = await promiseForStateChanges; + expect(stateChanges).toStrictEqual([ + [ + expect.any(Object), + expect.arrayContaining([ + expect.objectContaining({ + op: 'replace', + path: ['selectedNetworkClientId'], + value: 'BBBB-BBBB-BBBB-BBBB', + }), + expect.objectContaining({ + op: 'replace', + path: [ + 'networkConfigurationsByChainId', + infuraChainId, + ], + }), + ]), + ], + ]); + }, + ); + }); + }); + + describe('if the replacement RPC endpoint is being added', () => { + it('selects the network client that represents the replacement RPC endpoint', async () => { + uuidV4Mock.mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC'); + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network/1', + }), + buildCustomRpcEndpoint({ + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://test.network/2', + }), + ], + }); + + await withController( + { + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + }, + async ({ controller }) => { + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 1', + }, + }, + ]), + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 2', + }, + }, + ]), + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 3', + }, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + buildFakeClient(fakeProviders[2]), + ]; + mockCreateNetworkClient() + .calledWith({ + chainId: infuraChainId, + rpcUrl: 'https://test.network/1', + ticker: infuraNativeTokenName, + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + chainId: infuraChainId, + rpcUrl: 'https://test.network/2', + ticker: infuraNativeTokenName, + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]) + .calledWith({ + chainId: infuraChainId, + rpcUrl: 'https://test.network/3', + ticker: infuraNativeTokenName, + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[2]); + await controller.initializeProvider(); + expect(controller.state.selectedNetworkClientId).toBe( + 'AAAA-AAAA-AAAA-AAAA', + ); + const networkClient1 = + controller.getSelectedNetworkClient(); + assert(networkClient1, 'Network client is somehow unset'); + const result1 = await networkClient1.provider.request({ + method: 'test', + }); + expect(result1).toBe('test response from 1'); + + await controller.updateNetwork( + infuraChainId, + { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + buildUpdateNetworkCustomRpcEndpointFields({ + url: 'https://test.network/3', + }), + networkConfigurationToUpdate.rpcEndpoints[1], + ], + }, + { + replacementSelectedRpcEndpointIndex: 0, + }, + ); + expect(controller.state.selectedNetworkClientId).toBe( + 'CCCC-CCCC-CCCC-CCCC', + ); + const networkClient2 = + controller.getSelectedNetworkClient(); + assert(networkClient2, 'Network client is somehow unset'); + const result2 = await networkClient2.provider.request({ + method: 'test', + }); + expect(result2).toBe('test response from 3'); + }, + ); + }); + + it('updates selectedNetworkClientId and networkConfigurationsByChainId at the same time', async () => { + uuidV4Mock.mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC'); + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network/1', + }), + buildCustomRpcEndpoint({ + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://test.network/2', + }), + ], + }); + + await withController( + { + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + }, + async ({ controller, messenger }) => { + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 1', + }, + }, + ]), + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 2', + }, + }, + ]), + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 3', + }, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + buildFakeClient(fakeProviders[2]), + ]; + mockCreateNetworkClient() + .calledWith({ + chainId: infuraChainId, + rpcUrl: 'https://test.network/1', + ticker: infuraNativeTokenName, + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + chainId: infuraChainId, + rpcUrl: 'https://test.network/2', + ticker: infuraNativeTokenName, + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]) + .calledWith({ + chainId: infuraChainId, + rpcUrl: 'https://test.network/3', + ticker: infuraNativeTokenName, + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[2]); + await controller.initializeProvider(); + + const promiseForStateChanges = waitForStateChanges({ + messenger, + count: 1, + }); + + await controller.updateNetwork( + infuraChainId, + { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + buildUpdateNetworkCustomRpcEndpointFields({ + url: 'https://test.network/3', + }), + networkConfigurationToUpdate.rpcEndpoints[1], + ], + }, + { + replacementSelectedRpcEndpointIndex: 0, + }, + ); + const stateChanges = await promiseForStateChanges; + expect(stateChanges).toStrictEqual([ + [ + expect.any(Object), + expect.arrayContaining([ + expect.objectContaining({ + op: 'replace', + path: ['selectedNetworkClientId'], + value: 'CCCC-CCCC-CCCC-CCCC', + }), + expect.objectContaining({ + op: 'replace', + path: [ + 'networkConfigurationsByChainId', + infuraChainId, + ], + }), + ]), + ], + ]); + }, + ); + }); + }); + }); + }); + + describe('when the URL of an RPC endpoint is changed (using networkClientId as identification)', () => { + it('destroys and unregisters the network client for the previous version of the RPC endpoint', async () => { + uuidV4Mock.mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB'); + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + rpcEndpoints: [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint', + }), + ], + }); + + await withController( + { + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + }, + async ({ controller }) => { + mockCreateNetworkClient() + .calledWith({ + chainId: infuraChainId, + rpcUrl: 'https://some.other.url', + ticker: infuraNativeTokenName, + type: NetworkClientType.Custom, + }) + .mockReturnValue(buildFakeClient()); + const existingNetworkClient = controller.getNetworkClientById( + 'AAAA-AAAA-AAAA-AAAA', + ); + const destroySpy = jest.spyOn(existingNetworkClient, 'destroy'); + + await controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://some.other.url', + }), + ], + }); + + expect(destroySpy).toHaveBeenCalled(); + const networkClientRegistry = + controller.getNetworkClientRegistry(); + expect(networkClientRegistry).not.toHaveProperty( + 'AAAA-AAAA-AAAA-AAAA', + ); + }, + ); + }); + + it('creates and registers a network client for the new version of the RPC endpoint', async () => { + uuidV4Mock.mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB'); + const createAutoManagedNetworkClientSpy = jest.spyOn( + createAutoManagedNetworkClientModule, + 'createAutoManagedNetworkClient', + ); + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + rpcEndpoints: [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint', + }), + ], + }); + + await withController( + { + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + }, + async ({ controller }) => { + mockCreateNetworkClient() + .calledWith({ + chainId: infuraChainId, + rpcUrl: 'https://some.other.url', + ticker: infuraNativeTokenName, + type: NetworkClientType.Custom, + }) + .mockReturnValue(buildFakeClient()); + + await controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://some.other.url', + }), + ], + }); + + expect(createAutoManagedNetworkClientSpy).toHaveBeenCalledWith({ + chainId: infuraChainId, + rpcUrl: 'https://some.other.url', + ticker: infuraNativeTokenName, + type: NetworkClientType.Custom, + }); + expect( + getNetworkConfigurationsByNetworkClientId( + controller.getNetworkClientRegistry(), + ), + ).toMatchObject({ + 'BBBB-BBBB-BBBB-BBBB': { + chainId: infuraChainId, + rpcUrl: 'https://some.other.url', + ticker: infuraNativeTokenName, + type: NetworkClientType.Custom, + }, + }); + }, + ); + }); + + it('updates the network configuration in state with a new network client ID for the RPC endpoint', async () => { + uuidV4Mock.mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB'); + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + rpcEndpoints: [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint', + }), + ], + }); + + await withController( + { + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + }, + async ({ controller }) => { + mockCreateNetworkClient() + .calledWith({ + chainId: infuraChainId, + rpcUrl: 'https://some.other.url', + ticker: infuraNativeTokenName, + type: NetworkClientType.Custom, + }) + .mockReturnValue(buildFakeClient()); + + await controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://some.other.url', + }), + ], + }); + + expect( + controller.state.networkConfigurationsByChainId[ + infuraChainId + ], + ).toStrictEqual({ + ...networkConfigurationToUpdate, + rpcEndpoints: [ + { + name: 'Endpoint 1', + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + type: 'custom', + url: 'https://some.other.url', + }, + ], + }); + }, + ); + }); + + it('returns the updated network configuration', async () => { + uuidV4Mock.mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB'); + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + rpcEndpoints: [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint', + }), + ], + }); + + await withController( + { + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + }, + async ({ controller }) => { + mockCreateNetworkClient() + .calledWith({ + chainId: infuraChainId, + rpcUrl: 'https://some.other.url', + ticker: infuraNativeTokenName, + type: NetworkClientType.Custom, + }) + .mockReturnValue(buildFakeClient()); + + const updatedNetworkConfiguration = + await controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://some.other.url', + }), + ], + }); + + expect(updatedNetworkConfiguration).toStrictEqual({ + ...networkConfigurationToUpdate, + rpcEndpoints: [ + { + name: 'Endpoint 1', + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + type: 'custom', + url: 'https://some.other.url', + }, + ], + }); + }, + ); + }); + + describe('if the previous version of the RPC endpoint was represented by the selected network client', () => { + it('invisibly selects the network client for the new RPC endpoint', async () => { + uuidV4Mock.mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB'); + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + rpcEndpoints: [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint', + }), + ], + }); + + await withController( + { + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + }, + async ({ controller }) => { + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 1', + }, + }, + ]), + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 2', + }, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + chainId: infuraChainId, + rpcUrl: 'https://rpc.endpoint', + ticker: infuraNativeTokenName, + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + chainId: infuraChainId, + rpcUrl: 'https://some.other.url', + ticker: infuraNativeTokenName, + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); + expect(controller.state.selectedNetworkClientId).toBe( + 'AAAA-AAAA-AAAA-AAAA', + ); + const networkClient1 = controller.getSelectedNetworkClient(); + assert(networkClient1, 'Network client is somehow unset'); + const result1 = await networkClient1.provider.request({ + method: 'test', + }); + expect(result1).toBe('test response from 1'); + + await controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://some.other.url', + }), + ], + }); + expect(controller.state.selectedNetworkClientId).toBe( + 'BBBB-BBBB-BBBB-BBBB', + ); + const networkClient2 = controller.getSelectedNetworkClient(); + assert(networkClient2, 'Network client is somehow unset'); + const result2 = await networkClient2.provider.request({ + method: 'test', + }); + expect(result2).toBe('test response from 2'); + }, + ); + }); + + it('updates selectedNetworkClientId and networkConfigurationsByChainId at the same time', async () => { + uuidV4Mock.mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB'); + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + rpcEndpoints: [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint', + }), + ], + }); + + await withController( + { + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + }, + async ({ controller, messenger }) => { + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 1', + }, + }, + ]), + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 2', + }, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + chainId: infuraChainId, + rpcUrl: 'https://rpc.endpoint', + ticker: infuraNativeTokenName, + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + chainId: infuraChainId, + rpcUrl: 'https://some.other.url', + ticker: infuraNativeTokenName, + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); + + const promiseForStateChanges = waitForStateChanges({ + messenger, + count: 1, + }); + + await controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://some.other.url', + }), + ], + }); + const stateChanges = await promiseForStateChanges; + expect(stateChanges).toStrictEqual([ + [ + expect.any(Object), + expect.arrayContaining([ + expect.objectContaining({ + op: 'replace', + path: ['selectedNetworkClientId'], + value: 'BBBB-BBBB-BBBB-BBBB', + }), + expect.objectContaining({ + op: 'replace', + path: [ + 'networkConfigurationsByChainId', + infuraChainId, + ], + }), + ]), + ], + ]); + }, + ); + }); + }); + }); + + describe('when all of the RPC endpoints are simply being shuffled', () => { + it('does not touch the network client registry', async () => { + const [rpcEndpoint1, rpcEndpoint2, rpcEndpoint3] = [ + buildInfuraRpcEndpoint(infuraNetworkType), + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint/1', + }), + buildCustomRpcEndpoint({ + name: 'Endpoint 2', + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://rpc.endpoint/2', + }), + ]; + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + rpcEndpoints: [rpcEndpoint1, rpcEndpoint2, rpcEndpoint3], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId( + { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + ), + }, + async ({ controller }) => { + mockCreateNetworkClient().mockReturnValue(buildFakeClient()); + const networkClientRegistry = + controller.getNetworkClientRegistry(); + + await controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + rpcEndpoints: [rpcEndpoint3, rpcEndpoint1, rpcEndpoint2], + }); + + expect(controller.getNetworkClientRegistry()).toStrictEqual( + networkClientRegistry, + ); + }, + ); + }); + + it('updates the network configuration in state with the new order of RPC endpoints', async () => { + const [rpcEndpoint1, rpcEndpoint2, rpcEndpoint3] = [ + buildInfuraRpcEndpoint(infuraNetworkType), + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint/1', + }), + buildCustomRpcEndpoint({ + name: 'Endpoint 2', + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://rpc.endpoint/2', + }), + ]; + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + rpcEndpoints: [rpcEndpoint1, rpcEndpoint2, rpcEndpoint3], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId( + { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + ), + }, + async ({ controller }) => { + await controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + rpcEndpoints: [rpcEndpoint3, rpcEndpoint1, rpcEndpoint2], + }); + + expect( + controller.state.networkConfigurationsByChainId, + ).toStrictEqual({ + [infuraChainId]: { + ...networkConfigurationToUpdate, + rpcEndpoints: [rpcEndpoint3, rpcEndpoint1, rpcEndpoint2], + }, + }); + }, + ); + }); + + it('returns the network configuration with the new order of RPC endpoints', async () => { + const [rpcEndpoint1, rpcEndpoint2, rpcEndpoint3] = [ + buildInfuraRpcEndpoint(infuraNetworkType), + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint/1', + }), + buildCustomRpcEndpoint({ + name: 'Endpoint 2', + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://rpc.endpoint/2', + }), + ]; + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + rpcEndpoints: [rpcEndpoint1, rpcEndpoint2, rpcEndpoint3], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId( + { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + ), + }, + async ({ controller }) => { + const updatedNetworkConfiguration = + await controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + rpcEndpoints: [rpcEndpoint3, rpcEndpoint1, rpcEndpoint2], + }); + + expect(updatedNetworkConfiguration).toStrictEqual({ + ...networkConfigurationToUpdate, + rpcEndpoints: [rpcEndpoint3, rpcEndpoint1, rpcEndpoint2], + }); + }, + ); + }); + }); + + describe('when the networkClientId of some custom RPC endpoints are being cleared', () => { + it('does not touch the network client registry', async () => { + const [rpcEndpoint1, rpcEndpoint2] = [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint/1', + }), + buildCustomRpcEndpoint({ + name: 'Endpoint 2', + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://rpc.endpoint/2', + }), + ]; + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId( + { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + ), + }, + async ({ controller }) => { + mockCreateNetworkClient().mockReturnValue(buildFakeClient()); + const networkClientRegistry = + controller.getNetworkClientRegistry(); + + await controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + rpcEndpoint1, + { ...rpcEndpoint2, networkClientId: undefined }, + ], + }); + + expect(controller.getNetworkClientRegistry()).toStrictEqual( + networkClientRegistry, + ); + }, + ); + }); + + it('does not touch the network configuration in state, as if the network client IDs had not been cleared', async () => { + const [rpcEndpoint1, rpcEndpoint2] = [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint/1', + }), + buildCustomRpcEndpoint({ + name: 'Endpoint 2', + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://rpc.endpoint/2', + }), + ]; + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId( + { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + ), + }, + async ({ controller }) => { + const previousNetworkConfigurationsByChainId = + controller.state.networkConfigurationsByChainId; + + await controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + rpcEndpoint1, + { ...rpcEndpoint2, networkClientId: undefined }, + ], + }); + + expect( + controller.state.networkConfigurationsByChainId, + ).toStrictEqual(previousNetworkConfigurationsByChainId); + }, + ); + }); + + it('returns the network configuration, untouched', async () => { + const [rpcEndpoint1, rpcEndpoint2] = [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint/1', + }), + buildCustomRpcEndpoint({ + name: 'Endpoint 2', + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://rpc.endpoint/2', + }), + ]; + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId( + { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + ), + }, + async ({ controller }) => { + const updatedNetworkConfiguration = + await controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + rpcEndpoint1, + { ...rpcEndpoint2, networkClientId: undefined }, + ], + }); + + expect(updatedNetworkConfiguration).toStrictEqual( + networkConfigurationToUpdate, + ); + }, + ); + }); + }); + + describe('when no RPC endpoints are being changed', () => { + it('does not touch the network client registry', async () => { + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + name: 'Some Name', + rpcEndpoints: [buildInfuraRpcEndpoint(infuraNetworkType)], + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId( + { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + ), + }, + async ({ controller }) => { + mockCreateNetworkClient().mockReturnValue(buildFakeClient()); + const networkClientRegistry = + controller.getNetworkClientRegistry(); + + await controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + name: 'Some Other Name', + }); + + expect(controller.getNetworkClientRegistry()).toStrictEqual( + networkClientRegistry, + ); + }, + ); + }); + }); + }); + } + + describe('if the existing chain ID is a non-Infura-supported chain and is not being changed', () => { + it('throws (albeit for a different reason) if an Infura RPC endpoint is being added that represents a different chain than the one being updated', async () => { + const defaultRpcEndpoint = buildInfuraRpcEndpoint( + InfuraNetworkType.mainnet, + ); + const networkConfigurationToUpdate = buildNetworkConfiguration({ + chainId: '0x1337', + }); + + await withController( + { + state: + buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + [ChainId.mainnet]: buildInfuraNetworkConfiguration( + InfuraNetworkType.mainnet, + ), + }, + }), + }, + async ({ controller }) => { + await expect( + controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + ...networkConfigurationToUpdate.rpcEndpoints, + defaultRpcEndpoint, + ], + }), + ).rejects.toThrow( + "Could not update network to point to same RPC endpoint as existing network for chain 0x1 ('Mainnet')", + ); + }, + ); + }); + + describe('when new custom RPC endpoints are being added', () => { + it('creates and registers new network clients for each RPC endpoint', async () => { + uuidV4Mock + .mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB') + .mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC'); + const createAutoManagedNetworkClientSpy = jest.spyOn( + createAutoManagedNetworkClientModule, + 'createAutoManagedNetworkClient', ); + const rpcEndpoint1 = buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint/1', + }); + const networkConfigurationToUpdate = buildNetworkConfiguration({ + chainId: '0x1337', + nativeCurrency: 'TOKEN', + rpcEndpoints: [rpcEndpoint1], + }); await withController( { state: buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ networkConfigurationsByChainId: { - [infuraChainId]: networkConfigurationToUpdate, + '0x1337': networkConfigurationToUpdate, }, }), }, - ({ controller }) => { - const existingNetworkClient = controller.getNetworkClientById( - 'AAAA-AAAA-AAAA-AAAA', - ); - const destroySpy = jest.spyOn(existingNetworkClient, 'destroy'); - - controller.updateNetwork(infuraChainId, { + async ({ controller }) => { + await controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, defaultRpcEndpointIndex: 0, - rpcEndpoints: [rpcEndpoint2], + rpcEndpoints: [ + rpcEndpoint1, + buildUpdateNetworkCustomRpcEndpointFields({ + name: 'Endpoint 2', + url: 'https://rpc.endpoint/2', + }), + buildUpdateNetworkCustomRpcEndpointFields({ + name: 'Endpoint 3', + url: 'https://rpc.endpoint/3', + }), + ], + }); + + expect(createAutoManagedNetworkClientSpy).toHaveBeenCalledWith({ + chainId: '0x1337', + rpcUrl: 'https://rpc.endpoint/2', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }); + expect(createAutoManagedNetworkClientSpy).toHaveBeenCalledWith({ + chainId: '0x1337', + rpcUrl: 'https://rpc.endpoint/3', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }); + + expect( + getNetworkConfigurationsByNetworkClientId( + controller.getNetworkClientRegistry(), + ), + ).toMatchObject({ + 'AAAA-AAAA-AAAA-AAAA': { + chainId: '0x1337', + rpcUrl: 'https://rpc.endpoint/1', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }, + 'BBBB-BBBB-BBBB-BBBB': { + chainId: '0x1337', + rpcUrl: 'https://rpc.endpoint/2', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }, + 'CCCC-CCCC-CCCC-CCCC': { + chainId: '0x1337', + rpcUrl: 'https://rpc.endpoint/3', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }, }); - - expect(destroySpy).toHaveBeenCalled(); - const networkClientRegistry = - controller.getNetworkClientRegistry(); - expect(networkClientRegistry).not.toHaveProperty( - 'AAAA-AAAA-AAAA-AAAA', - ); }, ); }); - it('updates the network configuration in state', async () => { - const [rpcEndpoint1, rpcEndpoint2] = [ - buildCustomRpcEndpoint({ - name: 'Endpoint 1', - networkClientId: 'AAAA-AAAA-AAAA-AAAA', - url: 'https://rpc.endpoint/1', - }), - buildCustomRpcEndpoint({ - name: 'Endpoint 2', - networkClientId: 'BBBB-BBBB-BBBB-BBBB', - url: 'https://rpc.endpoint/2', - }), - ]; - const networkConfigurationToUpdate = buildInfuraNetworkConfiguration( - infuraNetworkType, - { - rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], - }, - ); + it('assigns the ID of the created network client to each RPC endpoint in state', async () => { + uuidV4Mock + .mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB') + .mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC'); + const rpcEndpoint1 = buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint/1', + }); + const networkConfigurationToUpdate = buildNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [rpcEndpoint1], + }); await withController( { state: buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ networkConfigurationsByChainId: { - [infuraChainId]: networkConfigurationToUpdate, + '0x1337': networkConfigurationToUpdate, }, }), }, - ({ controller }) => { - controller.updateNetwork(infuraChainId, { + async ({ controller }) => { + await controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, defaultRpcEndpointIndex: 0, - rpcEndpoints: [rpcEndpoint2], + rpcEndpoints: [ + rpcEndpoint1, + buildUpdateNetworkCustomRpcEndpointFields({ + name: 'Endpoint 2', + url: 'https://rpc.endpoint/2', + }), + buildUpdateNetworkCustomRpcEndpointFields({ + name: 'Endpoint 3', + url: 'https://rpc.endpoint/3', + }), + ], }); expect( - controller.state.networkConfigurationsByChainId[infuraChainId], + controller.state.networkConfigurationsByChainId['0x1337'], ).toStrictEqual({ ...networkConfigurationToUpdate, - defaultRpcEndpointIndex: 0, - rpcEndpoints: [rpcEndpoint2], + rpcEndpoints: [ + rpcEndpoint1, + { + name: 'Endpoint 2', + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + type: RpcEndpointType.Custom, + url: 'https://rpc.endpoint/2', + }, + { + name: 'Endpoint 3', + networkClientId: 'CCCC-CCCC-CCCC-CCCC', + type: RpcEndpointType.Custom, + url: 'https://rpc.endpoint/3', + }, + ], }); }, ); }); it('returns the updated network configuration', async () => { - const [rpcEndpoint1, rpcEndpoint2] = [ - buildCustomRpcEndpoint({ - name: 'Endpoint 1', - networkClientId: 'AAAA-AAAA-AAAA-AAAA', - url: 'https://rpc.endpoint/1', - }), - buildCustomRpcEndpoint({ - name: 'Endpoint 2', - networkClientId: 'BBBB-BBBB-BBBB-BBBB', - url: 'https://rpc.endpoint/2', - }), - ]; - const networkConfigurationToUpdate = buildInfuraNetworkConfiguration( - infuraNetworkType, - { - rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], - }, - ); + uuidV4Mock + .mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB') + .mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC'); + const rpcEndpoint1 = buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint/1', + }); + const networkConfigurationToUpdate = buildNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [rpcEndpoint1], + }); await withController( { state: buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ networkConfigurationsByChainId: { - [infuraChainId]: networkConfigurationToUpdate, + '0x1337': networkConfigurationToUpdate, }, }), }, - ({ controller }) => { - const updatedNetworkConfiguration = controller.updateNetwork( - infuraChainId, - { + async ({ controller }) => { + const updatedNetworkConfiguration = + await controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, defaultRpcEndpointIndex: 0, - rpcEndpoints: [rpcEndpoint2], - }, - ); + rpcEndpoints: [ + rpcEndpoint1, + buildUpdateNetworkCustomRpcEndpointFields({ + name: 'Endpoint 2', + url: 'https://rpc.endpoint/2', + }), + buildUpdateNetworkCustomRpcEndpointFields({ + name: 'Endpoint 3', + url: 'https://rpc.endpoint/3', + }), + ], + }); expect(updatedNetworkConfiguration).toStrictEqual({ ...networkConfigurationToUpdate, - defaultRpcEndpointIndex: 0, - rpcEndpoints: [rpcEndpoint2], + rpcEndpoints: [ + rpcEndpoint1, + { + name: 'Endpoint 2', + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + type: RpcEndpointType.Custom, + url: 'https://rpc.endpoint/2', + }, + { + name: 'Endpoint 3', + networkClientId: 'CCCC-CCCC-CCCC-CCCC', + type: RpcEndpointType.Custom, + url: 'https://rpc.endpoint/3', + }, + ], }); }, ); }); }); - describe('when the networkClientId of some custom RPC endpoints are being cleared', () => { + describe('when some custom RPC endpoints are being removed', () => { it('destroys and unregisters existing network clients for the RPC endpoints', async () => { const [rpcEndpoint1, rpcEndpoint2] = [ buildCustomRpcEndpoint({ @@ -4693,375 +6605,725 @@ describe('NetworkController', () => { url: 'https://rpc.endpoint/2', }), ]; - const networkConfigurationToUpdate = buildInfuraNetworkConfiguration( - infuraNetworkType, - { - rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], - }, - ); + const networkConfigurationToUpdate = buildNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], + }); await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ - networkConfigurationsByChainId: { - [infuraChainId]: networkConfigurationToUpdate, - }, - }), + state: { + selectedNetworkClientId: 'BBBB-BBBB-BBBB-BBBB', + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }, }, - ({ controller }) => { + async ({ controller }) => { const existingNetworkClient = controller.getNetworkClientById( - 'BBBB-BBBB-BBBB-BBBB', + 'AAAA-AAAA-AAAA-AAAA', ); const destroySpy = jest.spyOn(existingNetworkClient, 'destroy'); - controller.updateNetwork(infuraChainId, { + await controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, - rpcEndpoints: [ - rpcEndpoint1, - { ...rpcEndpoint2, networkClientId: undefined }, - ], + defaultRpcEndpointIndex: 0, + rpcEndpoints: [rpcEndpoint2], }); expect(destroySpy).toHaveBeenCalled(); const networkClientRegistry = controller.getNetworkClientRegistry(); expect(networkClientRegistry).not.toHaveProperty( - 'BBBB-BBBB-BBBB-BBBB', - ); - }, - ); - }); - - it('creates and registers new network clients for the RPC endpoints', async () => { - uuidV4Mock.mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC'); - const createAutoManagedNetworkClientSpy = jest.spyOn( - createAutoManagedNetworkClientModule, - 'createAutoManagedNetworkClient', - ); - const [rpcEndpoint1, rpcEndpoint2] = [ - buildCustomRpcEndpoint({ - networkClientId: 'AAAA-AAAA-AAAA-AAAA', - url: 'https://test.endpoint/1', - }), - buildCustomRpcEndpoint({ - networkClientId: 'BBBB-BBBB-BBBB-BBBB', - url: 'https://test.endpoint/2', - }), - ]; - const networkConfigurationToUpdate = buildInfuraNetworkConfiguration( - infuraNetworkType, - { - rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], - }, - ); - - await withController( - { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ - networkConfigurationsByChainId: { - [infuraChainId]: networkConfigurationToUpdate, - }, - }), - }, - ({ controller }) => { - controller.updateNetwork(infuraChainId, { - ...networkConfigurationToUpdate, - rpcEndpoints: [ - rpcEndpoint1, - { ...rpcEndpoint2, networkClientId: undefined }, - ], - }); - - expect(createAutoManagedNetworkClientSpy).toHaveBeenCalledWith({ - chainId: infuraChainId, - rpcUrl: 'https://test.endpoint/2', - ticker: infuraNativeTokenName, - type: NetworkClientType.Custom, - }); - - const networkConfigurationsByNetworkClientId = - getNetworkConfigurationsByNetworkClientId( - controller.getNetworkClientRegistry(), - ); - expect(networkConfigurationsByNetworkClientId).not.toHaveProperty( - 'BBBB-BBBB-BBBB-BBBB', + 'AAAA-AAAA-AAAA-AAAA', ); - expect(networkConfigurationsByNetworkClientId).toMatchObject({ - 'CCCC-CCCC-CCCC-CCCC': { - chainId: infuraChainId, - rpcUrl: 'https://test.endpoint/2', - ticker: infuraNativeTokenName, - type: NetworkClientType.Custom, - }, - }); }, ); }); - it('assigns the IDs of the new network clients to the RPC endpoints in state', async () => { - uuidV4Mock.mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC'); + it('updates the network configuration in state', async () => { const [rpcEndpoint1, rpcEndpoint2] = [ buildCustomRpcEndpoint({ + name: 'Endpoint 1', networkClientId: 'AAAA-AAAA-AAAA-AAAA', - url: 'https://test.endpoint/1', + url: 'https://rpc.endpoint/1', }), buildCustomRpcEndpoint({ + name: 'Endpoint 2', networkClientId: 'BBBB-BBBB-BBBB-BBBB', - url: 'https://test.endpoint/2', + url: 'https://rpc.endpoint/2', }), ]; - const networkConfigurationToUpdate = buildInfuraNetworkConfiguration( - infuraNetworkType, - { - rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], - }, - ); + const networkConfigurationToUpdate = buildNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], + }); await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ - networkConfigurationsByChainId: { - [infuraChainId]: networkConfigurationToUpdate, - }, - }), + state: { + selectedNetworkClientId: 'BBBB-BBBB-BBBB-BBBB', + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }, }, - ({ controller }) => { - controller.updateNetwork(infuraChainId, { + async ({ controller }) => { + await controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, - rpcEndpoints: [ - rpcEndpoint1, - { ...rpcEndpoint2, networkClientId: undefined }, - ], + defaultRpcEndpointIndex: 0, + rpcEndpoints: [rpcEndpoint2], }); expect( - controller.state.networkConfigurationsByChainId[infuraChainId], + controller.state.networkConfigurationsByChainId['0x1337'], ).toStrictEqual({ ...networkConfigurationToUpdate, - rpcEndpoints: [ - rpcEndpoint1, - { - ...rpcEndpoint2, - networkClientId: 'CCCC-CCCC-CCCC-CCCC', - }, - ], + defaultRpcEndpointIndex: 0, + rpcEndpoints: [rpcEndpoint2], }); }, ); }); it('returns the updated network configuration', async () => { - uuidV4Mock.mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC'); const [rpcEndpoint1, rpcEndpoint2] = [ buildCustomRpcEndpoint({ + name: 'Endpoint 1', networkClientId: 'AAAA-AAAA-AAAA-AAAA', - url: 'https://test.endpoint/1', + url: 'https://rpc.endpoint/1', }), buildCustomRpcEndpoint({ + name: 'Endpoint 2', networkClientId: 'BBBB-BBBB-BBBB-BBBB', - url: 'https://test.endpoint/2', + url: 'https://rpc.endpoint/2', }), ]; - const networkConfigurationToUpdate = buildInfuraNetworkConfiguration( - infuraNetworkType, + const networkConfigurationToUpdate = buildNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], + }); + + await withController( { - rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], + state: { + selectedNetworkClientId: 'BBBB-BBBB-BBBB-BBBB', + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }, + }, + async ({ controller }) => { + const updatedNetworkConfiguration = + await controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + defaultRpcEndpointIndex: 0, + rpcEndpoints: [rpcEndpoint2], + }); + + expect(updatedNetworkConfiguration).toStrictEqual({ + ...networkConfigurationToUpdate, + defaultRpcEndpointIndex: 0, + rpcEndpoints: [rpcEndpoint2], + }); }, ); + }); - await withController( - { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ - networkConfigurationsByChainId: { - [infuraChainId]: networkConfigurationToUpdate, + describe('when one is represented by the selected network client (and a replacement is specified)', () => { + describe('if the replacement RPC endpoint already exists', () => { + it('selects the network client that represents the replacement RPC endpoint', async () => { + const networkConfigurationToUpdate = + buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeCurrency: 'TOKEN', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network/1', + }), + buildCustomRpcEndpoint({ + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://test.network/2', + }), + ], + }); + + await withController( + { + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, }, - }), - }, - ({ controller }) => { - const updatedNetworkConfiguration = controller.updateNetwork( - infuraChainId, + }, + async ({ controller }) => { + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 1', + }, + }, + ]), + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 2', + }, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://test.network/1', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://test.network/2', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); + expect(controller.state.selectedNetworkClientId).toBe( + 'AAAA-AAAA-AAAA-AAAA', + ); + const networkClient1 = controller.getSelectedNetworkClient(); + assert(networkClient1, 'Network client is somehow unset'); + const result1 = await networkClient1.provider.request({ + method: 'test', + }); + expect(result1).toBe('test response from 1'); + + await controller.updateNetwork( + '0x1337', + { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + networkConfigurationToUpdate.rpcEndpoints[1], + ], + }, + { + replacementSelectedRpcEndpointIndex: 0, + }, + ); + expect(controller.state.selectedNetworkClientId).toBe( + 'BBBB-BBBB-BBBB-BBBB', + ); + const networkClient2 = controller.getSelectedNetworkClient(); + assert(networkClient2, 'Network client is somehow unset'); + const result2 = await networkClient2.provider.request({ + method: 'test', + }); + expect(result2).toBe('test response from 2'); + }, + ); + }); + + it('updates selectedNetworkClientId and networkConfigurationsByChainId at the same time', async () => { + const networkConfigurationToUpdate = + buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeCurrency: 'TOKEN', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network/1', + }), + buildCustomRpcEndpoint({ + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://test.network/2', + }), + ], + }); + + await withController( { - ...networkConfigurationToUpdate, + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }, + }, + async ({ controller, messenger }) => { + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 1', + }, + }, + ]), + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 2', + }, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://test.network/1', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://test.network/2', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); + + const promiseForStateChanges = waitForStateChanges({ + messenger, + count: 1, + }); + + await controller.updateNetwork( + '0x1337', + { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + networkConfigurationToUpdate.rpcEndpoints[1], + ], + }, + { + replacementSelectedRpcEndpointIndex: 0, + }, + ); + const stateChanges = await promiseForStateChanges; + expect(stateChanges).toStrictEqual([ + [ + expect.any(Object), + expect.arrayContaining([ + expect.objectContaining({ + op: 'replace', + path: ['selectedNetworkClientId'], + value: 'BBBB-BBBB-BBBB-BBBB', + }), + expect.objectContaining({ + op: 'replace', + path: ['networkConfigurationsByChainId', '0x1337'], + }), + ]), + ], + ]); + }, + ); + }); + }); + + describe('if the replacement RPC endpoint is being added', () => { + it('selects the network client that represents the replacement RPC endpoint', async () => { + uuidV4Mock.mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC'); + const networkConfigurationToUpdate = + buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeCurrency: 'TOKEN', rpcEndpoints: [ - rpcEndpoint1, - { ...rpcEndpoint2, networkClientId: undefined }, + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network/1', + }), + buildCustomRpcEndpoint({ + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://test.network/2', + }), + ], + }); + + await withController( + { + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }, + }, + async ({ controller }) => { + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 1', + }, + }, + ]), + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 2', + }, + }, + ]), + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 3', + }, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + buildFakeClient(fakeProviders[2]), + ]; + mockCreateNetworkClient() + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://test.network/1', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://test.network/2', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]) + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://test.network/3', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[2]); + await controller.initializeProvider(); + expect(controller.state.selectedNetworkClientId).toBe( + 'AAAA-AAAA-AAAA-AAAA', + ); + const networkClient1 = controller.getSelectedNetworkClient(); + assert(networkClient1, 'Network client is somehow unset'); + const result1 = await networkClient1.provider.request({ + method: 'test', + }); + expect(result1).toBe('test response from 1'); + + await controller.updateNetwork( + '0x1337', + { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + buildUpdateNetworkCustomRpcEndpointFields({ + url: 'https://test.network/3', + }), + networkConfigurationToUpdate.rpcEndpoints[1], + ], + }, + { + replacementSelectedRpcEndpointIndex: 0, + }, + ); + expect(controller.state.selectedNetworkClientId).toBe( + 'CCCC-CCCC-CCCC-CCCC', + ); + const networkClient2 = controller.getSelectedNetworkClient(); + assert(networkClient2, 'Network client is somehow unset'); + const result2 = await networkClient2.provider.request({ + method: 'test', + }); + expect(result2).toBe('test response from 3'); + }, + ); + }); + + it('updates selectedNetworkClientId and networkConfigurationsByChainId at the same time', async () => { + uuidV4Mock.mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC'); + const networkConfigurationToUpdate = + buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeCurrency: 'TOKEN', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network/1', + }), + buildCustomRpcEndpoint({ + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://test.network/2', + }), ], + }); + + await withController( + { + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }, + }, + async ({ controller, messenger }) => { + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 1', + }, + }, + ]), + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 2', + }, + }, + ]), + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 3', + }, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + buildFakeClient(fakeProviders[2]), + ]; + mockCreateNetworkClient() + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://test.network/1', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://test.network/2', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]) + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://test.network/3', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[2]); + await controller.initializeProvider(); + + const promiseForStateChanges = waitForStateChanges({ + messenger, + count: 1, + }); + + await controller.updateNetwork( + '0x1337', + { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + buildUpdateNetworkCustomRpcEndpointFields({ + url: 'https://test.network/3', + }), + networkConfigurationToUpdate.rpcEndpoints[1], + ], + }, + { + replacementSelectedRpcEndpointIndex: 0, + }, + ); + const stateChanges = await promiseForStateChanges; + expect(stateChanges).toStrictEqual([ + [ + expect.any(Object), + expect.arrayContaining([ + expect.objectContaining({ + op: 'replace', + path: ['selectedNetworkClientId'], + value: 'CCCC-CCCC-CCCC-CCCC', + }), + expect.objectContaining({ + op: 'replace', + path: ['networkConfigurationsByChainId', '0x1337'], + }), + ]), + ], + ]); }, ); + }); + }); + }); + }); + + describe('when the URL of an RPC endpoint is changed (using networkClientId as identification)', () => { + it('destroys and unregisters the network client for the previous version of the RPC endpoint', async () => { + uuidV4Mock.mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB'); + const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ + nativeCurrency: 'TOKEN', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint', + }), + ], + }); + + await withController( + { + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }, + }, + async ({ controller }) => { + mockCreateNetworkClient() + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://some.other.url', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(buildFakeClient()); + const existingNetworkClient = controller.getNetworkClientById( + 'AAAA-AAAA-AAAA-AAAA', + ); + const destroySpy = jest.spyOn(existingNetworkClient, 'destroy'); - expect(updatedNetworkConfiguration).toStrictEqual({ + await controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, rpcEndpoints: [ - rpcEndpoint1, - { - ...rpcEndpoint2, - networkClientId: 'CCCC-CCCC-CCCC-CCCC', - }, + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://some.other.url', + }), ], }); - }, - ); - }); - }); - describe('when no RPC endpoints are being changed', () => { - it('does not touch the network client registry', async () => { - const networkConfigurationToUpdate = buildInfuraNetworkConfiguration( - infuraNetworkType, - { - name: 'Some Name', - rpcEndpoints: [buildInfuraRpcEndpoint(infuraNetworkType)], - }, - ); - - await withController( - { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ - networkConfigurationsByChainId: { - [infuraChainId]: networkConfigurationToUpdate, - }, - }), - }, - ({ controller }) => { - mockCreateNetworkClient().mockReturnValue(buildFakeClient()); + expect(destroySpy).toHaveBeenCalled(); const networkClientRegistry = controller.getNetworkClientRegistry(); - - controller.updateNetwork(infuraChainId, { - ...networkConfigurationToUpdate, - name: 'Some Other Name', - }); - - expect(controller.getNetworkClientRegistry()).toStrictEqual( - networkClientRegistry, + expect(networkClientRegistry).not.toHaveProperty( + 'AAAA-AAAA-AAAA-AAAA', ); }, ); }); - }); - } - - describe('if the existing chain ID is not an Infura-supported chain and is not being changed', () => { - it('throws (albeit for a different reason) if an Infura RPC endpoint is being added that represents a different chain than the one being updated', async () => { - const defaultRpcEndpoint = buildInfuraRpcEndpoint( - InfuraNetworkType.mainnet, - ); - const networkConfigurationToUpdate = buildNetworkConfiguration({ - chainId: '0x1337', - }); - - await withController( - { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ - networkConfigurationsByChainId: { - '0x1337': networkConfigurationToUpdate, - [ChainId.mainnet]: buildInfuraNetworkConfiguration( - InfuraNetworkType.mainnet, - ), - }, - }), - }, - ({ controller }) => { - expect(() => - controller.updateNetwork('0x1337', { - ...networkConfigurationToUpdate, - rpcEndpoints: [ - ...networkConfigurationToUpdate.rpcEndpoints, - defaultRpcEndpoint, - ], - }), - ).toThrow( - "Could not update network to point to same RPC endpoint as existing network for chain 0x1 ('Mainnet')", - ); - }, - ); - }); - describe('when new custom RPC endpoints are being added', () => { - it('creates and registers new network clients for each RPC endpoint', async () => { - uuidV4Mock - .mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB') - .mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC'); + it('creates and registers a network client for the new version of the RPC endpoint', async () => { + uuidV4Mock.mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB'); const createAutoManagedNetworkClientSpy = jest.spyOn( createAutoManagedNetworkClientModule, 'createAutoManagedNetworkClient', ); - const rpcEndpoint1 = buildCustomRpcEndpoint({ - name: 'Endpoint 1', - networkClientId: 'AAAA-AAAA-AAAA-AAAA', - url: 'https://rpc.endpoint/1', - }); - const networkConfigurationToUpdate = buildNetworkConfiguration({ - chainId: '0x1337', + const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ nativeCurrency: 'TOKEN', - rpcEndpoints: [rpcEndpoint1], + rpcEndpoints: [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint', + }), + ], }); await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ - networkConfigurationsByChainId: { - '0x1337': networkConfigurationToUpdate, - }, - }), + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }, }, - ({ controller }) => { - controller.updateNetwork('0x1337', { + async ({ controller }) => { + mockCreateNetworkClient() + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://some.other.url', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(buildFakeClient()); + + await controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, - defaultRpcEndpointIndex: 0, rpcEndpoints: [ - rpcEndpoint1, - buildUpdateNetworkCustomRpcEndpointFields({ - name: 'Endpoint 2', - url: 'https://rpc.endpoint/2', - }), - buildUpdateNetworkCustomRpcEndpointFields({ - name: 'Endpoint 3', - url: 'https://rpc.endpoint/3', + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://some.other.url', }), ], }); expect(createAutoManagedNetworkClientSpy).toHaveBeenCalledWith({ chainId: '0x1337', - rpcUrl: 'https://rpc.endpoint/2', - ticker: 'TOKEN', - type: NetworkClientType.Custom, - }); - expect(createAutoManagedNetworkClientSpy).toHaveBeenCalledWith({ - chainId: '0x1337', - rpcUrl: 'https://rpc.endpoint/3', + rpcUrl: 'https://some.other.url', ticker: 'TOKEN', type: NetworkClientType.Custom, }); - expect( getNetworkConfigurationsByNetworkClientId( controller.getNetworkClientRegistry(), ), ).toMatchObject({ - 'AAAA-AAAA-AAAA-AAAA': { - chainId: '0x1337', - rpcUrl: 'https://rpc.endpoint/1', - ticker: 'TOKEN', - type: NetworkClientType.Custom, - }, 'BBBB-BBBB-BBBB-BBBB': { chainId: '0x1337', - rpcUrl: 'https://rpc.endpoint/2', - ticker: 'TOKEN', - type: NetworkClientType.Custom, - }, - 'CCCC-CCCC-CCCC-CCCC': { - chainId: '0x1337', - rpcUrl: 'https://rpc.endpoint/3', + rpcUrl: 'https://some.other.url', ticker: 'TOKEN', type: NetworkClientType.Custom, }, @@ -5070,42 +7332,45 @@ describe('NetworkController', () => { ); }); - it('assigns the ID of the created network client to each RPC endpoint in state', async () => { - uuidV4Mock - .mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB') - .mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC'); - const rpcEndpoint1 = buildCustomRpcEndpoint({ - name: 'Endpoint 1', - networkClientId: 'AAAA-AAAA-AAAA-AAAA', - url: 'https://rpc.endpoint/1', - }); - const networkConfigurationToUpdate = buildNetworkConfiguration({ - chainId: '0x1337', - rpcEndpoints: [rpcEndpoint1], + it('updates the network configuration in state with a new network client ID for the RPC endpoint', async () => { + uuidV4Mock.mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB'); + const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ + nativeCurrency: 'TOKEN', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint', + }), + ], }); await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ - networkConfigurationsByChainId: { - '0x1337': networkConfigurationToUpdate, - }, - }), + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }, }, - ({ controller }) => { - controller.updateNetwork('0x1337', { + async ({ controller }) => { + mockCreateNetworkClient() + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://some.other.url', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(buildFakeClient()); + + await controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, - defaultRpcEndpointIndex: 0, rpcEndpoints: [ - rpcEndpoint1, - buildUpdateNetworkCustomRpcEndpointFields({ - name: 'Endpoint 2', - url: 'https://rpc.endpoint/2', - }), - buildUpdateNetworkCustomRpcEndpointFields({ - name: 'Endpoint 3', - url: 'https://rpc.endpoint/3', + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://some.other.url', }), ], }); @@ -5115,18 +7380,11 @@ describe('NetworkController', () => { ).toStrictEqual({ ...networkConfigurationToUpdate, rpcEndpoints: [ - rpcEndpoint1, { - name: 'Endpoint 2', + name: 'Endpoint 1', networkClientId: 'BBBB-BBBB-BBBB-BBBB', - type: RpcEndpointType.Custom, - url: 'https://rpc.endpoint/2', - }, - { - name: 'Endpoint 3', - networkClientId: 'CCCC-CCCC-CCCC-CCCC', - type: RpcEndpointType.Custom, - url: 'https://rpc.endpoint/3', + type: 'custom', + url: 'https://some.other.url', }, ], }); @@ -5135,123 +7393,273 @@ describe('NetworkController', () => { }); it('returns the updated network configuration', async () => { - uuidV4Mock - .mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB') - .mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC'); - const rpcEndpoint1 = buildCustomRpcEndpoint({ - name: 'Endpoint 1', - networkClientId: 'AAAA-AAAA-AAAA-AAAA', - url: 'https://rpc.endpoint/1', - }); - const networkConfigurationToUpdate = buildNetworkConfiguration({ - chainId: '0x1337', - rpcEndpoints: [rpcEndpoint1], + uuidV4Mock.mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB'); + const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ + nativeCurrency: 'TOKEN', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint', + }), + ], }); await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ - networkConfigurationsByChainId: { - '0x1337': networkConfigurationToUpdate, - }, - }), + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }, }, - ({ controller }) => { - const updatedNetworkConfiguration = controller.updateNetwork( - '0x1337', - { + async ({ controller }) => { + mockCreateNetworkClient() + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://some.other.url', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(buildFakeClient()); + + const updatedNetworkConfiguration = + await controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, - defaultRpcEndpointIndex: 0, rpcEndpoints: [ - rpcEndpoint1, - buildUpdateNetworkCustomRpcEndpointFields({ - name: 'Endpoint 2', - url: 'https://rpc.endpoint/2', - }), - buildUpdateNetworkCustomRpcEndpointFields({ - name: 'Endpoint 3', - url: 'https://rpc.endpoint/3', + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://some.other.url', }), ], - }, - ); + }); expect(updatedNetworkConfiguration).toStrictEqual({ ...networkConfigurationToUpdate, rpcEndpoints: [ - rpcEndpoint1, { - name: 'Endpoint 2', + name: 'Endpoint 1', networkClientId: 'BBBB-BBBB-BBBB-BBBB', - type: RpcEndpointType.Custom, - url: 'https://rpc.endpoint/2', - }, - { - name: 'Endpoint 3', - networkClientId: 'CCCC-CCCC-CCCC-CCCC', - type: RpcEndpointType.Custom, - url: 'https://rpc.endpoint/3', + type: 'custom', + url: 'https://some.other.url', }, ], }); }, ); }); - }); - describe('when some custom RPC endpoints are being removed', () => { - it('destroys and unregisters existing network clients for the RPC endpoints', async () => { - const [rpcEndpoint1, rpcEndpoint2] = [ - buildCustomRpcEndpoint({ - name: 'Endpoint 1', - networkClientId: 'AAAA-AAAA-AAAA-AAAA', - url: 'https://rpc.endpoint/1', - }), - buildCustomRpcEndpoint({ - name: 'Endpoint 2', - networkClientId: 'BBBB-BBBB-BBBB-BBBB', - url: 'https://rpc.endpoint/2', - }), - ]; - const networkConfigurationToUpdate = buildNetworkConfiguration({ - chainId: '0x1337', - rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], - }); + describe('if the previous version of the RPC endpoint was represented by the selected network client', () => { + it('invisibly selects the network client for the new RPC endpoint', async () => { + uuidV4Mock.mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB'); + const networkConfigurationToUpdate = + buildCustomNetworkConfiguration({ + nativeCurrency: 'TOKEN', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint', + }), + ], + }); - await withController( - { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ + await withController( + { + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', networkConfigurationsByChainId: { '0x1337': networkConfigurationToUpdate, }, - }), - }, - ({ controller }) => { - const existingNetworkClient = controller.getNetworkClientById( - 'AAAA-AAAA-AAAA-AAAA', - ); - const destroySpy = jest.spyOn(existingNetworkClient, 'destroy'); + }, + }, + async ({ controller }) => { + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 1', + }, + }, + ]), + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 2', + }, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://rpc.endpoint', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://some.other.url', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); + expect(controller.state.selectedNetworkClientId).toBe( + 'AAAA-AAAA-AAAA-AAAA', + ); + const networkClient1 = controller.getSelectedNetworkClient(); + assert(networkClient1, 'Network client is somehow unset'); + const result1 = await networkClient1.provider.request({ + method: 'test', + }); + expect(result1).toBe('test response from 1'); - controller.updateNetwork('0x1337', { - ...networkConfigurationToUpdate, - defaultRpcEndpointIndex: 0, - rpcEndpoints: [rpcEndpoint2], + await controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://some.other.url', + }), + ], + }); + + expect(controller.state.selectedNetworkClientId).toBe( + 'BBBB-BBBB-BBBB-BBBB', + ); + const networkClient2 = controller.getSelectedNetworkClient(); + assert(networkClient2, 'Network client is somehow unset'); + const result2 = await networkClient1.provider.request({ + method: 'test', + }); + expect(result2).toBe('test response from 2'); + }, + ); + }); + + it('updates selectedNetworkClientId and networkConfigurationsByChainId at the same time', async () => { + uuidV4Mock.mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB'); + const networkConfigurationToUpdate = + buildCustomNetworkConfiguration({ + nativeCurrency: 'TOKEN', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint', + }), + ], }); - expect(destroySpy).toHaveBeenCalled(); - const networkClientRegistry = - controller.getNetworkClientRegistry(); - expect(networkClientRegistry).not.toHaveProperty( - 'AAAA-AAAA-AAAA-AAAA', - ); - }, - ); + await withController( + { + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }, + }, + async ({ controller, messenger }) => { + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 1', + }, + }, + ]), + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 2', + }, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://rpc.endpoint', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://some.other.url', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); + + const promiseForStateChanges = waitForStateChanges({ + messenger, + count: 1, + }); + + await controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + rpcEndpoints: [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://some.other.url', + }), + ], + }); + const stateChanges = await promiseForStateChanges; + expect(stateChanges).toStrictEqual([ + [ + expect.any(Object), + expect.arrayContaining([ + expect.objectContaining({ + op: 'replace', + path: ['selectedNetworkClientId'], + value: 'BBBB-BBBB-BBBB-BBBB', + }), + expect.objectContaining({ + op: 'replace', + path: ['networkConfigurationsByChainId', '0x1337'], + }), + ]), + ], + ]); + }, + ); + }); }); + }); - it('updates the network configuration in state', async () => { - const [rpcEndpoint1, rpcEndpoint2] = [ + describe('when all of the RPC endpoints are simply being shuffled', () => { + it('does not touch the network client registry', async () => { + const [rpcEndpoint1, rpcEndpoint2, rpcEndpoint3] = [ buildCustomRpcEndpoint({ name: 'Endpoint 1', networkClientId: 'AAAA-AAAA-AAAA-AAAA', @@ -5262,10 +7670,15 @@ describe('NetworkController', () => { networkClientId: 'BBBB-BBBB-BBBB-BBBB', url: 'https://rpc.endpoint/2', }), + buildCustomRpcEndpoint({ + name: 'Endpoint 3', + networkClientId: 'CCCC-CCCC-CCCC-CCCC', + url: 'https://rpc.endpoint/3', + }), ]; - const networkConfigurationToUpdate = buildNetworkConfiguration({ + const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ chainId: '0x1337', - rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], + rpcEndpoints: [rpcEndpoint1, rpcEndpoint2, rpcEndpoint3], }); await withController( @@ -5277,26 +7690,25 @@ describe('NetworkController', () => { }, }), }, - ({ controller }) => { - controller.updateNetwork('0x1337', { - ...networkConfigurationToUpdate, - defaultRpcEndpointIndex: 0, - rpcEndpoints: [rpcEndpoint2], - }); + async ({ controller }) => { + mockCreateNetworkClient().mockReturnValue(buildFakeClient()); + const networkClientRegistry = + controller.getNetworkClientRegistry(); - expect( - controller.state.networkConfigurationsByChainId['0x1337'], - ).toStrictEqual({ + await controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, - defaultRpcEndpointIndex: 0, - rpcEndpoints: [rpcEndpoint2], + rpcEndpoints: [rpcEndpoint3, rpcEndpoint1, rpcEndpoint2], }); + + expect(controller.getNetworkClientRegistry()).toStrictEqual( + networkClientRegistry, + ); }, ); }); - it('returns the updated network configuration', async () => { - const [rpcEndpoint1, rpcEndpoint2] = [ + it('updates the network configuration in state with the new order of RPC endpoints', async () => { + const [rpcEndpoint1, rpcEndpoint2, rpcEndpoint3] = [ buildCustomRpcEndpoint({ name: 'Endpoint 1', networkClientId: 'AAAA-AAAA-AAAA-AAAA', @@ -5307,10 +7719,15 @@ describe('NetworkController', () => { networkClientId: 'BBBB-BBBB-BBBB-BBBB', url: 'https://rpc.endpoint/2', }), + buildCustomRpcEndpoint({ + name: 'Endpoint 3', + networkClientId: 'CCCC-CCCC-CCCC-CCCC', + url: 'https://rpc.endpoint/3', + }), ]; - const networkConfigurationToUpdate = buildNetworkConfiguration({ + const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ chainId: '0x1337', - rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], + rpcEndpoints: [rpcEndpoint1, rpcEndpoint2, rpcEndpoint3], }); await withController( @@ -5322,29 +7739,26 @@ describe('NetworkController', () => { }, }), }, - ({ controller }) => { - const updatedNetworkConfiguration = controller.updateNetwork( - '0x1337', - { + async ({ controller }) => { + await controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + rpcEndpoints: [rpcEndpoint3, rpcEndpoint1, rpcEndpoint2], + }); + + expect( + controller.state.networkConfigurationsByChainId, + ).toStrictEqual({ + '0x1337': { ...networkConfigurationToUpdate, - defaultRpcEndpointIndex: 0, - rpcEndpoints: [rpcEndpoint2], + rpcEndpoints: [rpcEndpoint3, rpcEndpoint1, rpcEndpoint2], }, - ); - - expect(updatedNetworkConfiguration).toStrictEqual({ - ...networkConfigurationToUpdate, - defaultRpcEndpointIndex: 0, - rpcEndpoints: [rpcEndpoint2], }); }, ); }); - }); - describe('when the networkClientId of some custom RPC endpoints are being cleared', () => { - it('destroys and unregisters existing network clients for the RPC endpoints', async () => { - const [rpcEndpoint1, rpcEndpoint2] = [ + it('returns the network configuration with the new order of RPC endpoints', async () => { + const [rpcEndpoint1, rpcEndpoint2, rpcEndpoint3] = [ buildCustomRpcEndpoint({ name: 'Endpoint 1', networkClientId: 'AAAA-AAAA-AAAA-AAAA', @@ -5355,10 +7769,15 @@ describe('NetworkController', () => { networkClientId: 'BBBB-BBBB-BBBB-BBBB', url: 'https://rpc.endpoint/2', }), + buildCustomRpcEndpoint({ + name: 'Endpoint 3', + networkClientId: 'CCCC-CCCC-CCCC-CCCC', + url: 'https://rpc.endpoint/3', + }), ]; - const networkConfigurationToUpdate = buildNetworkConfiguration({ + const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ chainId: '0x1337', - rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], + rpcEndpoints: [rpcEndpoint1, rpcEndpoint2, rpcEndpoint3], }); await withController( @@ -5370,51 +7789,38 @@ describe('NetworkController', () => { }, }), }, - ({ controller }) => { - const existingNetworkClient = controller.getNetworkClientById( - 'BBBB-BBBB-BBBB-BBBB', - ); - const destroySpy = jest.spyOn(existingNetworkClient, 'destroy'); + async ({ controller }) => { + const updatedNetworkConfiguration = + await controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + rpcEndpoints: [rpcEndpoint3, rpcEndpoint1, rpcEndpoint2], + }); - controller.updateNetwork('0x1337', { + expect(updatedNetworkConfiguration).toStrictEqual({ ...networkConfigurationToUpdate, - defaultRpcEndpointIndex: 1, - rpcEndpoints: [ - rpcEndpoint1, - { ...rpcEndpoint2, networkClientId: undefined }, - ], + rpcEndpoints: [rpcEndpoint3, rpcEndpoint1, rpcEndpoint2], }); - - expect(destroySpy).toHaveBeenCalled(); - const networkClientRegistry = - controller.getNetworkClientRegistry(); - expect(networkClientRegistry).not.toHaveProperty( - 'BBBB-BBBB-BBBB-BBBB', - ); }, ); }); + }); - it('creates and registers new network clients for the RPC endpoints', async () => { - uuidV4Mock.mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC'); - const createAutoManagedNetworkClientSpy = jest.spyOn( - createAutoManagedNetworkClientModule, - 'createAutoManagedNetworkClient', - ); + describe('when the networkClientId of some custom RPC endpoints are being cleared', () => { + it('does not touch the network client registry', async () => { const [rpcEndpoint1, rpcEndpoint2] = [ buildCustomRpcEndpoint({ + name: 'Endpoint 1', networkClientId: 'AAAA-AAAA-AAAA-AAAA', - url: 'https://test.endpoint/1', + url: 'https://rpc.endpoint/1', }), buildCustomRpcEndpoint({ + name: 'Endpoint 2', networkClientId: 'BBBB-BBBB-BBBB-BBBB', - url: 'https://test.endpoint/2', + url: 'https://rpc.endpoint/2', }), ]; - const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ + const networkConfigurationToUpdate = buildNetworkConfiguration({ chainId: '0x1337', - defaultRpcEndpointIndex: 0, - nativeCurrency: 'TOKEN', rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], }); @@ -5427,8 +7833,12 @@ describe('NetworkController', () => { }, }), }, - ({ controller }) => { - controller.updateNetwork('0x1337', { + async ({ controller }) => { + mockCreateNetworkClient().mockReturnValue(buildFakeClient()); + const networkClientRegistry = + controller.getNetworkClientRegistry(); + + await controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, rpcEndpoints: [ rpcEndpoint1, @@ -5436,50 +7846,28 @@ describe('NetworkController', () => { ], }); - expect(createAutoManagedNetworkClientSpy).toHaveBeenCalledWith({ - chainId: '0x1337', - rpcUrl: 'https://test.endpoint/2', - ticker: 'TOKEN', - type: NetworkClientType.Custom, - }); - - expect( - getNetworkConfigurationsByNetworkClientId( - controller.getNetworkClientRegistry(), - ), - ).toMatchObject({ - 'AAAA-AAAA-AAAA-AAAA': { - chainId: '0x1337', - rpcUrl: 'https://test.endpoint/1', - ticker: 'TOKEN', - type: NetworkClientType.Custom, - }, - 'CCCC-CCCC-CCCC-CCCC': { - chainId: '0x1337', - rpcUrl: 'https://test.endpoint/2', - ticker: 'TOKEN', - type: NetworkClientType.Custom, - }, - }); + expect(controller.getNetworkClientRegistry()).toStrictEqual( + networkClientRegistry, + ); }, ); }); - it('assigns the IDs of the new network clients to the RPC endpoints in state', async () => { - uuidV4Mock.mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC'); + it('does not touch the network configuration in state, as if the network client IDs had not been cleared', async () => { const [rpcEndpoint1, rpcEndpoint2] = [ buildCustomRpcEndpoint({ + name: 'Endpoint 1', networkClientId: 'AAAA-AAAA-AAAA-AAAA', - url: 'https://test.endpoint/1', + url: 'https://rpc.endpoint/1', }), buildCustomRpcEndpoint({ + name: 'Endpoint 2', networkClientId: 'BBBB-BBBB-BBBB-BBBB', - url: 'https://test.endpoint/2', + url: 'https://rpc.endpoint/2', }), ]; - const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ + const networkConfigurationToUpdate = buildNetworkConfiguration({ chainId: '0x1337', - defaultRpcEndpointIndex: 0, rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], }); @@ -5492,8 +7880,11 @@ describe('NetworkController', () => { }, }), }, - ({ controller }) => { - controller.updateNetwork('0x1337', { + async ({ controller }) => { + const previousNetworkConfigurationsByChainId = + controller.state.networkConfigurationsByChainId; + + await controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, rpcEndpoints: [ rpcEndpoint1, @@ -5502,36 +7893,27 @@ describe('NetworkController', () => { }); expect( - controller.state.networkConfigurationsByChainId['0x1337'], - ).toStrictEqual({ - ...networkConfigurationToUpdate, - rpcEndpoints: [ - rpcEndpoint1, - { - ...rpcEndpoint2, - networkClientId: 'CCCC-CCCC-CCCC-CCCC', - }, - ], - }); + controller.state.networkConfigurationsByChainId, + ).toStrictEqual(previousNetworkConfigurationsByChainId); }, ); }); - it('returns the updated network configuration', async () => { - uuidV4Mock.mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC'); + it('returns the network configuration, untouched', async () => { const [rpcEndpoint1, rpcEndpoint2] = [ buildCustomRpcEndpoint({ + name: 'Endpoint 1', networkClientId: 'AAAA-AAAA-AAAA-AAAA', - url: 'https://test.endpoint/1', + url: 'https://rpc.endpoint/1', }), buildCustomRpcEndpoint({ + name: 'Endpoint 2', networkClientId: 'BBBB-BBBB-BBBB-BBBB', - url: 'https://test.endpoint/2', + url: 'https://rpc.endpoint/2', }), ]; - const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ + const networkConfigurationToUpdate = buildNetworkConfiguration({ chainId: '0x1337', - defaultRpcEndpointIndex: 0, rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], }); @@ -5544,28 +7926,19 @@ describe('NetworkController', () => { }, }), }, - ({ controller }) => { - const updatedNetworkConfiguration = controller.updateNetwork( - '0x1337', - { + async ({ controller }) => { + const updatedNetworkConfiguration = + await controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, rpcEndpoints: [ rpcEndpoint1, { ...rpcEndpoint2, networkClientId: undefined }, ], - }, - ); + }); - expect(updatedNetworkConfiguration).toStrictEqual({ - ...networkConfigurationToUpdate, - rpcEndpoints: [ - rpcEndpoint1, - { - ...rpcEndpoint2, - networkClientId: 'CCCC-CCCC-CCCC-CCCC', - }, - ], - }); + expect(updatedNetworkConfiguration).toStrictEqual( + networkConfigurationToUpdate, + ); }, ); }); @@ -5592,12 +7965,12 @@ describe('NetworkController', () => { }, }), }, - ({ controller }) => { + async ({ controller }) => { mockCreateNetworkClient().mockReturnValue(buildFakeClient()); const networkClientRegistry = controller.getNetworkClientRegistry(); - controller.updateNetwork('0x1337', { + await controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, name: 'Some Other Name', }); @@ -5621,6 +7994,8 @@ describe('NetworkController', () => { (infuraNetworkTypeIndex + 1) % possibleInfuraNetworkTypes.length ]; const anotherInfuraChainId = ChainId[anotherInfuraNetworkType]; + const anotherInfuraNativeTokenName = + NetworksTicker[anotherInfuraNetworkType]; const anotherInfuraNetworkNickname = NetworkNickname[anotherInfuraNetworkType]; @@ -5646,13 +8021,13 @@ describe('NetworkController', () => { }, ), }, - ({ controller }) => { - expect(() => + async ({ controller }) => { + await expect( controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, chainId: infuraChainId, }), - ).toThrow( + ).rejects.toThrow( // This is a string. // eslint-disable-next-line @typescript-eslint/restrict-template-expressions `Cannot move network from chain 0x1337 to ${infuraChainId} as another network for that chain already exists ('${infuraNetworkNickname}')`, @@ -5679,8 +8054,8 @@ describe('NetworkController', () => { }, ), }, - ({ controller }) => { - expect(() => + async ({ controller }) => { + await expect( controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, chainId: infuraChainId, @@ -5689,7 +8064,7 @@ describe('NetworkController', () => { buildInfuraRpcEndpoint(anotherInfuraNetworkType), ], }), - ).toThrow( + ).rejects.toThrow( new Error( // This is a string. // eslint-disable-next-line @typescript-eslint/restrict-template-expressions @@ -5731,8 +8106,17 @@ describe('NetworkController', () => { }, ), }, - ({ controller }) => { - controller.updateNetwork('0x1337', { + async ({ controller }) => { + mockCreateNetworkClient() + .calledWith({ + chainId: infuraChainId, + rpcUrl: 'https://test.endpoint/1', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(buildFakeClient()); + + await controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, chainId: infuraChainId, }); @@ -5793,7 +8177,15 @@ describe('NetworkController', () => { }, ), }, - ({ controller }) => { + async ({ controller }) => { + mockCreateNetworkClient() + .calledWith({ + chainId: infuraChainId, + rpcUrl: 'https://test.endpoint/1', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(buildFakeClient()); const existingNetworkClient1 = controller.getNetworkClientById( 'AAAA-AAAA-AAAA-AAAA', ); @@ -5809,7 +8201,7 @@ describe('NetworkController', () => { 'destroy', ); - controller.updateNetwork('0x1337', { + await controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, chainId: infuraChainId, }); @@ -5832,12 +8224,10 @@ describe('NetworkController', () => { uuidV4Mock .mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC') .mockReturnValueOnce('DDDD-DDDD-DDDD-DDDD'); - const createAutoManagedNetworkClientSpy = jest.spyOn( createAutoManagedNetworkClientModule, 'createAutoManagedNetworkClient', ); - const networkConfigurationToUpdate = buildNetworkConfiguration({ chainId: '0x1337', nativeCurrency: 'TOKEN', @@ -5866,8 +8256,17 @@ describe('NetworkController', () => { }, ), }, - ({ controller }) => { - controller.updateNetwork('0x1337', { + async ({ controller }) => { + mockCreateNetworkClient() + .calledWith({ + chainId: infuraChainId, + rpcUrl: 'https://test.endpoint/1', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(buildFakeClient()); + + await controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, chainId: infuraChainId, }); @@ -5938,14 +8337,21 @@ describe('NetworkController', () => { }, ), }, - ({ controller }) => { - const updatedNetworkConfiguration = controller.updateNetwork( - '0x1337', - { + async ({ controller }) => { + mockCreateNetworkClient() + .calledWith({ + chainId: infuraChainId, + rpcUrl: 'https://test.endpoint/1', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(buildFakeClient()); + + const updatedNetworkConfiguration = + await controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, chainId: infuraChainId, - }, - ); + }); expect(updatedNetworkConfiguration).toStrictEqual({ ...networkConfigurationToUpdate, @@ -5964,6 +8370,206 @@ describe('NetworkController', () => { }, ); }); + + describe('if one of the RPC endpoints was represented by the selected network client', () => { + it('invisibly selects the network client created for the RPC endpoint', async () => { + uuidV4Mock.mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB'); + const networkConfigurationToUpdate = + buildCustomNetworkConfiguration({ + nativeCurrency: 'TOKEN', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint', + }), + ], + }); + + await withController( + { + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }, + }, + async ({ controller }) => { + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 1', + }, + }, + ]), + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 2', + }, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://rpc.endpoint', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + chainId: infuraChainId, + rpcUrl: 'https://rpc.endpoint', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); + expect(controller.state.selectedNetworkClientId).toBe( + 'AAAA-AAAA-AAAA-AAAA', + ); + const networkClient1 = controller.getSelectedNetworkClient(); + assert(networkClient1, 'Network client is somehow unset'); + const result1 = await networkClient1.provider.request({ + method: 'test', + }); + expect(result1).toBe('test response from 1'); + + await controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + chainId: infuraChainId, + }); + + expect(controller.state.selectedNetworkClientId).toBe( + 'BBBB-BBBB-BBBB-BBBB', + ); + const networkClient2 = controller.getSelectedNetworkClient(); + assert(networkClient2, 'Network client is somehow unset'); + const result2 = await networkClient1.provider.request({ + method: 'test', + }); + expect(result2).toBe('test response from 2'); + }, + ); + }); + + it('updates selectedNetworkClientId and networkConfigurationsByChainId at the same time', async () => { + uuidV4Mock.mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB'); + const networkConfigurationToUpdate = + buildCustomNetworkConfiguration({ + nativeCurrency: 'TOKEN', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint', + }), + ], + }); + + await withController( + { + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }, + }, + async ({ controller, messenger }) => { + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 1', + }, + }, + ]), + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 2', + }, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://rpc.endpoint', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + chainId: infuraChainId, + rpcUrl: 'https://rpc.endpoint', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); + + const promiseForStateChanges = waitForStateChanges({ + messenger, + count: 1, + }); + + await controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + chainId: infuraChainId, + }); + const stateChanges = await promiseForStateChanges; + expect(stateChanges).toStrictEqual([ + [ + expect.any(Object), + expect.arrayContaining([ + expect.objectContaining({ + op: 'replace', + path: ['selectedNetworkClientId'], + value: 'BBBB-BBBB-BBBB-BBBB', + }), + expect.objectContaining({ + op: 'remove', + path: ['networkConfigurationsByChainId', '0x1337'], + }), + expect.objectContaining({ + op: 'add', + path: [ + 'networkConfigurationsByChainId', + infuraChainId, + ], + }), + ]), + ], + ]); + }, + ); + }); + }); }); // False negative - this is a string. @@ -5988,13 +8594,13 @@ describe('NetworkController', () => { }, ), }, - ({ controller }) => { - expect(() => + async ({ controller }) => { + await expect( controller.updateNetwork(infuraChainId, { ...networkConfigurationToUpdate, chainId: '0x1337', }), - ).toThrow( + ).rejects.toThrow( // False negative - this is a string. // eslint-disable-next-line @typescript-eslint/restrict-template-expressions `Cannot move network from chain ${infuraChainId} to 0x1337 as another network for that chain already exists ('Some Network')`, @@ -6018,13 +8624,13 @@ describe('NetworkController', () => { }, ), }, - ({ controller }) => { - expect(() => + async ({ controller }) => { + await expect( controller.updateNetwork(infuraChainId, { ...networkConfigurationToUpdate, chainId: '0x1337', }), - ).toThrow( + ).rejects.toThrow( new Error( // This is a string. // eslint-disable-next-line @typescript-eslint/restrict-template-expressions @@ -6070,15 +8676,29 @@ describe('NetworkController', () => { [infuraChainId]: networkConfigurationToUpdate, }, }, - ), - }, - ({ controller }) => { - controller.updateNetwork(infuraChainId, { - ...networkConfigurationToUpdate, - chainId: '0x1337', - defaultRpcEndpointIndex: 0, - rpcEndpoints: [customRpcEndpoint1, customRpcEndpoint2], - }); + ), + }, + async ({ controller }) => { + mockCreateNetworkClient() + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://test.endpoint/1', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(buildFakeClient()); + + await controller.updateNetwork( + infuraChainId, + { + ...networkConfigurationToUpdate, + chainId: '0x1337', + defaultRpcEndpointIndex: 0, + nativeCurrency: 'TOKEN', + rpcEndpoints: [customRpcEndpoint1, customRpcEndpoint2], + }, + { replacementSelectedRpcEndpointIndex: 0 }, + ); expect( controller.state.networkConfigurationsByChainId, @@ -6092,6 +8712,7 @@ describe('NetworkController', () => { ...networkConfigurationToUpdate, chainId: '0x1337', defaultRpcEndpointIndex: 0, + nativeCurrency: 'TOKEN', rpcEndpoints: [ { ...customRpcEndpoint1, @@ -6140,7 +8761,15 @@ describe('NetworkController', () => { }, ), }, - ({ controller }) => { + async ({ controller }) => { + mockCreateNetworkClient() + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://test.endpoint/1', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(buildFakeClient()); const existingNetworkClient1 = controller.getNetworkClientById( 'AAAA-AAAA-AAAA-AAAA', ); @@ -6156,12 +8785,17 @@ describe('NetworkController', () => { 'destroy', ); - controller.updateNetwork(infuraChainId, { - ...networkConfigurationToUpdate, - chainId: '0x1337', - defaultRpcEndpointIndex: 0, - rpcEndpoints: [customRpcEndpoint1, customRpcEndpoint2], - }); + await controller.updateNetwork( + infuraChainId, + { + ...networkConfigurationToUpdate, + chainId: '0x1337', + defaultRpcEndpointIndex: 0, + nativeCurrency: 'TOKEN', + rpcEndpoints: [customRpcEndpoint1, customRpcEndpoint2], + }, + { replacementSelectedRpcEndpointIndex: 0 }, + ); expect(destroySpy1).toHaveBeenCalled(); expect(destroySpy2).toHaveBeenCalled(); @@ -6220,24 +8854,38 @@ describe('NetworkController', () => { }, ), }, - ({ controller }) => { - controller.updateNetwork(infuraChainId, { - ...networkConfigurationToUpdate, - chainId: '0x1337', - defaultRpcEndpointIndex: 0, - rpcEndpoints: [customRpcEndpoint1, customRpcEndpoint2], - }); + async ({ controller }) => { + mockCreateNetworkClient() + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://test.endpoint/1', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(buildFakeClient()); + + await controller.updateNetwork( + infuraChainId, + { + ...networkConfigurationToUpdate, + chainId: '0x1337', + defaultRpcEndpointIndex: 0, + nativeCurrency: 'TOKEN', + rpcEndpoints: [customRpcEndpoint1, customRpcEndpoint2], + }, + { replacementSelectedRpcEndpointIndex: 0 }, + ); expect(createAutoManagedNetworkClientSpy).toHaveBeenCalledWith({ chainId: '0x1337', rpcUrl: 'https://test.endpoint/1', - ticker: 'ETH', + ticker: 'TOKEN', type: NetworkClientType.Custom, }); expect(createAutoManagedNetworkClientSpy).toHaveBeenCalledWith({ chainId: '0x1337', rpcUrl: 'https://test.endpoint/2', - ticker: 'ETH', + ticker: 'TOKEN', type: NetworkClientType.Custom, }); @@ -6249,13 +8897,13 @@ describe('NetworkController', () => { 'CCCC-CCCC-CCCC-CCCC': { chainId: '0x1337', rpcUrl: 'https://test.endpoint/1', - ticker: 'ETH', + ticker: 'TOKEN', type: NetworkClientType.Custom, }, 'DDDD-DDDD-DDDD-DDDD': { chainId: '0x1337', rpcUrl: 'https://test.endpoint/2', - ticker: 'ETH', + ticker: 'TOKEN', type: NetworkClientType.Custom, }, }); @@ -6300,21 +8948,34 @@ describe('NetworkController', () => { }, ), }, - ({ controller }) => { - const updatedNetworkConfiguration = controller.updateNetwork( - infuraChainId, - { - ...networkConfigurationToUpdate, + async ({ controller }) => { + mockCreateNetworkClient() + .calledWith({ chainId: '0x1337', - defaultRpcEndpointIndex: 0, - rpcEndpoints: [customRpcEndpoint1, customRpcEndpoint2], - }, - ); + rpcUrl: 'https://test.endpoint/1', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(buildFakeClient()); + + const updatedNetworkConfiguration = + await controller.updateNetwork( + infuraChainId, + { + ...networkConfigurationToUpdate, + chainId: '0x1337', + defaultRpcEndpointIndex: 0, + nativeCurrency: 'TOKEN', + rpcEndpoints: [customRpcEndpoint1, customRpcEndpoint2], + }, + { replacementSelectedRpcEndpointIndex: 0 }, + ); expect(updatedNetworkConfiguration).toStrictEqual({ ...networkConfigurationToUpdate, chainId: '0x1337', defaultRpcEndpointIndex: 0, + nativeCurrency: 'TOKEN', rpcEndpoints: [ { ...customRpcEndpoint1, @@ -6329,6 +8990,206 @@ describe('NetworkController', () => { }, ); }); + + describe('if one of the RPC endpoints was represented by the selected network client', () => { + it('invisibly selects the network client created for the RPC endpoint', async () => { + uuidV4Mock.mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB'); + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + nativeCurrency: 'TOKEN', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint', + }), + ], + }); + + await withController( + { + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + }, + async ({ controller }) => { + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 1', + }, + }, + ]), + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 2', + }, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + chainId: infuraChainId, + rpcUrl: 'https://rpc.endpoint', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://rpc.endpoint', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); + expect(controller.state.selectedNetworkClientId).toBe( + 'AAAA-AAAA-AAAA-AAAA', + ); + const networkClient1 = controller.getSelectedNetworkClient(); + assert(networkClient1, 'Network client is somehow unset'); + const result1 = await networkClient1.provider.request({ + method: 'test', + }); + expect(result1).toBe('test response from 1'); + + await controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + chainId: '0x1337', + }); + + expect(controller.state.selectedNetworkClientId).toBe( + 'BBBB-BBBB-BBBB-BBBB', + ); + const networkClient2 = controller.getSelectedNetworkClient(); + assert(networkClient2, 'Network client is somehow unset'); + const result2 = await networkClient1.provider.request({ + method: 'test', + }); + expect(result2).toBe('test response from 2'); + }, + ); + }); + + it('updates selectedNetworkClientId and networkConfigurationsByChainId at the same time', async () => { + uuidV4Mock.mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB'); + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + nativeCurrency: 'TOKEN', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint', + }), + ], + }); + + await withController( + { + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + }, + async ({ controller, messenger }) => { + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 1', + }, + }, + ]), + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 2', + }, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + chainId: infuraChainId, + rpcUrl: 'https://rpc.endpoint', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://rpc.endpoint', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); + + const promiseForStateChanges = waitForStateChanges({ + messenger, + count: 1, + }); + + await controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + chainId: '0x1337', + }); + const stateChanges = await promiseForStateChanges; + expect(stateChanges).toStrictEqual([ + [ + expect.any(Object), + expect.arrayContaining([ + expect.objectContaining({ + op: 'replace', + path: ['selectedNetworkClientId'], + value: 'BBBB-BBBB-BBBB-BBBB', + }), + expect.objectContaining({ + op: 'remove', + path: [ + 'networkConfigurationsByChainId', + infuraChainId, + ], + }), + expect.objectContaining({ + op: 'add', + path: ['networkConfigurationsByChainId', '0x1337'], + }), + ]), + ], + ]); + }, + ); + }); + }); }); // False negative - this is a string. @@ -6354,13 +9215,13 @@ describe('NetworkController', () => { }, ), }, - ({ controller }) => { - expect(() => + async ({ controller }) => { + await expect( controller.updateNetwork(infuraChainId, { ...networkConfigurationToUpdate, chainId: anotherInfuraChainId, }), - ).toThrow( + ).rejects.toThrow( // This is a string. // eslint-disable-next-line @typescript-eslint/restrict-template-expressions `Cannot move network from chain ${infuraChainId} to ${anotherInfuraChainId} as another network for that chain already exists ('${anotherInfuraNetworkNickname}')`, @@ -6384,13 +9245,13 @@ describe('NetworkController', () => { }, ), }, - ({ controller }) => { - expect(() => + async ({ controller }) => { + await expect( controller.updateNetwork(infuraChainId, { ...networkConfigurationToUpdate, chainId: anotherInfuraChainId, }), - ).toThrow( + ).rejects.toThrow( new Error( // This is a string. // eslint-disable-next-line @typescript-eslint/restrict-template-expressions @@ -6437,13 +9298,32 @@ describe('NetworkController', () => { }, }, ), + infuraProjectId: 'some-infura-project-id', }, - ({ controller }) => { - controller.updateNetwork(infuraChainId, { + async ({ controller }) => { + mockCreateNetworkClient() + .calledWith({ + chainId: anotherInfuraChainId, + infuraProjectId: 'some-infura-project-id', + network: anotherInfuraNetworkType, + ticker: anotherInfuraNativeTokenName, + type: NetworkClientType.Infura, + }) + .mockReturnValue(buildFakeClient()); + + const anotherInfuraRpcEndpoint = buildInfuraRpcEndpoint( + anotherInfuraNetworkType, + ); + await controller.updateNetwork(infuraChainId, { ...networkConfigurationToUpdate, chainId: anotherInfuraChainId, defaultRpcEndpointIndex: 0, - rpcEndpoints: [customRpcEndpoint1, customRpcEndpoint2], + nativeCurrency: anotherInfuraNativeTokenName, + rpcEndpoints: [ + anotherInfuraRpcEndpoint, + customRpcEndpoint1, + customRpcEndpoint2, + ], }); expect( @@ -6459,8 +9339,9 @@ describe('NetworkController', () => { ).toStrictEqual({ ...networkConfigurationToUpdate, chainId: anotherInfuraChainId, - defaultRpcEndpointIndex: 0, + nativeCurrency: anotherInfuraNativeTokenName, rpcEndpoints: [ + anotherInfuraRpcEndpoint, { ...customRpcEndpoint1, networkClientId: 'CCCC-CCCC-CCCC-CCCC', @@ -6476,6 +9357,10 @@ describe('NetworkController', () => { }); it('destroys and unregisters every network client for each of the custom RPC endpoints (even if none of the endpoint URLs were changed)', async () => { + uuidV4Mock + .mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC') + .mockReturnValueOnce('DDDD-DDDD-DDDD-DDDD'); + const [defaultRpcEndpoint, customRpcEndpoint1, customRpcEndpoint2] = [ buildInfuraRpcEndpoint(infuraNetworkType), @@ -6507,8 +9392,18 @@ describe('NetworkController', () => { }, }, ), + infuraProjectId: 'some-infura-project-id', }, - ({ controller }) => { + async ({ controller }) => { + mockCreateNetworkClient() + .calledWith({ + chainId: anotherInfuraChainId, + infuraProjectId: 'some-infura-project-id', + network: anotherInfuraNetworkType, + ticker: anotherInfuraNativeTokenName, + type: NetworkClientType.Infura, + }) + .mockReturnValue(buildFakeClient()); const existingNetworkClient1 = controller.getNetworkClientById( 'AAAA-AAAA-AAAA-AAAA', ); @@ -6524,11 +9419,16 @@ describe('NetworkController', () => { 'destroy', ); - controller.updateNetwork(infuraChainId, { + await controller.updateNetwork(infuraChainId, { ...networkConfigurationToUpdate, chainId: anotherInfuraChainId, defaultRpcEndpointIndex: 0, - rpcEndpoints: [customRpcEndpoint1, customRpcEndpoint2], + nativeCurrency: anotherInfuraNativeTokenName, + rpcEndpoints: [ + buildInfuraRpcEndpoint(anotherInfuraNetworkType), + customRpcEndpoint1, + customRpcEndpoint2, + ], }); expect(destroySpy1).toHaveBeenCalled(); @@ -6587,25 +9487,41 @@ describe('NetworkController', () => { }, }, ), + infuraProjectId: 'some-infura-project-id', }, - ({ controller }) => { - controller.updateNetwork(infuraChainId, { + async ({ controller }) => { + mockCreateNetworkClient() + .calledWith({ + chainId: anotherInfuraChainId, + infuraProjectId: 'some-infura-project-id', + network: anotherInfuraNetworkType, + ticker: anotherInfuraNativeTokenName, + type: NetworkClientType.Infura, + }) + .mockReturnValue(buildFakeClient()); + + await controller.updateNetwork(infuraChainId, { ...networkConfigurationToUpdate, chainId: anotherInfuraChainId, defaultRpcEndpointIndex: 0, - rpcEndpoints: [customRpcEndpoint1, customRpcEndpoint2], + nativeCurrency: anotherInfuraNativeTokenName, + rpcEndpoints: [ + buildInfuraRpcEndpoint(anotherInfuraNetworkType), + customRpcEndpoint1, + customRpcEndpoint2, + ], }); expect(createAutoManagedNetworkClientSpy).toHaveBeenCalledWith({ chainId: anotherInfuraChainId, rpcUrl: 'https://test.endpoint/1', - ticker: 'ETH', + ticker: anotherInfuraNativeTokenName, type: NetworkClientType.Custom, }); expect(createAutoManagedNetworkClientSpy).toHaveBeenCalledWith({ chainId: anotherInfuraChainId, rpcUrl: 'https://test.endpoint/2', - ticker: 'ETH', + ticker: anotherInfuraNativeTokenName, type: NetworkClientType.Custom, }); @@ -6613,17 +9529,24 @@ describe('NetworkController', () => { getNetworkConfigurationsByNetworkClientId( controller.getNetworkClientRegistry(), ), - ).toMatchObject({ + ).toStrictEqual({ + [anotherInfuraNetworkType]: { + chainId: anotherInfuraChainId, + infuraProjectId: 'some-infura-project-id', + network: anotherInfuraNetworkType, + ticker: anotherInfuraNativeTokenName, + type: NetworkClientType.Infura, + }, 'CCCC-CCCC-CCCC-CCCC': { chainId: anotherInfuraChainId, rpcUrl: 'https://test.endpoint/1', - ticker: 'ETH', + ticker: anotherInfuraNativeTokenName, type: NetworkClientType.Custom, }, 'DDDD-DDDD-DDDD-DDDD': { chainId: anotherInfuraChainId, rpcUrl: 'https://test.endpoint/2', - ticker: 'ETH', + ticker: anotherInfuraNativeTokenName, type: NetworkClientType.Custom, }, }); @@ -6667,23 +9590,42 @@ describe('NetworkController', () => { }, }, ), + infuraProjectId: 'some-infura-project-id', }, - ({ controller }) => { - const updatedNetworkConfiguration = controller.updateNetwork( - infuraChainId, - { + async ({ controller }) => { + mockCreateNetworkClient() + .calledWith({ + chainId: anotherInfuraChainId, + infuraProjectId: 'some-infura-project-id', + network: anotherInfuraNetworkType, + ticker: anotherInfuraNativeTokenName, + type: NetworkClientType.Infura, + }) + .mockReturnValue(buildFakeClient()); + + const anotherInfuraRpcEndpoint = buildInfuraRpcEndpoint( + anotherInfuraNetworkType, + ); + const updatedNetworkConfiguration = + await controller.updateNetwork(infuraChainId, { ...networkConfigurationToUpdate, chainId: anotherInfuraChainId, defaultRpcEndpointIndex: 0, - rpcEndpoints: [customRpcEndpoint1, customRpcEndpoint2], - }, - ); + nativeCurrency: anotherInfuraNativeTokenName, + rpcEndpoints: [ + anotherInfuraRpcEndpoint, + customRpcEndpoint1, + customRpcEndpoint2, + ], + }); expect(updatedNetworkConfiguration).toStrictEqual({ ...networkConfigurationToUpdate, chainId: anotherInfuraChainId, defaultRpcEndpointIndex: 0, + nativeCurrency: anotherInfuraNativeTokenName, rpcEndpoints: [ + anotherInfuraRpcEndpoint, { ...customRpcEndpoint1, networkClientId: 'CCCC-CCCC-CCCC-CCCC', @@ -6697,6 +9639,209 @@ describe('NetworkController', () => { }, ); }); + + describe('if one of the RPC endpoints was represented by the selected network client', () => { + it('invisibly selects the network client created for the RPC endpoint', async () => { + uuidV4Mock.mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB'); + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + nativeCurrency: 'TOKEN', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint', + }), + ], + }); + + await withController( + { + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + }, + async ({ controller }) => { + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 1', + }, + }, + ]), + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 2', + }, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + chainId: infuraChainId, + rpcUrl: 'https://rpc.endpoint', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + chainId: anotherInfuraChainId, + rpcUrl: 'https://rpc.endpoint', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); + expect(controller.state.selectedNetworkClientId).toBe( + 'AAAA-AAAA-AAAA-AAAA', + ); + const networkClient1 = controller.getSelectedNetworkClient(); + assert(networkClient1, 'Network client is somehow unset'); + const result1 = await networkClient1.provider.request({ + method: 'test', + }); + expect(result1).toBe('test response from 1'); + + await controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + chainId: anotherInfuraChainId, + }); + + expect(controller.state.selectedNetworkClientId).toBe( + 'BBBB-BBBB-BBBB-BBBB', + ); + const networkClient2 = controller.getSelectedNetworkClient(); + assert(networkClient2, 'Network client is somehow unset'); + const result2 = await networkClient1.provider.request({ + method: 'test', + }); + expect(result2).toBe('test response from 2'); + }, + ); + }); + + it('updates selectedNetworkClientId and networkConfigurationsByChainId at the same time', async () => { + uuidV4Mock.mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB'); + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + nativeCurrency: 'TOKEN', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint', + }), + ], + }); + + await withController( + { + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + }, + }, + }, + async ({ controller, messenger }) => { + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 1', + }, + }, + ]), + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 2', + }, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + chainId: infuraChainId, + rpcUrl: 'https://rpc.endpoint', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + chainId: anotherInfuraChainId, + rpcUrl: 'https://rpc.endpoint', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); + + const promiseForStateChanges = waitForStateChanges({ + messenger, + count: 1, + }); + + await controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, + chainId: anotherInfuraChainId, + }); + const stateChanges = await promiseForStateChanges; + expect(stateChanges).toStrictEqual([ + [ + expect.any(Object), + expect.arrayContaining([ + expect.objectContaining({ + op: 'replace', + path: ['selectedNetworkClientId'], + value: 'BBBB-BBBB-BBBB-BBBB', + }), + expect.objectContaining({ + op: 'remove', + path: [ + 'networkConfigurationsByChainId', + infuraChainId, + ], + }), + expect.objectContaining({ + op: 'add', + path: [ + 'networkConfigurationsByChainId', + anotherInfuraChainId, + ], + }), + ]), + ], + ]); + }, + ); + }); + }); }); }, ); @@ -6717,13 +9862,13 @@ describe('NetworkController', () => { }, }), }, - ({ controller }) => { - expect(() => + async ({ controller }) => { + await expect(() => controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, chainId: '0x2448', }), - ).toThrow( + ).rejects.toThrow( "Cannot move network from chain 0x1337 to 0x2448 as another network for that chain already exists ('Some Network')", ); }, @@ -6747,17 +9892,17 @@ describe('NetworkController', () => { }, }), }, - ({ controller }) => { + async ({ controller }) => { const newRpcEndpoint = buildInfuraRpcEndpoint( InfuraNetworkType.goerli, ); - expect(() => + await expect(() => controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, chainId: '0x2448', rpcEndpoints: [newRpcEndpoint], }), - ).toThrow( + ).rejects.toThrow( new Error( "Could not update network to point to same RPC endpoint as existing network for chain 0x5 ('Goerli')", ), @@ -6795,8 +9940,30 @@ describe('NetworkController', () => { }, }), }, - ({ controller }) => { - controller.updateNetwork('0x1337', { + async ({ controller }) => { + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 1', + }, + }, + ]), + ]; + const fakeNetworkClients = [buildFakeClient(fakeProviders[0])]; + mockCreateNetworkClient() + .calledWith({ + chainId: '0x2448', + rpcUrl: 'https://test.endpoint/1', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[0]); + + await controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, chainId: '0x2448', }); @@ -6853,7 +10020,28 @@ describe('NetworkController', () => { }, }), }, - ({ controller }) => { + async ({ controller }) => { + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 1', + }, + }, + ]), + ]; + const fakeNetworkClients = [buildFakeClient(fakeProviders[0])]; + mockCreateNetworkClient() + .calledWith({ + chainId: '0x2448', + rpcUrl: 'https://test.endpoint/1', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[0]); const existingNetworkClient1 = controller.getNetworkClientById( 'AAAA-AAAA-AAAA-AAAA', ); @@ -6863,7 +10051,7 @@ describe('NetworkController', () => { ); const destroySpy2 = jest.spyOn(existingNetworkClient2, 'destroy'); - controller.updateNetwork('0x1337', { + await controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, chainId: '0x2448', }); @@ -6917,8 +10105,30 @@ describe('NetworkController', () => { }, }), }, - ({ controller }) => { - controller.updateNetwork('0x1337', { + async ({ controller }) => { + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 1', + }, + }, + ]), + ]; + const fakeNetworkClients = [buildFakeClient(fakeProviders[0])]; + mockCreateNetworkClient() + .calledWith({ + chainId: '0x2448', + rpcUrl: 'https://test.endpoint/1', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[0]); + + await controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, chainId: '0x2448', }); @@ -6987,8 +10197,30 @@ describe('NetworkController', () => { }, }), }, - ({ controller }) => { - const updatedNetworkConfiguration = controller.updateNetwork( + async ({ controller }) => { + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 1', + }, + }, + ]), + ]; + const fakeNetworkClients = [buildFakeClient(fakeProviders[0])]; + mockCreateNetworkClient() + .calledWith({ + chainId: '0x2448', + rpcUrl: 'https://test.endpoint/1', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[0]); + + const updatedNetworkConfiguration = await controller.updateNetwork( '0x1337', { ...networkConfigurationToUpdate, @@ -7013,6 +10245,201 @@ describe('NetworkController', () => { }, ); }); + + describe('if one of the RPC endpoints was represented by the selected network client', () => { + it('invisibly selects the network client created for the RPC endpoint', async () => { + uuidV4Mock.mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB'); + const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ + nativeCurrency: 'TOKEN', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint', + }), + ], + }); + + await withController( + { + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }, + }, + async ({ controller }) => { + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 1', + }, + }, + ]), + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 2', + }, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://rpc.endpoint', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + chainId: '0x2448', + rpcUrl: 'https://rpc.endpoint', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); + expect(controller.state.selectedNetworkClientId).toBe( + 'AAAA-AAAA-AAAA-AAAA', + ); + const networkClient1 = controller.getSelectedNetworkClient(); + assert(networkClient1, 'Network client is somehow unset'); + const result1 = await networkClient1.provider.request({ + method: 'test', + }); + expect(result1).toBe('test response from 1'); + + await controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + chainId: '0x2448', + }); + + expect(controller.state.selectedNetworkClientId).toBe( + 'BBBB-BBBB-BBBB-BBBB', + ); + const networkClient2 = controller.getSelectedNetworkClient(); + assert(networkClient2, 'Network client is somehow unset'); + const result2 = await networkClient1.provider.request({ + method: 'test', + }); + expect(result2).toBe('test response from 2'); + }, + ); + }); + + it('updates selectedNetworkClientId and networkConfigurationsByChainId at the same time', async () => { + uuidV4Mock.mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB'); + const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ + nativeCurrency: 'TOKEN', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + name: 'Endpoint 1', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://rpc.endpoint', + }), + ], + }); + + await withController( + { + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + }, + }, + }, + async ({ controller, messenger }) => { + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 1', + }, + }, + ]), + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response from 2', + }, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://rpc.endpoint', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + chainId: '0x2448', + rpcUrl: 'https://rpc.endpoint', + ticker: 'TOKEN', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); + + const promiseForStateChanges = waitForStateChanges({ + messenger, + count: 1, + }); + + await controller.updateNetwork('0x1337', { + ...networkConfigurationToUpdate, + chainId: '0x2448', + }); + const stateChanges = await promiseForStateChanges; + expect(stateChanges).toStrictEqual([ + [ + expect.any(Object), + expect.arrayContaining([ + expect.objectContaining({ + op: 'replace', + path: ['selectedNetworkClientId'], + value: 'BBBB-BBBB-BBBB-BBBB', + }), + expect.objectContaining({ + op: 'remove', + path: ['networkConfigurationsByChainId', '0x1337'], + }), + expect.objectContaining({ + op: 'add', + path: ['networkConfigurationsByChainId', '0x2448'], + }), + ]), + ], + ]); + }, + ); + }); + }); }); describe('if nothing is being changed', () => { @@ -7021,7 +10448,7 @@ describe('NetworkController', () => { // This is a string. // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - describe(`if the given chain ID is the Infura-supported chain ${infuraChainId}`, () => { + describe(`given the ID of the Infura-supported chain ${infuraChainId}`, () => { it('makes no updates to state', async () => { const existingNetworkConfiguration = buildInfuraNetworkConfiguration(infuraNetworkType); @@ -7037,8 +10464,8 @@ describe('NetworkController', () => { }, ), }, - ({ controller }) => { - controller.updateNetwork( + async ({ controller }) => { + await controller.updateNetwork( infuraChainId, existingNetworkConfiguration, ); @@ -7067,12 +10494,12 @@ describe('NetworkController', () => { }, ), }, - ({ controller }) => { + async ({ controller }) => { const existingNetworkClient = controller.getNetworkClientById(infuraNetworkType); const destroySpy = jest.spyOn(existingNetworkClient, 'destroy'); - controller.updateNetwork( + await controller.updateNetwork( infuraChainId, existingNetworkConfiguration, ); @@ -7102,8 +10529,8 @@ describe('NetworkController', () => { }, ), }, - ({ controller }) => { - controller.updateNetwork( + async ({ controller }) => { + await controller.updateNetwork( infuraChainId, existingNetworkConfiguration, ); @@ -7118,7 +10545,7 @@ describe('NetworkController', () => { }); } - describe('if the given chain ID is not an Infura-supported chain', () => { + describe('given the ID of a non-Infura-supported chain', () => { it('makes no updates to state', async () => { const existingNetworkConfiguration = buildCustomNetworkConfiguration({ chainId: '0x1337', @@ -7133,8 +10560,11 @@ describe('NetworkController', () => { }, }), }, - ({ controller }) => { - controller.updateNetwork('0x1337', existingNetworkConfiguration); + async ({ controller }) => { + await controller.updateNetwork( + '0x1337', + existingNetworkConfiguration, + ); expect( controller.state.networkConfigurationsByChainId['0x1337'], @@ -7162,13 +10592,16 @@ describe('NetworkController', () => { }, }), }, - ({ controller }) => { + async ({ controller }) => { const existingNetworkClient = controller.getNetworkClientById( 'AAAA-AAAA-AAAA-AAAA', ); const destroySpy = jest.spyOn(existingNetworkClient, 'destroy'); - controller.updateNetwork('0x1337', existingNetworkConfiguration); + await controller.updateNetwork( + '0x1337', + existingNetworkConfiguration, + ); expect(destroySpy).not.toHaveBeenCalled(); }, @@ -7194,8 +10627,11 @@ describe('NetworkController', () => { }, }), }, - ({ controller }) => { - controller.updateNetwork('0x1337', existingNetworkConfiguration); + async ({ controller }) => { + await controller.updateNetwork( + '0x1337', + existingNetworkConfiguration, + ); // Once when the controller is initialized, but no more expect(createAutoManagedNetworkClientSpy).toHaveBeenCalledTimes( @@ -7246,7 +10682,7 @@ describe('NetworkController', () => { // This is a string. // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - describe(`when the given chain ID is the Infura-supported chain ${infuraChainId}`, () => { + describe(`given the ID of the Infura-supported chain ${infuraChainId}`, () => { it('removes the existing network configuration from state', async () => { await withController( { @@ -7339,7 +10775,7 @@ describe('NetworkController', () => { }); } - describe('when the given chain ID is not an Infura-supported chain', () => { + describe('given the ID of a non-Infura-supported chain', () => { it('removes the existing network configuration', async () => { await withController( { From 1d11bf30466dddc4679f1d5af07d2038e3ee0108 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Mon, 15 Jul 2024 09:57:15 -0600 Subject: [PATCH 24/49] Remove TODOs --- packages/network-controller/src/NetworkController.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index 148e37e9aed..275ed6073d5 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -1809,7 +1809,6 @@ export class NetworkController extends BaseController< correctedReplacementSelectedRpcEndpointIndex ]; - // TODO: Test if (rpcEndpointToSelect === undefined) { throw new Error( `Could not update network: \`replacementSelectedRpcEndpointIndex\` ${correctedReplacementSelectedRpcEndpointIndex} does not refer to an entry in \`rpcEndpoints\``, @@ -1817,7 +1816,6 @@ export class NetworkController extends BaseController< } } - // TODO: Test if ( rpcEndpointToSelect && rpcEndpointToSelect.networkClientId !== this.state.selectedNetworkClientId From b6d6afb9d96e5dbb74e81b214ed602833fcc3295 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Mon, 15 Jul 2024 11:40:35 -0600 Subject: [PATCH 25/49] Remove another TODO --- packages/network-controller/src/NetworkController.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index 275ed6073d5..7da8fbac3a6 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -120,7 +120,6 @@ export type InfuraRpcEndpoint = { * `{infuraProjectId}`, which will get replaced with the Infura project ID * when the network client is created. */ - // TODO: Link this to networkClientId url: `https://${InfuraNetworkType}.infura.io/v3/{infuraProjectId}`; }; From e108ea36a6909e4105d2a94efc186a43e8f09203 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Tue, 16 Jul 2024 09:59:44 -0600 Subject: [PATCH 26/49] Update typo in changelog --- packages/network-controller/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/network-controller/CHANGELOG.md b/packages/network-controller/CHANGELOG.md index c7c40c16bf6..d82e1a6c535 100644 --- a/packages/network-controller/CHANGELOG.md +++ b/packages/network-controller/CHANGELOG.md @@ -30,7 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `nickname` has been renamed to `name`. - `rpcEndpoints` has been added as well. This is an an array of objects, where each object has properties `name` (optional), `networkClientId` (optional), `type`, and `url`. - `defaultRpcEndpointIndex` has been added. This must point to an entry in `rpcEndpoints`. - - The block explorer URL is no longer located in `rpcPrefs` and is no longer restricted to one: `blockExplorerUrls` has been added along with a corresponding property `defaultRpcEndpointIndex`, which must point to an entry in `blockExplorerUrls`. + - The block explorer URL is no longer located in `rpcPrefs` and is no longer restricted to one: `blockExplorerUrls` has been added along with a corresponding property `defaultBlockExplorerUrlIndex`, which must point to an entry in `blockExplorerUrls`. - `id` has been removed. Previously, this represented the ID of the network client associated with the network configuration. Since network clients are now created from RPC endpoints, the equivalent to this is the `networkClientId` property on an `RpcEndpoint`. - **BREAKING:** The network controller messenger must now allow the action `NetworkController:getNetworkConfigurationByChainId` ([#4268](https://github.com/MetaMask/core/pull/4286)) - **BREAKING:** The network controller messenger must now allow the event `NetworkController:networkAdded` ([#4268](https://github.com/MetaMask/core/pull/4286)) From 7d111533035cbfd36b8cd36af91c6192606212a7 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Tue, 16 Jul 2024 09:59:55 -0600 Subject: [PATCH 27/49] Clarify some other things in the changelog --- packages/network-controller/CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/network-controller/CHANGELOG.md b/packages/network-controller/CHANGELOG.md index d82e1a6c535..d260660e568 100644 --- a/packages/network-controller/CHANGELOG.md +++ b/packages/network-controller/CHANGELOG.md @@ -25,10 +25,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **BREAKING:** Replace `NetworkConfiguration` type with a new definition ([#4268](https://github.com/MetaMask/core/pull/4286)) - A network configuration no longer represents a single RPC endpoint but rather a collection of RPC endpoints that can all be used to interface with a single chain. - - The only property that has been retained on this type is `chainId`. + - The only property that has brought over to this type unchanged is `chainId`. - `ticker` has been renamed to `nativeCurrency`. - `nickname` has been renamed to `name`. - - `rpcEndpoints` has been added as well. This is an an array of objects, where each object has properties `name` (optional), `networkClientId` (optional), `type`, and `url`. + - `rpcEndpoints` has been added. This is an an array of objects, where each object has properties `name` (optional), `networkClientId` (optional), `type`, and `url`. - `defaultRpcEndpointIndex` has been added. This must point to an entry in `rpcEndpoints`. - The block explorer URL is no longer located in `rpcPrefs` and is no longer restricted to one: `blockExplorerUrls` has been added along with a corresponding property `defaultBlockExplorerUrlIndex`, which must point to an entry in `blockExplorerUrls`. - `id` has been removed. Previously, this represented the ID of the network client associated with the network configuration. Since network clients are now created from RPC endpoints, the equivalent to this is the `networkClientId` property on an `RpcEndpoint`. @@ -38,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `networkConfigurationsByChainId` cannot be empty. - The `chainId` of a network configuration in `networkConfigurationsByChainId` must match the chain ID it is filed under. - The `defaultRpcEndpointIndex` of a network configuration in `networkConfigurationsByChainId` must point to an entry in its `rpcEndpoints`. + - The `defaultBlockExplorerUrlIndex` of a network configuration in `networkConfigurationsByChainId` must point to an entry in its `blockExplorerUrls`. - `selectedNetworkClientId` must match the `networkClientId` of an RPC endpoint in `networkConfigurationsByChainId`. - **BREAKING:** Update `getNetworkConfigurationByNetworkClientId` so that when given an Infura network name (that is, a value from `InfuraNetworkType`), it will return a masked version of the RPC endpoint URL for the associated Infura network ([#4268](https://github.com/MetaMask/core/pull/4286)) - If you want the unmasked version, you'll need the `url` property from the network _client_ configuration, which you can get by calling `getNetworkClientById` and then accessing the `configuration` property off of the network client. From c875acff97b27bc85e3072d2b4604fb6c0961c0d Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Tue, 16 Jul 2024 10:12:41 -0600 Subject: [PATCH 28/49] Fix lint violations? --- .../src/NetworkController.ts | 34 +++++++++++-------- .../tests/NetworkController.test.ts | 4 +++ 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index 7da8fbac3a6..95938563238 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -519,9 +519,9 @@ function getDefaultNetworkConfigurationsByChainId(): Record< Record >((obj, infuraNetworkType) => { const chainId = ChainId[infuraNetworkType]; - // False negative - this is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions const rpcEndpointUrl = + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}` as const; const networkConfiguration: NetworkConfiguration = { @@ -1775,6 +1775,8 @@ export class NetworkController extends BaseController< }) ) { throw new Error( + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions `Could not update network: Cannot update RPC endpoints in such a way that the selected network '${this.state.selectedNetworkClientId}' would be removed without a replacement. Choose a different RPC endpoint as the selected network via the \`replacementSelectedRpcEndpointIndex\` option.`, ); } @@ -2008,13 +2010,19 @@ export class NetworkController extends BaseController< const existingNetworkConfigurationViaChainId = this.state.networkConfigurationsByChainId[networkFields.chainId]; if (existingNetworkConfigurationViaChainId !== undefined) { - throw new Error( - // False negative - these are strings. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - existingNetworkConfiguration === null - ? `Could not add network for chain ${args.networkFields.chainId} as another network for that chain already exists ('${existingNetworkConfigurationViaChainId.name}')` - : `Cannot move network from chain ${existingNetworkConfiguration.chainId} to ${networkFields.chainId} as another network for that chain already exists ('${existingNetworkConfigurationViaChainId.name}')`, - ); + if (existingNetworkConfiguration === null) { + throw new Error( + // False negative - these are strings. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `Could not add network for chain ${args.networkFields.chainId} as another network for that chain already exists ('${existingNetworkConfigurationViaChainId.name}')`, + ); + } else { + throw new Error( + // False negative - these are strings. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `Cannot move network from chain ${existingNetworkConfiguration.chainId} to ${networkFields.chainId} as another network for that chain already exists ('${existingNetworkConfigurationViaChainId.name}')`, + ); + } } } @@ -2056,11 +2064,9 @@ export class NetworkController extends BaseController< isInfuraNetworkType(networkClientId) ) { throw new Error( - `${errorMessagePrefix}: Custom RPC endpoint '${ - // This is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - rpcEndpointFields.url - }' has invalid network client ID '${networkClientId}'`, + // This is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `${errorMessagePrefix}: Custom RPC endpoint '${rpcEndpointFields.url}' has invalid network client ID '${networkClientId}'`, ); } diff --git a/packages/network-controller/tests/NetworkController.test.ts b/packages/network-controller/tests/NetworkController.test.ts index 9a54fe5e64c..e532da5e8ea 100644 --- a/packages/network-controller/tests/NetworkController.test.ts +++ b/packages/network-controller/tests/NetworkController.test.ts @@ -4655,6 +4655,8 @@ describe('NetworkController', () => { { networkClientId: infuraNetworkType, type: RpcEndpointType.Infura, + // This is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions url: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`, }, ], @@ -4706,6 +4708,8 @@ describe('NetworkController', () => { { networkClientId: infuraNetworkType, type: RpcEndpointType.Infura, + // This is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions url: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`, }, ], From dbcd8509e70129d5ca0e6b41885d5b153c7d489d Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Wed, 17 Jul 2024 12:14:21 -0600 Subject: [PATCH 29/49] Fix bug with detection of removing selectedNetworkClientId --- .../src/NetworkController.ts | 18 ++++- .../tests/NetworkController.test.ts | 71 ++++++++++++++++--- packages/network-controller/tests/helpers.ts | 23 ++++-- 3 files changed, 97 insertions(+), 15 deletions(-) diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index 95938563238..c44daf9fcdf 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -1759,11 +1759,25 @@ export class NetworkController extends BaseController< networkClientOperations, }); + /* + console.log( + 'replacementSelectedRpcEndpointIndex', + replacementSelectedRpcEndpointIndex, + 'updatedNetworkConfiguration', + updatedNetworkConfiguration, + 'networkClientOperations', + networkClientOperations, + 'this.state.selectedNetworkClientId', + this.state.selectedNetworkClientId, + ) + */ if ( replacementSelectedRpcEndpointIndex === undefined && - !updatedNetworkConfiguration.rpcEndpoints.some((rpcEndpoint) => { + networkClientOperations.some((networkClientOperation) => { return ( - rpcEndpoint.networkClientId === this.state.selectedNetworkClientId + networkClientOperation.type === 'remove' && + networkClientOperation.rpcEndpoint.networkClientId === + this.state.selectedNetworkClientId ); }) && !networkClientOperations.some((networkClientOperation) => { diff --git a/packages/network-controller/tests/NetworkController.test.ts b/packages/network-controller/tests/NetworkController.test.ts index e532da5e8ea..28e0f22ec15 100644 --- a/packages/network-controller/tests/NetworkController.test.ts +++ b/packages/network-controller/tests/NetworkController.test.ts @@ -10555,14 +10555,18 @@ describe('NetworkController', () => { chainId: '0x1337', }); + const state = buildNetworkControllerStateWithSelectedChain('0x2448', { + networkConfigurationsByChainId: { + '0x1337': existingNetworkConfiguration, + '0x2448': buildCustomNetworkConfiguration({ + chainId: '0x2448', + }), + }, + }); + await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ - networkConfigurationsByChainId: { - '0x1337': existingNetworkConfiguration, - }, - }), + state, }, async ({ controller }) => { await controller.updateNetwork( @@ -13683,8 +13687,10 @@ function getNetworkConfigurationsByNetworkClientId( * property must match the `networkClientId` of an RPC endpoint in * `networkConfigurationsByChainId`. Sometimes when writing tests we care about * what the `selectedNetworkClientId` is, but sometimes we don't and we'd rather - * have this property automatically filled in for us. This function takes care - * of that step. + * have this property automatically filled in for us. + * + * This function takes care of filling in the `selectedNetworkClientId` using + * the first RPC endpoint of the first network configuration given. * * @param networkControllerState - The desired NetworkController state * overrides. @@ -13722,3 +13728,52 @@ function buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ ...rest, }; } + +/** + * When initializing NetworkController with state, the `selectedNetworkClientId` + * property must match the `networkClientId` of an RPC endpoint in + * `networkConfigurationsByChainId`. Sometimes when writing tests we care about + * what the `selectedNetworkClientId` is, but sometimes we don't and we'd rather + * have this property automatically filled in for us. + * + * This function takes care of filling in the `selectedNetworkClientId` using + * the first RPC endpoint of the network configuration with the given chain ID. + * + * @param chainId - The chain ID to use. + * @param networkControllerState - The desired NetworkController state + * overrides. + * @param networkControllerState.networkConfigurationsByChainId - The desired + * `networkConfigurationsByChainId`. + * @param networkControllerState.selectedNetworkClientId - The desired + * `selectedNetworkClientId`; if not provided, then will be set to the + * `networkClientId` of the first RPC endpoint of the network configuration with + * the given chain ID. + * @returns The complete NetworkController state with `selectedNetworkClientId` + * properly filled in. + */ +function buildNetworkControllerStateWithSelectedChain( + chainId: Hex, + { + networkConfigurationsByChainId, + selectedNetworkClientId: givenSelectedNetworkClientId, + ...rest + }: Partial> & + Pick, +) { + if (givenSelectedNetworkClientId === undefined) { + const networkConfiguration = networkConfigurationsByChainId[chainId]; + const selectedNetworkClientId = + networkConfiguration.rpcEndpoints[0].networkClientId; + return { + networkConfigurationsByChainId, + selectedNetworkClientId, + ...rest, + }; + } + + return { + networkConfigurationsByChainId, + selectedNetworkClientId: givenSelectedNetworkClientId, + ...rest, + }; +} diff --git a/packages/network-controller/tests/helpers.ts b/packages/network-controller/tests/helpers.ts index 709f5c4388a..2c6067011cd 100644 --- a/packages/network-controller/tests/helpers.ts +++ b/packages/network-controller/tests/helpers.ts @@ -248,7 +248,7 @@ export function buildCustomNetworkConfiguration( nativeCurrency: () => 'TOKEN', rpcEndpoints: () => [ buildCustomRpcEndpoint({ - url: 'https://test.endpoint', + url: generateCustomRpcEndpointUrl(), }), ], }, @@ -343,7 +343,7 @@ export function buildCustomRpcEndpoint( { networkClientId: () => uuidV4(), type: () => RpcEndpointType.Custom as const, - url: () => 'https://test.endpoint', + url: () => generateCustomRpcEndpointUrl(), }, overrides, ); @@ -369,7 +369,7 @@ export function buildAddNetworkFields( nativeCurrency: () => 'TOKEN', rpcEndpoints: () => [ buildAddNetworkCustomRpcEndpointFields({ - url: 'https://test.endpoint', + url: generateCustomRpcEndpointUrl(), }), ], }, @@ -403,7 +403,7 @@ export function buildAddNetworkCustomRpcEndpointFields( return buildTestObject( { type: () => RpcEndpointType.Custom as const, - url: () => 'https://test.endpoint', + url: () => generateCustomRpcEndpointUrl(), }, overrides, ); @@ -423,8 +423,21 @@ export function buildUpdateNetworkCustomRpcEndpointFields( return buildTestObject( { type: () => RpcEndpointType.Custom as const, - url: () => 'https://test.endpoint', + url: () => generateCustomRpcEndpointUrl(), }, overrides, ); } + +let testEndpointCounter = 0; + +/** + * Generates a unique custom RPC endpoint URL for testing. + * + * @returns The generated RPC endpoint URL. + */ +function generateCustomRpcEndpointUrl(): string { + const url = `https://test.endpoint/${testEndpointCounter}`; + testEndpointCounter += 1; + return url; +} From 8fd0716fa54332600d8e72897bbc7b15ef05b8fc Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Wed, 17 Jul 2024 12:21:24 -0600 Subject: [PATCH 30/49] Fix tests for SelectedNetworkController --- .../tests/SelectedNetworkController.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/selected-network-controller/tests/SelectedNetworkController.test.ts b/packages/selected-network-controller/tests/SelectedNetworkController.test.ts index 5c5ce941af2..90d92c5fb73 100644 --- a/packages/selected-network-controller/tests/SelectedNetworkController.test.ts +++ b/packages/selected-network-controller/tests/SelectedNetworkController.test.ts @@ -302,7 +302,7 @@ describe('SelectedNetworkController', () => { messenger.publish( 'NetworkController:stateChange', { - selectedNetworkClientId: InfuraNetworkType.goerli, + selectedNetworkClientId: 'goerli', networkConfigurationsByChainId: {}, networksMetadata: {}, }, @@ -331,7 +331,7 @@ describe('SelectedNetworkController', () => { messenger.publish( 'NetworkController:stateChange', { - selectedNetworkClientId: InfuraNetworkType.goerli, + selectedNetworkClientId: 'goerli', networkConfigurationsByChainId: {}, networksMetadata: {}, }, From a1fe96faac31345c33afe91a63afe92983512da9 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Wed, 17 Jul 2024 14:29:24 -0600 Subject: [PATCH 31/49] Fix transaction controller tests --- .../TransactionControllerIntegration.test.ts | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/packages/transaction-controller/src/TransactionControllerIntegration.test.ts b/packages/transaction-controller/src/TransactionControllerIntegration.test.ts index 8153970d781..1a99f10126f 100644 --- a/packages/transaction-controller/src/TransactionControllerIntegration.test.ts +++ b/packages/transaction-controller/src/TransactionControllerIntegration.test.ts @@ -70,7 +70,7 @@ jest.mock('uuid', () => { return { ...actual, - v4: jest.fn().mockReturnValue('UUID'), + v4: jest.fn(), }; }); @@ -258,8 +258,16 @@ const setupController = async ( describe('TransactionController Integration', () => { let clock: SinonFakeTimers; + let uuidCounter = 0; + beforeEach(() => { clock = useFakeTimers(); + + uuidV4Mock.mockImplementation(() => { + const uuid = `UUID-${uuidCounter}`; + uuidCounter += 1; + return uuid; + }); }); afterEach(() => { @@ -882,7 +890,7 @@ describe('TransactionController Integration', () => { 'Could not find network configuration for Goerli', ); const updatedGoerliNetworkConfiguration = - networkController.updateNetwork(ChainId.goerli, { + await networkController.updateNetwork(ChainId.goerli, { ...existingGoerliNetworkConfiguration, rpcEndpoints: [ ...existingGoerliNetworkConfiguration.rpcEndpoints, @@ -1047,9 +1055,8 @@ describe('TransactionController Integration', () => { existingGoerliNetworkConfiguration, 'Could not find network configuration for Goerli', ); - const updatedGoerliNetworkConfiguration = networkController.updateNetwork( - ChainId.goerli, - { + const updatedGoerliNetworkConfiguration = + await networkController.updateNetwork(ChainId.goerli, { ...existingGoerliNetworkConfiguration, rpcEndpoints: [ ...existingGoerliNetworkConfiguration.rpcEndpoints, @@ -1057,8 +1064,7 @@ describe('TransactionController Integration', () => { url: 'https://mock.rpc.url', }), ], - }, - ); + }); const otherGoerliRpcEndpoint = updatedGoerliNetworkConfiguration.rpcEndpoints.find((rpcEndpoint) => { return rpcEndpoint.url === 'https://mock.rpc.url'; @@ -1429,7 +1435,7 @@ describe('TransactionController Integration', () => { 'Could not find network configuration for Goerli', ); const updatedGoerliNetworkConfiguration = - networkController.updateNetwork(ChainId.goerli, { + await networkController.updateNetwork(ChainId.goerli, { ...existingGoerliNetworkConfiguration, rpcEndpoints: [ ...existingGoerliNetworkConfiguration.rpcEndpoints, @@ -1923,9 +1929,8 @@ describe('TransactionController Integration', () => { existingGoerliNetworkConfiguration, 'Could not find network configuration for Goerli', ); - const updatedGoerliNetworkConfiguration = networkController.updateNetwork( - ChainId.goerli, - { + const updatedGoerliNetworkConfiguration = + await networkController.updateNetwork(ChainId.goerli, { ...existingGoerliNetworkConfiguration, rpcEndpoints: [ ...existingGoerliNetworkConfiguration.rpcEndpoints, @@ -1933,8 +1938,7 @@ describe('TransactionController Integration', () => { url: 'https://mock.rpc.url', }), ], - }, - ); + }); const otherGoerliRpcEndpoint = updatedGoerliNetworkConfiguration.rpcEndpoints.find((rpcEndpoint) => { return rpcEndpoint.url === 'https://mock.rpc.url'; From 4331bd99e7e83d50e050831a8548cd8f74de9695 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Fri, 19 Jul 2024 11:37:59 -0600 Subject: [PATCH 32/49] Ensure we do not test updating selected network configuration in most cases --- .../tests/NetworkController.test.ts | 1582 +++++++++++------ 1 file changed, 1034 insertions(+), 548 deletions(-) diff --git a/packages/network-controller/tests/NetworkController.test.ts b/packages/network-controller/tests/NetworkController.test.ts index 28e0f22ec15..4372e6b714e 100644 --- a/packages/network-controller/tests/NetworkController.test.ts +++ b/packages/network-controller/tests/NetworkController.test.ts @@ -4552,14 +4552,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId( - { - networkConfigurationsByChainId: { - [infuraChainId]: networkConfigurationToUpdate, - }, - }, - ), + state: { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { @@ -4574,11 +4582,10 @@ describe('NetworkController', () => { ], }); - // Skipping the 1st call because it's for the custom RPC - // endpoint + // Skipping network client creation for existing RPC endpoints expect( createAutoManagedNetworkClientSpy, - ).toHaveBeenNthCalledWith(2, { + ).toHaveBeenNthCalledWith(3, { chainId: infuraChainId, infuraProjectId: 'some-infura-project-id', network: infuraNetworkType, @@ -4591,12 +4598,6 @@ describe('NetworkController', () => { controller.getNetworkClientRegistry(), ), ).toStrictEqual({ - 'AAAA-AAAA-AAAA-AAAA': { - chainId: infuraChainId, - rpcUrl: 'https://rpc.network', - ticker: infuraNativeTokenName, - type: NetworkClientType.Custom, - }, [infuraNetworkType]: { chainId: infuraChainId, infuraProjectId: 'some-infura-project-id', @@ -4604,6 +4605,18 @@ describe('NetworkController', () => { ticker: infuraNativeTokenName, type: NetworkClientType.Infura, }, + 'AAAA-AAAA-AAAA-AAAA': { + chainId: infuraChainId, + rpcUrl: 'https://rpc.network', + ticker: infuraNativeTokenName, + type: NetworkClientType.Custom, + }, + 'ZZZZ-ZZZZ-ZZZZ-ZZZZ': { + chainId: '0x9999', + rpcUrl: 'https://selected.endpoint', + ticker: 'TEST-9999', + type: NetworkClientType.Custom, + }, }); }, ); @@ -4622,14 +4635,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId( - { - networkConfigurationsByChainId: { - [infuraChainId]: networkConfigurationToUpdate, - }, - }, - ), + state: { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { @@ -4678,14 +4699,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId( - { - networkConfigurationsByChainId: { - [infuraChainId]: networkConfigurationToUpdate, - }, - }, - ), + state: { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { @@ -4735,14 +4764,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId( - { - networkConfigurationsByChainId: { - [infuraChainId]: networkConfigurationToUpdate, - }, - }, - ), + state: { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { @@ -4766,10 +4803,10 @@ describe('NetworkController', () => { ], }); - // Skipping the 1st call because it's for the Infura network + // Skipping network client creation for existing RPC endpoints expect( createAutoManagedNetworkClientSpy, - ).toHaveBeenNthCalledWith(2, { + ).toHaveBeenNthCalledWith(3, { chainId: infuraChainId, rpcUrl: 'https://rpc.endpoint/1', ticker: infuraNativeTokenName, @@ -4777,7 +4814,7 @@ describe('NetworkController', () => { }); expect( createAutoManagedNetworkClientSpy, - ).toHaveBeenNthCalledWith(3, { + ).toHaveBeenNthCalledWith(4, { chainId: infuraChainId, rpcUrl: 'https://rpc.endpoint/2', ticker: infuraNativeTokenName, @@ -4808,6 +4845,12 @@ describe('NetworkController', () => { ticker: infuraNativeTokenName, type: NetworkClientType.Custom, }, + 'ZZZZ-ZZZZ-ZZZZ-ZZZZ': { + chainId: '0x9999', + rpcUrl: 'https://selected.endpoint', + ticker: 'TEST-9999', + type: NetworkClientType.Custom, + }, }); }, ); @@ -4815,35 +4858,39 @@ describe('NetworkController', () => { it('assigns the ID of the created network client to each RPC endpoint in state', async () => { uuidV4Mock - .mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB') - .mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC'); - const rpcEndpoint1 = buildCustomRpcEndpoint({ - name: 'Endpoint 1', - networkClientId: 'AAAA-AAAA-AAAA-AAAA', - url: 'https://rpc.endpoint/1', - }); - const networkConfigurationToUpdate = buildNetworkConfiguration({ - chainId: '0x1337', - rpcEndpoints: [rpcEndpoint1], - }); + .mockReturnValueOnce('AAAA-AAAA-AAAA-AAAA') + .mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB'); + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + rpcEndpoints: [buildInfuraRpcEndpoint(infuraNetworkType)], + }); await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId( - { - networkConfigurationsByChainId: { - '0x1337': networkConfigurationToUpdate, - }, - }, - ), + state: { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, + infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { - await controller.updateNetwork('0x1337', { + await controller.updateNetwork(infuraChainId, { ...networkConfigurationToUpdate, defaultRpcEndpointIndex: 0, rpcEndpoints: [ - rpcEndpoint1, + ...networkConfigurationToUpdate.rpcEndpoints, buildUpdateNetworkCustomRpcEndpointFields({ name: 'Endpoint 2', url: 'https://rpc.endpoint/2', @@ -4856,20 +4903,22 @@ describe('NetworkController', () => { }); expect( - controller.state.networkConfigurationsByChainId['0x1337'], + controller.state.networkConfigurationsByChainId[ + infuraChainId + ], ).toStrictEqual({ ...networkConfigurationToUpdate, rpcEndpoints: [ - rpcEndpoint1, + ...networkConfigurationToUpdate.rpcEndpoints, { name: 'Endpoint 2', - networkClientId: 'BBBB-BBBB-BBBB-BBBB', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', type: RpcEndpointType.Custom, url: 'https://rpc.endpoint/2', }, { name: 'Endpoint 3', - networkClientId: 'CCCC-CCCC-CCCC-CCCC', + networkClientId: 'BBBB-BBBB-BBBB-BBBB', type: RpcEndpointType.Custom, url: 'https://rpc.endpoint/3', }, @@ -4881,36 +4930,40 @@ describe('NetworkController', () => { it('returns the updated network configuration', async () => { uuidV4Mock - .mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB') - .mockReturnValueOnce('CCCC-CCCC-CCCC-CCCC'); - const rpcEndpoint1 = buildCustomRpcEndpoint({ - name: 'Endpoint 1', - networkClientId: 'AAAA-AAAA-AAAA-AAAA', - url: 'https://rpc.endpoint/1', - }); - const networkConfigurationToUpdate = buildNetworkConfiguration({ - chainId: '0x1337', - rpcEndpoints: [rpcEndpoint1], - }); + .mockReturnValueOnce('AAAA-AAAA-AAAA-AAAA') + .mockReturnValueOnce('BBBB-BBBB-BBBB-BBBB'); + const networkConfigurationToUpdate = + buildInfuraNetworkConfiguration(infuraNetworkType, { + rpcEndpoints: [buildInfuraRpcEndpoint(infuraNetworkType)], + }); await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId( - { - networkConfigurationsByChainId: { - '0x1337': networkConfigurationToUpdate, - }, - }, - ), + state: { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, + infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { const updatedNetworkConfiguration = - await controller.updateNetwork('0x1337', { + await controller.updateNetwork(infuraChainId, { ...networkConfigurationToUpdate, defaultRpcEndpointIndex: 0, rpcEndpoints: [ - rpcEndpoint1, + ...networkConfigurationToUpdate.rpcEndpoints, buildUpdateNetworkCustomRpcEndpointFields({ name: 'Endpoint 2', url: 'https://rpc.endpoint/2', @@ -4925,16 +4978,16 @@ describe('NetworkController', () => { expect(updatedNetworkConfiguration).toStrictEqual({ ...networkConfigurationToUpdate, rpcEndpoints: [ - rpcEndpoint1, + ...networkConfigurationToUpdate.rpcEndpoints, { name: 'Endpoint 2', - networkClientId: 'BBBB-BBBB-BBBB-BBBB', + networkClientId: 'AAAA-AAAA-AAAA-AAAA', type: RpcEndpointType.Custom, url: 'https://rpc.endpoint/2', }, { name: 'Endpoint 3', - networkClientId: 'CCCC-CCCC-CCCC-CCCC', + networkClientId: 'BBBB-BBBB-BBBB-BBBB', type: RpcEndpointType.Custom, url: 'https://rpc.endpoint/3', }, @@ -5580,10 +5633,20 @@ describe('NetworkController', () => { await withController( { state: { - selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', networkConfigurationsByChainId: { [infuraChainId]: networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', }, }, async ({ controller }) => { @@ -5641,10 +5704,20 @@ describe('NetworkController', () => { await withController( { state: { - selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', networkConfigurationsByChainId: { [infuraChainId]: networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', }, }, async ({ controller }) => { @@ -5706,10 +5779,20 @@ describe('NetworkController', () => { await withController( { state: { - selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', networkConfigurationsByChainId: { [infuraChainId]: networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', }, }, async ({ controller }) => { @@ -5768,10 +5851,20 @@ describe('NetworkController', () => { await withController( { state: { - selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', networkConfigurationsByChainId: { [infuraChainId]: networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', }, }, async ({ controller }) => { @@ -6039,14 +6132,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId( - { - networkConfigurationsByChainId: { - [infuraChainId]: networkConfigurationToUpdate, - }, - }, - ), + state: { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { mockCreateNetworkClient().mockReturnValue(buildFakeClient()); @@ -6086,14 +6187,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId( - { - networkConfigurationsByChainId: { - [infuraChainId]: networkConfigurationToUpdate, - }, - }, - ), + state: { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { await controller.updateNetwork(infuraChainId, { @@ -6102,12 +6211,10 @@ describe('NetworkController', () => { }); expect( - controller.state.networkConfigurationsByChainId, + controller.state.networkConfigurationsByChainId[infuraChainId], ).toStrictEqual({ - [infuraChainId]: { - ...networkConfigurationToUpdate, - rpcEndpoints: [rpcEndpoint3, rpcEndpoint1, rpcEndpoint2], - }, + ...networkConfigurationToUpdate, + rpcEndpoints: [rpcEndpoint3, rpcEndpoint1, rpcEndpoint2], }); }, ); @@ -6134,19 +6241,27 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId( - { - networkConfigurationsByChainId: { - [infuraChainId]: networkConfigurationToUpdate, - }, - }, - ), - }, - async ({ controller }) => { - const updatedNetworkConfiguration = - await controller.updateNetwork(infuraChainId, { - ...networkConfigurationToUpdate, + state: { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, + }, + async ({ controller }) => { + const updatedNetworkConfiguration = + await controller.updateNetwork(infuraChainId, { + ...networkConfigurationToUpdate, rpcEndpoints: [rpcEndpoint3, rpcEndpoint1, rpcEndpoint2], }); @@ -6180,14 +6295,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId( - { - networkConfigurationsByChainId: { - [infuraChainId]: networkConfigurationToUpdate, - }, - }, - ), + state: { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { mockCreateNetworkClient().mockReturnValue(buildFakeClient()); @@ -6229,14 +6352,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId( - { - networkConfigurationsByChainId: { - [infuraChainId]: networkConfigurationToUpdate, - }, - }, - ), + state: { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { const previousNetworkConfigurationsByChainId = @@ -6277,14 +6408,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId( - { - networkConfigurationsByChainId: { - [infuraChainId]: networkConfigurationToUpdate, - }, - }, - ), + state: { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { const updatedNetworkConfiguration = @@ -6314,14 +6453,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId( - { - networkConfigurationsByChainId: { - [infuraChainId]: networkConfigurationToUpdate, - }, - }, - ), + state: { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { mockCreateNetworkClient().mockReturnValue(buildFakeClient()); @@ -6354,15 +6501,15 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ - networkConfigurationsByChainId: { - '0x1337': networkConfigurationToUpdate, - [ChainId.mainnet]: buildInfuraNetworkConfiguration( - InfuraNetworkType.mainnet, - ), - }, - }), + state: { + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + [ChainId.mainnet]: buildInfuraNetworkConfiguration( + InfuraNetworkType.mainnet, + ), + }, + selectedNetworkClientId: InfuraNetworkType.mainnet, + }, }, async ({ controller }) => { await expect( @@ -6402,12 +6549,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ - networkConfigurationsByChainId: { - '0x1337': networkConfigurationToUpdate, - }, - }), + state: { + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { await controller.updateNetwork('0x1337', { @@ -6483,12 +6640,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ - networkConfigurationsByChainId: { - '0x1337': networkConfigurationToUpdate, - }, - }), + state: { + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { await controller.updateNetwork('0x1337', { @@ -6547,12 +6714,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ - networkConfigurationsByChainId: { - '0x1337': networkConfigurationToUpdate, - }, - }), + state: { + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { const updatedNetworkConfiguration = @@ -7226,10 +7403,20 @@ describe('NetworkController', () => { await withController( { state: { - selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', networkConfigurationsByChainId: { '0x1337': networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', }, }, async ({ controller }) => { @@ -7287,10 +7474,20 @@ describe('NetworkController', () => { await withController( { state: { - selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', networkConfigurationsByChainId: { '0x1337': networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', }, }, async ({ controller }) => { @@ -7352,10 +7549,20 @@ describe('NetworkController', () => { await withController( { state: { - selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', networkConfigurationsByChainId: { '0x1337': networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', }, }, async ({ controller }) => { @@ -7412,10 +7619,20 @@ describe('NetworkController', () => { await withController( { state: { - selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', networkConfigurationsByChainId: { '0x1337': networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', }, }, async ({ controller }) => { @@ -7687,12 +7904,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ - networkConfigurationsByChainId: { - '0x1337': networkConfigurationToUpdate, - }, - }), + state: { + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { mockCreateNetworkClient().mockReturnValue(buildFakeClient()); @@ -7736,12 +7963,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ - networkConfigurationsByChainId: { - '0x1337': networkConfigurationToUpdate, - }, - }), + state: { + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { await controller.updateNetwork('0x1337', { @@ -7750,12 +7987,10 @@ describe('NetworkController', () => { }); expect( - controller.state.networkConfigurationsByChainId, + controller.state.networkConfigurationsByChainId['0x1337'], ).toStrictEqual({ - '0x1337': { - ...networkConfigurationToUpdate, - rpcEndpoints: [rpcEndpoint3, rpcEndpoint1, rpcEndpoint2], - }, + ...networkConfigurationToUpdate, + rpcEndpoints: [rpcEndpoint3, rpcEndpoint1, rpcEndpoint2], }); }, ); @@ -7786,12 +8021,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ - networkConfigurationsByChainId: { - '0x1337': networkConfigurationToUpdate, - }, - }), + state: { + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { const updatedNetworkConfiguration = @@ -7830,12 +8075,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ - networkConfigurationsByChainId: { - '0x1337': networkConfigurationToUpdate, - }, - }), + state: { + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { mockCreateNetworkClient().mockReturnValue(buildFakeClient()); @@ -7877,17 +8132,27 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ - networkConfigurationsByChainId: { - '0x1337': networkConfigurationToUpdate, - }, - }), - }, - async ({ controller }) => { - const previousNetworkConfigurationsByChainId = - controller.state.networkConfigurationsByChainId; - + state: { + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, + }, + async ({ controller }) => { + const previousNetworkConfigurationsByChainId = + controller.state.networkConfigurationsByChainId; + await controller.updateNetwork('0x1337', { ...networkConfigurationToUpdate, rpcEndpoints: [ @@ -7923,12 +8188,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ - networkConfigurationsByChainId: { - '0x1337': networkConfigurationToUpdate, - }, - }), + state: { + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { const updatedNetworkConfiguration = @@ -7962,12 +8237,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ - networkConfigurationsByChainId: { - '0x1337': networkConfigurationToUpdate, - }, - }), + state: { + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { mockCreateNetworkClient().mockReturnValue(buildFakeClient()); @@ -8014,16 +8299,23 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId( - { - networkConfigurationsByChainId: { - '0x1337': networkConfigurationToUpdate, - [infuraChainId]: - buildInfuraNetworkConfiguration(infuraNetworkType), - }, - }, - ), + state: { + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + [infuraChainId]: buildInfuraNetworkConfiguration(infuraNetworkType), + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { await expect( @@ -8046,17 +8338,25 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId( - { - networkConfigurationsByChainId: { - '0x1337': networkConfigurationToUpdate, - [anotherInfuraChainId]: buildInfuraNetworkConfiguration( - anotherInfuraNetworkType, - ), - }, - }, - ), + state: { + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + [anotherInfuraChainId]: buildInfuraNetworkConfiguration( + anotherInfuraNetworkType, + ), + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { await expect( @@ -8099,16 +8399,25 @@ describe('NetworkController', () => { rpcEndpoints: [rpcEndpoint1, rpcEndpoint2], }); + // TODO: This is where we stopped await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId( - { - networkConfigurationsByChainId: { - '0x1337': networkConfigurationToUpdate, - }, - }, - ), + state: { + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { mockCreateNetworkClient() @@ -8172,14 +8481,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId( - { - networkConfigurationsByChainId: { - '0x1337': networkConfigurationToUpdate, - }, - }, - ), + state: { + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { mockCreateNetworkClient() @@ -8251,14 +8568,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId( - { - networkConfigurationsByChainId: { - '0x1337': networkConfigurationToUpdate, - }, - }, - ), + state: { + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { mockCreateNetworkClient() @@ -8332,14 +8657,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId( - { - networkConfigurationsByChainId: { - '0x1337': networkConfigurationToUpdate, - }, - }, - ), + state: { + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { mockCreateNetworkClient() @@ -8585,18 +8918,26 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId( - { - networkConfigurationsByChainId: { - [infuraChainId]: networkConfigurationToUpdate, - '0x1337': buildNetworkConfiguration({ - name: 'Some Network', - chainId: '0x1337', + state: { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + name: 'Some Network', + }), + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', }), - }, - }, - ), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { await expect( @@ -8619,14 +8960,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId( - { - networkConfigurationsByChainId: { - [infuraChainId]: networkConfigurationToUpdate, - }, - }, - ), + state: { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { await expect( @@ -8673,14 +9022,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId( - { - networkConfigurationsByChainId: { - [infuraChainId]: networkConfigurationToUpdate, - }, - }, - ), + state: { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { mockCreateNetworkClient() @@ -8756,14 +9113,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId( - { - networkConfigurationsByChainId: { - [infuraChainId]: networkConfigurationToUpdate, - }, - }, - ), + state: { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { mockCreateNetworkClient() @@ -8849,14 +9214,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId( - { - networkConfigurationsByChainId: { - [infuraChainId]: networkConfigurationToUpdate, - }, - }, - ), + state: { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { mockCreateNetworkClient() @@ -8943,14 +9316,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId( - { - networkConfigurationsByChainId: { - [infuraChainId]: networkConfigurationToUpdate, - }, - }, - ), + state: { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { mockCreateNetworkClient() @@ -9207,17 +9588,25 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId( - { - networkConfigurationsByChainId: { - [infuraChainId]: networkConfigurationToUpdate, - [anotherInfuraChainId]: buildInfuraNetworkConfiguration( - anotherInfuraNetworkType, - ), - }, - }, - ), + state: { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + [anotherInfuraChainId]: buildInfuraNetworkConfiguration( + anotherInfuraNetworkType, + ), + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { await expect( @@ -9240,14 +9629,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId( - { - networkConfigurationsByChainId: { - [infuraChainId]: networkConfigurationToUpdate, - }, - }, - ), + state: { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { await expect( @@ -9294,14 +9691,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId( - { - networkConfigurationsByChainId: { - [infuraChainId]: networkConfigurationToUpdate, - }, - }, - ), + state: { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { @@ -9388,14 +9793,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId( - { - networkConfigurationsByChainId: { - [infuraChainId]: networkConfigurationToUpdate, - }, - }, - ), + state: { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { @@ -9483,14 +9896,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId( - { - networkConfigurationsByChainId: { - [infuraChainId]: networkConfigurationToUpdate, - }, - }, - ), + state: { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { @@ -9533,14 +9954,7 @@ describe('NetworkController', () => { getNetworkConfigurationsByNetworkClientId( controller.getNetworkClientRegistry(), ), - ).toStrictEqual({ - [anotherInfuraNetworkType]: { - chainId: anotherInfuraChainId, - infuraProjectId: 'some-infura-project-id', - network: anotherInfuraNetworkType, - ticker: anotherInfuraNativeTokenName, - type: NetworkClientType.Infura, - }, + ).toMatchObject({ 'CCCC-CCCC-CCCC-CCCC': { chainId: anotherInfuraChainId, rpcUrl: 'https://test.endpoint/1', @@ -9586,14 +10000,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId( - { - networkConfigurationsByChainId: { - [infuraChainId]: networkConfigurationToUpdate, - }, - }, - ), + state: { + networkConfigurationsByChainId: { + [infuraChainId]: networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { @@ -9858,13 +10280,26 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ - networkConfigurationsByChainId: { - '0x1337': networkConfigurationToUpdate, - '0x2448': buildNetworkConfiguration({ chainId: '0x2448' }), - }, - }), + state: { + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + '0x2448': buildNetworkConfiguration({ + name: 'Some Network', + chainId: '0x2448' + }), + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { await expect(() => @@ -9886,15 +10321,25 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ - networkConfigurationsByChainId: { - '0x1337': networkConfigurationToUpdate, - [ChainId.goerli]: buildInfuraNetworkConfiguration( - InfuraNetworkType.goerli, - ), - }, - }), + state: { + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + [ChainId.goerli]: buildInfuraNetworkConfiguration( + InfuraNetworkType.goerli, + ), + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { const newRpcEndpoint = buildInfuraRpcEndpoint( @@ -9937,12 +10382,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ - networkConfigurationsByChainId: { - '0x1337': networkConfigurationToUpdate, - }, - }), + state: { + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { const fakeProviders = [ @@ -10017,12 +10472,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ - networkConfigurationsByChainId: { - '0x1337': networkConfigurationToUpdate, - }, - }), + state: { + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { const fakeProviders = [ @@ -10102,12 +10567,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ - networkConfigurationsByChainId: { - '0x1337': networkConfigurationToUpdate, - }, - }), + state: { + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { const fakeProviders = [ @@ -10194,12 +10669,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ - networkConfigurationsByChainId: { - '0x1337': networkConfigurationToUpdate, - }, - }), + state: { + networkConfigurationsByChainId: { + '0x1337': networkConfigurationToUpdate, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { const fakeProviders = [ @@ -10459,14 +10944,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId( - { - networkConfigurationsByChainId: { - [infuraChainId]: existingNetworkConfiguration, - }, - }, - ), + state: { + networkConfigurationsByChainId: { + [infuraChainId]: existingNetworkConfiguration, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { await controller.updateNetwork( @@ -10489,14 +10982,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId( - { - networkConfigurationsByChainId: { - [infuraChainId]: existingNetworkConfiguration, - }, - }, - ), + state: { + networkConfigurationsByChainId: { + [infuraChainId]: existingNetworkConfiguration, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { const existingNetworkClient = @@ -10524,14 +11025,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId( - { - networkConfigurationsByChainId: { - [infuraChainId]: existingNetworkConfiguration, - }, - }, - ), + state: { + networkConfigurationsByChainId: { + [infuraChainId]: existingNetworkConfiguration, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { await controller.updateNetwork( @@ -10539,9 +11048,9 @@ describe('NetworkController', () => { existingNetworkConfiguration, ); - // Once when the controller is initialized, but no more + // 2 times for existing RPC endpoints, but no more expect(createAutoManagedNetworkClientSpy).toHaveBeenCalledTimes( - 1, + 2, ); }, ); @@ -10555,18 +11064,24 @@ describe('NetworkController', () => { chainId: '0x1337', }); - const state = buildNetworkControllerStateWithSelectedChain('0x2448', { - networkConfigurationsByChainId: { - '0x1337': existingNetworkConfiguration, - '0x2448': buildCustomNetworkConfiguration({ - chainId: '0x2448', - }), - }, - }); - await withController( { - state, + state: { + networkConfigurationsByChainId: { + '0x1337': existingNetworkConfiguration, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { await controller.updateNetwork( @@ -10593,12 +11108,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ - networkConfigurationsByChainId: { - '0x1337': existingNetworkConfiguration, - }, - }), + state: { + networkConfigurationsByChainId: { + '0x1337': existingNetworkConfiguration, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { const existingNetworkClient = controller.getNetworkClientById( @@ -10628,12 +11153,22 @@ describe('NetworkController', () => { await withController( { - state: - buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ - networkConfigurationsByChainId: { - '0x1337': existingNetworkConfiguration, - }, - }), + state: { + networkConfigurationsByChainId: { + '0x1337': existingNetworkConfiguration, + '0x9999': buildCustomNetworkConfiguration({ + chainId: '0x9999', + nativeCurrency: 'TEST-9999', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + url: 'https://selected.endpoint', + }), + ], + }), + }, + selectedNetworkClientId: 'ZZZZ-ZZZZ-ZZZZ-ZZZZ', + }, }, async ({ controller }) => { await controller.updateNetwork( @@ -10641,9 +11176,9 @@ describe('NetworkController', () => { existingNetworkConfiguration, ); - // Once when the controller is initialized, but no more + // 2 times for existing RPC endpoints, but no more expect(createAutoManagedNetworkClientSpy).toHaveBeenCalledTimes( - 1, + 2, ); }, ); @@ -13728,52 +14263,3 @@ function buildNetworkControllerStateWithDefaultSelectedNetworkClientId({ ...rest, }; } - -/** - * When initializing NetworkController with state, the `selectedNetworkClientId` - * property must match the `networkClientId` of an RPC endpoint in - * `networkConfigurationsByChainId`. Sometimes when writing tests we care about - * what the `selectedNetworkClientId` is, but sometimes we don't and we'd rather - * have this property automatically filled in for us. - * - * This function takes care of filling in the `selectedNetworkClientId` using - * the first RPC endpoint of the network configuration with the given chain ID. - * - * @param chainId - The chain ID to use. - * @param networkControllerState - The desired NetworkController state - * overrides. - * @param networkControllerState.networkConfigurationsByChainId - The desired - * `networkConfigurationsByChainId`. - * @param networkControllerState.selectedNetworkClientId - The desired - * `selectedNetworkClientId`; if not provided, then will be set to the - * `networkClientId` of the first RPC endpoint of the network configuration with - * the given chain ID. - * @returns The complete NetworkController state with `selectedNetworkClientId` - * properly filled in. - */ -function buildNetworkControllerStateWithSelectedChain( - chainId: Hex, - { - networkConfigurationsByChainId, - selectedNetworkClientId: givenSelectedNetworkClientId, - ...rest - }: Partial> & - Pick, -) { - if (givenSelectedNetworkClientId === undefined) { - const networkConfiguration = networkConfigurationsByChainId[chainId]; - const selectedNetworkClientId = - networkConfiguration.rpcEndpoints[0].networkClientId; - return { - networkConfigurationsByChainId, - selectedNetworkClientId, - ...rest, - }; - } - - return { - networkConfigurationsByChainId, - selectedNetworkClientId: givenSelectedNetworkClientId, - ...rest, - }; -} From 607ccd3246b89e3f28f584b59c5a7876609978d7 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Fri, 19 Jul 2024 11:38:19 -0600 Subject: [PATCH 33/49] No need to sort chain IDs when creating network client registry --- .../network-controller/src/NetworkController.ts | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index c44daf9fcdf..c0efbaa8b59 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -6,7 +6,6 @@ import type { import { BaseController } from '@metamask/base-controller'; import type { Partialize } from '@metamask/controller-utils'; import { - toHex, InfuraNetworkType, NetworkType, isSafeChainId, @@ -20,12 +19,7 @@ import { errorCodes } from '@metamask/rpc-errors'; import { createEventEmitterProxy } from '@metamask/swappable-obj-proxy'; import type { SwappableProxy } from '@metamask/swappable-obj-proxy'; import type { Hex } from '@metamask/utils'; -import { - hexToBigInt, - isStrictHexString, - hasProperty, - isPlainObject, -} from '@metamask/utils'; +import { isStrictHexString, hasProperty, isPlainObject } from '@metamask/utils'; import { strict as assert } from 'assert'; import type { Draft } from 'immer'; import type { Logger } from 'loglevel'; @@ -2448,13 +2442,8 @@ export class NetworkController extends BaseController< * @returns The network clients keyed by ID. */ #createAutoManagedNetworkClientRegistry(): AutoManagedNetworkClientRegistry { - const sortedChainIds = Object.keys( - this.state.networkConfigurationsByChainId, - ) - .map(hexToBigInt) - .sort() - .map(toHex); - const networkClientsWithIds = sortedChainIds.flatMap((chainId) => { + const chainIds = knownKeysOf(this.state.networkConfigurationsByChainId); + const networkClientsWithIds = chainIds.flatMap((chainId) => { const networkConfiguration = this.state.networkConfigurationsByChainId[chainId]; return networkConfiguration.rpcEndpoints.map((rpcEndpoint) => { From 32634ea2000f53375e71ea7159a3e91038cb4ed7 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Fri, 19 Jul 2024 11:42:33 -0600 Subject: [PATCH 34/49] Fix lint violations --- .../network-controller/tests/NetworkController.test.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/network-controller/tests/NetworkController.test.ts b/packages/network-controller/tests/NetworkController.test.ts index 4372e6b714e..e1338640222 100644 --- a/packages/network-controller/tests/NetworkController.test.ts +++ b/packages/network-controller/tests/NetworkController.test.ts @@ -6211,7 +6211,9 @@ describe('NetworkController', () => { }); expect( - controller.state.networkConfigurationsByChainId[infuraChainId], + controller.state.networkConfigurationsByChainId[ + infuraChainId + ], ).toStrictEqual({ ...networkConfigurationToUpdate, rpcEndpoints: [rpcEndpoint3, rpcEndpoint1, rpcEndpoint2], @@ -8302,7 +8304,8 @@ describe('NetworkController', () => { state: { networkConfigurationsByChainId: { '0x1337': networkConfigurationToUpdate, - [infuraChainId]: buildInfuraNetworkConfiguration(infuraNetworkType), + [infuraChainId]: + buildInfuraNetworkConfiguration(infuraNetworkType), '0x9999': buildCustomNetworkConfiguration({ chainId: '0x9999', nativeCurrency: 'TEST-9999', @@ -10285,7 +10288,7 @@ describe('NetworkController', () => { '0x1337': networkConfigurationToUpdate, '0x2448': buildNetworkConfiguration({ name: 'Some Network', - chainId: '0x2448' + chainId: '0x2448', }), '0x9999': buildCustomNetworkConfiguration({ chainId: '0x9999', From f4de92d0d989a8375e97b829d99e4f25ab1fdfe2 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Fri, 19 Jul 2024 11:47:55 -0600 Subject: [PATCH 35/49] Fix TokenDetectionController test --- .../assets-controllers/src/TokenDetectionController.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/assets-controllers/src/TokenDetectionController.test.ts b/packages/assets-controllers/src/TokenDetectionController.test.ts index e21599d1aa1..77d515f0654 100644 --- a/packages/assets-controllers/src/TokenDetectionController.test.ts +++ b/packages/assets-controllers/src/TokenDetectionController.test.ts @@ -121,9 +121,10 @@ const mockNetworkConfigurations: Record = { InfuraNetworkType.goerli, ), polygon: { - blockExplorerUrl: 'https://polygonscan.com/', + blockExplorerUrls: ['https://polygonscan.com/'], chainId: '0x89', - defaultRpcEndpointUrl: 'https://polygon-mainnet.infura.io/v3/fakekey', + defaultBlockExplorerUrlIndex: 0, + defaultRpcEndpointIndex: 0, name: 'Polygon Mainnet', nativeCurrency: 'MATIC', rpcEndpoints: [ From 4f103f503d3b6f03472f4982ec4e637453720154 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Mon, 22 Jul 2024 08:32:35 -0600 Subject: [PATCH 36/49] Export network fields types Co-authored-by: Brian Bergeron --- packages/network-controller/src/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/network-controller/src/index.ts b/packages/network-controller/src/index.ts index 6ad80870f1f..7f1a6daa436 100644 --- a/packages/network-controller/src/index.ts +++ b/packages/network-controller/src/index.ts @@ -9,6 +9,8 @@ export type { NetworkState, BlockTrackerProxy, ProviderProxy, + AddNetworkFields, + UpdateNetworkFields, NetworkControllerStateChangeEvent, NetworkControllerNetworkWillChangeEvent, NetworkControllerNetworkDidChangeEvent, From 7d36993ff50436c7dccc7c54bbfb00d21ebccb01 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Mon, 22 Jul 2024 09:37:48 -0600 Subject: [PATCH 37/49] Fix tests --- .../src/TokenDetectionController.test.ts | 8 ++++---- packages/network-controller/src/NetworkController.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/assets-controllers/src/TokenDetectionController.test.ts b/packages/assets-controllers/src/TokenDetectionController.test.ts index e8c7dd1476f..21ae2029dd0 100644 --- a/packages/assets-controllers/src/TokenDetectionController.test.ts +++ b/packages/assets-controllers/src/TokenDetectionController.test.ts @@ -17,10 +17,6 @@ import type { AutoManagedNetworkClient, CustomNetworkClientConfiguration, } from '@metamask/network-controller'; -import { - buildCustomRpcEndpoint, - buildInfuraNetworkConfiguration, -} from '@metamask/network-controller/tests/helpers'; import { getDefaultPreferencesState, type PreferencesState, @@ -32,6 +28,10 @@ import * as sinon from 'sinon'; import { advanceTime } from '../../../tests/helpers'; import { createMockInternalAccount } from '../../accounts-controller/src/tests/mocks'; +import { + buildCustomRpcEndpoint, + buildInfuraNetworkConfiguration, +} from '../../network-controller/tests/helpers'; import { formatAggregatorNames } from './assetsUtil'; import { TOKEN_END_POINT_API } from './token-service'; import type { diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index c0efbaa8b59..968e4fdc104 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -249,7 +249,7 @@ export type UpdateNetworkCustomRpcEndpointFields = Partialize< * they do, then it is assumed that they already exist, and if not, then it is * assumed that they are new and are not represented by network clients yet. */ -type UpdateNetworkFields = Omit & { +export type UpdateNetworkFields = Omit & { rpcEndpoints: (InfuraRpcEndpoint | UpdateNetworkCustomRpcEndpointFields)[]; }; From 8c8a94486130b2fa05cea7e3b8e8e90764f38e81 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Mon, 22 Jul 2024 10:46:33 -0600 Subject: [PATCH 38/49] Fix yarn.lock --- yarn.lock | 2006 +++++++++++++++++++++++------------------------------ 1 file changed, 862 insertions(+), 1144 deletions(-) diff --git a/yarn.lock b/yarn.lock index cff891554f3..e7c7cd6d334 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,13 +5,6 @@ __metadata: version: 8 cacheKey: 10 -"@aashutoshrathi/word-wrap@npm:^1.2.3": - version: 1.2.6 - resolution: "@aashutoshrathi/word-wrap@npm:1.2.6" - checksum: 10/6eebd12a5cd03cee38fcb915ef9f4ea557df6a06f642dfc7fe8eb4839eb5c9ca55a382f3604d52c14200b0c214c12af5e1f23d2a6d8e23ef2d016b105a9d6c0a - languageName: node - linkType: hard - "@adraffy/ens-normalize@npm:1.10.1": version: 1.10.1 resolution: "@adraffy/ens-normalize@npm:1.10.1" @@ -48,59 +41,37 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.23.5, @babel/code-frame@npm:^7.24.1, @babel/code-frame@npm:^7.24.2": - version: 7.24.2 - resolution: "@babel/code-frame@npm:7.24.2" - dependencies: - "@babel/highlight": "npm:^7.24.2" - picocolors: "npm:^1.0.0" - checksum: 10/7db8f5b36ffa3f47a37f58f61e3d130b9ecad21961f3eede7e2a4ac2c7e4a5efb6e9d03a810c669bc986096831b6c0dfc2c3082673d93351b82359c1b03e0590 - languageName: node - linkType: hard - -"@babel/compat-data@npm:^7.23.5": - version: 7.24.4 - resolution: "@babel/compat-data@npm:7.24.4" - checksum: 10/e51faec0ac8259f03cc5029d2b4a944b4fee44cb5188c11530769d5beb81f384d031dba951febc3e33dbb48ceb8045b1184f5c1ac4c5f86ab1f5e951e9aaf7af +"@babel/compat-data@npm:^7.24.8": + version: 7.24.9 + resolution: "@babel/compat-data@npm:7.24.9" + checksum: 10/fcdbf3dd978305880f06ae20a23f4f68a8eddbe64fc5d2fbc98dfe4cdf15c174cff41e3a8eb9d935f9f3a68d3a23fa432044082ee9768a2ed4b15f769b8f6853 languageName: node linkType: hard "@babel/core@npm:^7.1.0, @babel/core@npm:^7.12.3, @babel/core@npm:^7.23.2, @babel/core@npm:^7.23.5, @babel/core@npm:^7.7.2, @babel/core@npm:^7.8.0": - version: 7.24.4 - resolution: "@babel/core@npm:7.24.4" + version: 7.24.9 + resolution: "@babel/core@npm:7.24.9" dependencies: "@ampproject/remapping": "npm:^2.2.0" - "@babel/code-frame": "npm:^7.24.2" - "@babel/generator": "npm:^7.24.4" - "@babel/helper-compilation-targets": "npm:^7.23.6" - "@babel/helper-module-transforms": "npm:^7.23.3" - "@babel/helpers": "npm:^7.24.4" - "@babel/parser": "npm:^7.24.4" - "@babel/template": "npm:^7.24.0" - "@babel/traverse": "npm:^7.24.1" - "@babel/types": "npm:^7.24.0" + "@babel/code-frame": "npm:^7.24.7" + "@babel/generator": "npm:^7.24.9" + "@babel/helper-compilation-targets": "npm:^7.24.8" + "@babel/helper-module-transforms": "npm:^7.24.9" + "@babel/helpers": "npm:^7.24.8" + "@babel/parser": "npm:^7.24.8" + "@babel/template": "npm:^7.24.7" + "@babel/traverse": "npm:^7.24.8" + "@babel/types": "npm:^7.24.9" convert-source-map: "npm:^2.0.0" debug: "npm:^4.1.0" gensync: "npm:^1.0.0-beta.2" json5: "npm:^2.2.3" semver: "npm:^6.3.1" - checksum: 10/1e049f8df26be0fe5be36173fd7c33dfb004eeeec28152fea83c90e71784f9a6f2237296f43a2ee7d9041e2a33a05f43da48ce2d4e0cd473a682328ca07ce7e0 + checksum: 10/f00a372fa547f6e21f4db1b6e521e6eb01f77f5931726897aae6f4cf29a687f615b9b77147b539e851a68bf94e4850bcfba7eb11091dd8e2bc625f6d831ce257 languageName: node linkType: hard -"@babel/generator@npm:^7.24.1, @babel/generator@npm:^7.24.4": - version: 7.24.4 - resolution: "@babel/generator@npm:7.24.4" - dependencies: - "@babel/types": "npm:^7.24.0" - "@jridgewell/gen-mapping": "npm:^0.3.5" - "@jridgewell/trace-mapping": "npm:^0.3.25" - jsesc: "npm:^2.5.1" - checksum: 10/69e1772dcf8f95baec951f422cca091d59a3f29b5eedc989ad87f7262289b94625983f6fe654302ca17aae0a32f9232332b83fcc85533311d6267b09c58b1061 - languageName: node - linkType: hard - -"@babel/generator@npm:^7.24.8, @babel/generator@npm:^7.7.2": +"@babel/generator@npm:^7.24.8, @babel/generator@npm:^7.24.9, @babel/generator@npm:^7.7.2": version: 7.24.10 resolution: "@babel/generator@npm:7.24.10" dependencies: @@ -112,51 +83,44 @@ __metadata: languageName: node linkType: hard -"@babel/helper-annotate-as-pure@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/helper-annotate-as-pure@npm:7.22.5" +"@babel/helper-annotate-as-pure@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-annotate-as-pure@npm:7.24.7" dependencies: - "@babel/types": "npm:^7.22.5" - checksum: 10/53da330f1835c46f26b7bf4da31f7a496dee9fd8696cca12366b94ba19d97421ce519a74a837f687749318f94d1a37f8d1abcbf35e8ed22c32d16373b2f6198d + "@babel/types": "npm:^7.24.7" + checksum: 10/a9017bfc1c4e9f2225b967fbf818004703de7cf29686468b54002ffe8d6b56e0808afa20d636819fcf3a34b89ba72f52c11bdf1d69f303928ee10d92752cad95 languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.23.6": - version: 7.23.6 - resolution: "@babel/helper-compilation-targets@npm:7.23.6" +"@babel/helper-compilation-targets@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/helper-compilation-targets@npm:7.24.8" dependencies: - "@babel/compat-data": "npm:^7.23.5" - "@babel/helper-validator-option": "npm:^7.23.5" - browserslist: "npm:^4.22.2" + "@babel/compat-data": "npm:^7.24.8" + "@babel/helper-validator-option": "npm:^7.24.8" + browserslist: "npm:^4.23.1" lru-cache: "npm:^5.1.1" semver: "npm:^6.3.1" - checksum: 10/05595cd73087ddcd81b82d2f3297aac0c0422858dfdded43d304786cf680ec33e846e2317e6992d2c964ee61d93945cbf1fa8ec80b55aee5bfb159227fb02cb9 + checksum: 10/3489280d07b871af565b32f9b11946ff9a999fac0db9bec5df960760f6836c7a4b52fccb9d64229ccce835d37a43afb85659beb439ecedde04dcea7eb062a143 languageName: node linkType: hard -"@babel/helper-create-class-features-plugin@npm:^7.24.4": - version: 7.24.4 - resolution: "@babel/helper-create-class-features-plugin@npm:7.24.4" +"@babel/helper-create-class-features-plugin@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/helper-create-class-features-plugin@npm:7.24.8" dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.22.5" - "@babel/helper-environment-visitor": "npm:^7.22.20" - "@babel/helper-function-name": "npm:^7.23.0" - "@babel/helper-member-expression-to-functions": "npm:^7.23.0" - "@babel/helper-optimise-call-expression": "npm:^7.22.5" - "@babel/helper-replace-supers": "npm:^7.24.1" - "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.22.5" - "@babel/helper-split-export-declaration": "npm:^7.22.6" + "@babel/helper-annotate-as-pure": "npm:^7.24.7" + "@babel/helper-environment-visitor": "npm:^7.24.7" + "@babel/helper-function-name": "npm:^7.24.7" + "@babel/helper-member-expression-to-functions": "npm:^7.24.8" + "@babel/helper-optimise-call-expression": "npm:^7.24.7" + "@babel/helper-replace-supers": "npm:^7.24.7" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.24.7" + "@babel/helper-split-export-declaration": "npm:^7.24.7" semver: "npm:^6.3.1" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/86153719d98e4402f92f24d6b1be94e6b59c0236a6cc36b173a570a64b5156dbc2f16ccfe3c8485dc795524ca88acca65b14863be63049586668c45567f2acd4 - languageName: node - linkType: hard - -"@babel/helper-environment-visitor@npm:^7.22.20": - version: 7.22.20 - resolution: "@babel/helper-environment-visitor@npm:7.22.20" - checksum: 10/d80ee98ff66f41e233f36ca1921774c37e88a803b2f7dca3db7c057a5fea0473804db9fb6729e5dbfd07f4bed722d60f7852035c2c739382e84c335661590b69 + checksum: 10/a779c5356fcc4881e807d85d973fd37e99e773fe95837b0f6582ca9a89331f84e5f26b0b6aa9a101181325b73cf3f54081d178b657a79819b8abadc53b0ea8ec languageName: node linkType: hard @@ -169,16 +133,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-function-name@npm:^7.23.0": - version: 7.23.0 - resolution: "@babel/helper-function-name@npm:7.23.0" - dependencies: - "@babel/template": "npm:^7.22.15" - "@babel/types": "npm:^7.23.0" - checksum: 10/7b2ae024cd7a09f19817daf99e0153b3bf2bc4ab344e197e8d13623d5e36117ed0b110914bc248faa64e8ccd3e97971ec7b41cc6fd6163a2b980220c58dcdf6d - languageName: node - linkType: hard - "@babel/helper-function-name@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-function-name@npm:7.24.7" @@ -189,15 +143,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-hoist-variables@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/helper-hoist-variables@npm:7.22.5" - dependencies: - "@babel/types": "npm:^7.22.5" - checksum: 10/394ca191b4ac908a76e7c50ab52102669efe3a1c277033e49467913c7ed6f7c64d7eacbeabf3bed39ea1f41731e22993f763b1edce0f74ff8563fd1f380d92cc - languageName: node - linkType: hard - "@babel/helper-hoist-variables@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-hoist-variables@npm:7.24.7" @@ -207,21 +152,13 @@ __metadata: languageName: node linkType: hard -"@babel/helper-member-expression-to-functions@npm:^7.23.0": - version: 7.23.0 - resolution: "@babel/helper-member-expression-to-functions@npm:7.23.0" - dependencies: - "@babel/types": "npm:^7.23.0" - checksum: 10/325feb6e200478c8cd6e10433fabe993a7d3315cc1a2a457e45514a5f95a73dff4c69bea04cc2daea0ffe72d8ed85d504b3f00b2e0767b7d4f5ae25fec9b35b2 - languageName: node - linkType: hard - -"@babel/helper-module-imports@npm:^7.22.15": - version: 7.24.3 - resolution: "@babel/helper-module-imports@npm:7.24.3" +"@babel/helper-member-expression-to-functions@npm:^7.24.7, @babel/helper-member-expression-to-functions@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/helper-member-expression-to-functions@npm:7.24.8" dependencies: - "@babel/types": "npm:^7.24.0" - checksum: 10/42fe124130b78eeb4bb6af8c094aa749712be0f4606f46716ce74bc18a5ea91c918c547c8bb2307a2e4b33f163e4ad2cb6a7b45f80448e624eae45b597ea3499 + "@babel/traverse": "npm:^7.24.8" + "@babel/types": "npm:^7.24.8" + checksum: 10/ac878761cfd0a46c081cda0da75cc186f922cf16e8ecdd0c4fb6dca4330d9fe4871b41a9976224cf9669c9e7fe0421b5c27349f2e99c125fa0be871b327fa770 languageName: node linkType: hard @@ -235,22 +172,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-module-transforms@npm:^7.23.3": - version: 7.23.3 - resolution: "@babel/helper-module-transforms@npm:7.23.3" - dependencies: - "@babel/helper-environment-visitor": "npm:^7.22.20" - "@babel/helper-module-imports": "npm:^7.22.15" - "@babel/helper-simple-access": "npm:^7.22.5" - "@babel/helper-split-export-declaration": "npm:^7.22.6" - "@babel/helper-validator-identifier": "npm:^7.22.20" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10/583fa580f8e50e6f45c4f46aa76a8e49c2528deb84e25f634d66461b9a0e2420e13979b0a607b67aef67eaf8db8668eb9edc038b4514b16e3879fe09e8fd294b - languageName: node - linkType: hard - -"@babel/helper-module-transforms@npm:^7.24.8": +"@babel/helper-module-transforms@npm:^7.24.8, @babel/helper-module-transforms@npm:^7.24.9": version: 7.24.9 resolution: "@babel/helper-module-transforms@npm:7.24.9" dependencies: @@ -265,12 +187,12 @@ __metadata: languageName: node linkType: hard -"@babel/helper-optimise-call-expression@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/helper-optimise-call-expression@npm:7.22.5" +"@babel/helper-optimise-call-expression@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-optimise-call-expression@npm:7.24.7" dependencies: - "@babel/types": "npm:^7.22.5" - checksum: 10/c70ef6cc6b6ed32eeeec4482127e8be5451d0e5282d5495d5d569d39eb04d7f1d66ec99b327f45d1d5842a9ad8c22d48567e93fc502003a47de78d122e355f7c + "@babel/types": "npm:^7.24.7" + checksum: 10/da7a7f2d1bb1be4cffd5fa820bd605bc075c7dd014e0458f608bb6f34f450fe9412c8cea93e788227ab396e0e02c162d7b1db3fbcb755a6360e354c485d61df0 languageName: node linkType: hard @@ -281,32 +203,16 @@ __metadata: languageName: node linkType: hard -"@babel/helper-plugin-utils@npm:^7.24.0": - version: 7.24.0 - resolution: "@babel/helper-plugin-utils@npm:7.24.0" - checksum: 10/dc8c7af321baf7653d93315beffee1790eb2c464b4f529273a24c8743a3f3095bf3f2d11828cb2c52d56282ef43a4bdc67a79c9ab8dd845e35d01871f3f28a0e - languageName: node - linkType: hard - -"@babel/helper-replace-supers@npm:^7.24.1": - version: 7.24.1 - resolution: "@babel/helper-replace-supers@npm:7.24.1" +"@babel/helper-replace-supers@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-replace-supers@npm:7.24.7" dependencies: - "@babel/helper-environment-visitor": "npm:^7.22.20" - "@babel/helper-member-expression-to-functions": "npm:^7.23.0" - "@babel/helper-optimise-call-expression": "npm:^7.22.5" + "@babel/helper-environment-visitor": "npm:^7.24.7" + "@babel/helper-member-expression-to-functions": "npm:^7.24.7" + "@babel/helper-optimise-call-expression": "npm:^7.24.7" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/1103b28ce0cc7fba903c21bc78035c696ff191bdbbe83c20c37030a2e10ae6254924556d942cdf8c44c48ba606a8266fdb105e6bb10945de9285f79cb1905df1 - languageName: node - linkType: hard - -"@babel/helper-simple-access@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/helper-simple-access@npm:7.22.5" - dependencies: - "@babel/types": "npm:^7.22.5" - checksum: 10/7d5430eecf880937c27d1aed14245003bd1c7383ae07d652b3932f450f60bfcf8f2c1270c593ab063add185108d26198c69d1aca0e6fb7c6fdada4bcf72ab5b7 + checksum: 10/18b7c3709819d008a14953e885748f3e197537f131d8f7ae095fec245506d854ff40b236edb1754afb6467f795aa90ae42a1d961a89557702249bacfc3fdad19 languageName: node linkType: hard @@ -320,21 +226,13 @@ __metadata: languageName: node linkType: hard -"@babel/helper-skip-transparent-expression-wrappers@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.22.5" - dependencies: - "@babel/types": "npm:^7.22.5" - checksum: 10/1012ef2295eb12dc073f2b9edf3425661e9b8432a3387e62a8bc27c42963f1f216ab3124228015c748770b2257b4f1fda882ca8fa34c0bf485e929ae5bc45244 - languageName: node - linkType: hard - -"@babel/helper-split-export-declaration@npm:^7.22.6": - version: 7.22.6 - resolution: "@babel/helper-split-export-declaration@npm:7.22.6" +"@babel/helper-skip-transparent-expression-wrappers@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.24.7" dependencies: - "@babel/types": "npm:^7.22.5" - checksum: 10/e141cace583b19d9195f9c2b8e17a3ae913b7ee9b8120246d0f9ca349ca6f03cb2c001fd5ec57488c544347c0bb584afec66c936511e447fd20a360e591ac921 + "@babel/traverse": "npm:^7.24.7" + "@babel/types": "npm:^7.24.7" + checksum: 10/784a6fdd251a9a7e42ccd04aca087ecdab83eddc60fda76a2950e00eb239cc937d3c914266f0cc476298b52ac3f44ffd04c358e808bd17552a7e008d75494a77 languageName: node linkType: hard @@ -347,13 +245,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-string-parser@npm:^7.23.4": - version: 7.24.1 - resolution: "@babel/helper-string-parser@npm:7.24.1" - checksum: 10/04c0ede77b908b43e6124753b48bc485528112a9335f0a21a226bff1ace75bb6e64fab24c85cb4b1610ef3494dacd1cb807caeb6b79a7b36c43d48c289b35949 - languageName: node - linkType: hard - "@babel/helper-string-parser@npm:^7.24.8": version: 7.24.8 resolution: "@babel/helper-string-parser@npm:7.24.8" @@ -361,13 +252,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-validator-identifier@npm:^7.22.20": - version: 7.22.20 - resolution: "@babel/helper-validator-identifier@npm:7.22.20" - checksum: 10/df882d2675101df2d507b95b195ca2f86a3ef28cb711c84f37e79ca23178e13b9f0d8b522774211f51e40168bf5142be4c1c9776a150cddb61a0d5bf3e95750b - languageName: node - linkType: hard - "@babel/helper-validator-identifier@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-validator-identifier@npm:7.24.7" @@ -375,21 +259,20 @@ __metadata: languageName: node linkType: hard -"@babel/helper-validator-option@npm:^7.23.5": - version: 7.23.5 - resolution: "@babel/helper-validator-option@npm:7.23.5" - checksum: 10/537cde2330a8aede223552510e8a13e9c1c8798afee3757995a7d4acae564124fe2bf7e7c3d90d62d3657434a74340a274b3b3b1c6f17e9a2be1f48af29cb09e +"@babel/helper-validator-option@npm:^7.24.7, @babel/helper-validator-option@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/helper-validator-option@npm:7.24.8" + checksum: 10/a52442dfa74be6719c0608fee3225bd0493c4057459f3014681ea1a4643cd38b68ff477fe867c4b356da7330d085f247f0724d300582fa4ab9a02efaf34d107c languageName: node linkType: hard -"@babel/helpers@npm:^7.24.4": - version: 7.24.4 - resolution: "@babel/helpers@npm:7.24.4" +"@babel/helpers@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/helpers@npm:7.24.8" dependencies: - "@babel/template": "npm:^7.24.0" - "@babel/traverse": "npm:^7.24.1" - "@babel/types": "npm:^7.24.0" - checksum: 10/54a9d0f86f2803fcc216cfa23b66b871ea0fa0a892af1c9a79075872c2437de71afbb150ed8216f30e00b19a0b9c5c9d5845173d170e1ebfbbf8887839b89dde + "@babel/template": "npm:^7.24.7" + "@babel/types": "npm:^7.24.8" + checksum: 10/61c08a2baa87382a87c7110e9b5574c782603e247b7e6267769ee0e8b7b54b70ff05f16466f05bb318622b7ac28e79b449edff565abf5adcb1adb1b0f42fee9c languageName: node linkType: hard @@ -405,18 +288,6 @@ __metadata: languageName: node linkType: hard -"@babel/highlight@npm:^7.24.2": - version: 7.24.2 - resolution: "@babel/highlight@npm:7.24.2" - dependencies: - "@babel/helper-validator-identifier": "npm:^7.22.20" - chalk: "npm:^2.4.2" - js-tokens: "npm:^4.0.0" - picocolors: "npm:^1.0.0" - checksum: 10/4555124235f34403bb28f55b1de58edf598491cc181c75f8afc8fe529903cb598cd52fe3bf2faab9bc1f45c299681ef0e44eea7a848bb85c500c5a4fe13f54f6 - languageName: node - linkType: hard - "@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.0, @babel/parser@npm:^7.24.7, @babel/parser@npm:^7.24.8": version: 7.24.8 resolution: "@babel/parser@npm:7.24.8" @@ -426,15 +297,6 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.23.9, @babel/parser@npm:^7.24.0, @babel/parser@npm:^7.24.1, @babel/parser@npm:^7.24.4": - version: 7.24.4 - resolution: "@babel/parser@npm:7.24.4" - bin: - parser: ./bin/babel-parser.js - checksum: 10/3742cc5068036287e6395269dce5a2735e6349cdc8d4b53297c75f98c580d7e1c8cb43235623999d151f2ef975d677dbc2c2357573a1855caa71c271bf3046c9 - languageName: node - linkType: hard - "@babel/plugin-syntax-async-generators@npm:^7.8.4": version: 7.8.4 resolution: "@babel/plugin-syntax-async-generators@npm:7.8.4" @@ -490,14 +352,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-jsx@npm:^7.24.1": - version: 7.24.1 - resolution: "@babel/plugin-syntax-jsx@npm:7.24.1" +"@babel/plugin-syntax-jsx@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-syntax-jsx@npm:7.24.7" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.0" + "@babel/helper-plugin-utils": "npm:^7.24.7" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/712f7e7918cb679f106769f57cfab0bc99b311032665c428b98f4c3e2e6d567601d45386a4f246df6a80d741e1f94192b3f008800d66c4f1daae3ad825c243f0 + checksum: 10/a93516ae5b34868ab892a95315027d4e5e38e8bd1cfca6158f2974b0901cbb32bbe64ea10ad5b25f919ddc40c6d8113c4823372909c9c9922170c12b0b1acecb languageName: node linkType: hard @@ -578,18 +440,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-typescript@npm:^7.24.1": - version: 7.24.1 - resolution: "@babel/plugin-syntax-typescript@npm:7.24.1" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/bf4bd70788d5456b5f75572e47a2e31435c7c4e43609bd4dffd2cc0c7a6cf90aabcf6cd389e351854de9a64412a07d30effef5373251fe8f6a4c9db0c0163bda - languageName: node - linkType: hard - -"@babel/plugin-syntax-typescript@npm:^7.7.2": +"@babel/plugin-syntax-typescript@npm:^7.24.7, @babel/plugin-syntax-typescript@npm:^7.7.2": version: 7.24.7 resolution: "@babel/plugin-syntax-typescript@npm:7.24.7" dependencies: @@ -600,7 +451,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-modules-commonjs@npm:^7.23.3": +"@babel/plugin-transform-modules-commonjs@npm:^7.23.3, @babel/plugin-transform-modules-commonjs@npm:^7.24.7": version: 7.24.8 resolution: "@babel/plugin-transform-modules-commonjs@npm:7.24.8" dependencies: @@ -613,65 +464,41 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-modules-commonjs@npm:^7.24.1": - version: 7.24.1 - resolution: "@babel/plugin-transform-modules-commonjs@npm:7.24.1" - dependencies: - "@babel/helper-module-transforms": "npm:^7.23.3" - "@babel/helper-plugin-utils": "npm:^7.24.0" - "@babel/helper-simple-access": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/7326a62ed5f766f93ee75684868635b59884e2801533207ea11561c296de53037949fecad4055d828fa7ebeb6cc9e55908aa3e7c13f930ded3e62ad9f24680d7 - languageName: node - linkType: hard - -"@babel/plugin-transform-typescript@npm:^7.24.1": - version: 7.24.4 - resolution: "@babel/plugin-transform-typescript@npm:7.24.4" +"@babel/plugin-transform-typescript@npm:^7.24.7": + version: 7.24.8 + resolution: "@babel/plugin-transform-typescript@npm:7.24.8" dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.22.5" - "@babel/helper-create-class-features-plugin": "npm:^7.24.4" - "@babel/helper-plugin-utils": "npm:^7.24.0" - "@babel/plugin-syntax-typescript": "npm:^7.24.1" + "@babel/helper-annotate-as-pure": "npm:^7.24.7" + "@babel/helper-create-class-features-plugin": "npm:^7.24.8" + "@babel/helper-plugin-utils": "npm:^7.24.8" + "@babel/plugin-syntax-typescript": "npm:^7.24.7" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/e8d66fbafd6cbfeca2ebe77c4fc67537be9e01813f835ce097fa91329b0cd7ba587a9cf4c4a1df661cdde438741cb3c63d2ab95c97354eb89d7682a4d99bea5d + checksum: 10/a41b687dcb86c28888ef022f4c0136a75ad66ae26787da06ff9c3232ddf7e2bd36a570b89e9c40924cf95880152eb4123c31bc19118f20d441f68256595d8677 languageName: node linkType: hard "@babel/preset-typescript@npm:^7.23.3": - version: 7.24.1 - resolution: "@babel/preset-typescript@npm:7.24.1" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.0" - "@babel/helper-validator-option": "npm:^7.23.5" - "@babel/plugin-syntax-jsx": "npm:^7.24.1" - "@babel/plugin-transform-modules-commonjs": "npm:^7.24.1" - "@babel/plugin-transform-typescript": "npm:^7.24.1" + version: 7.24.7 + resolution: "@babel/preset-typescript@npm:7.24.7" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-validator-option": "npm:^7.24.7" + "@babel/plugin-syntax-jsx": "npm:^7.24.7" + "@babel/plugin-transform-modules-commonjs": "npm:^7.24.7" + "@babel/plugin-transform-typescript": "npm:^7.24.7" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/ba774bd427c9f376769ddbc2723f5801a6b30113a7c3aaa14c36215508e347a527fdae98cfc294f0ecb283d800ee0c1f74e66e38e84c9bc9ed2fe6ed50dcfaf8 + checksum: 10/995e9783f8e474581e7533d6b10ec1fbea69528cc939ad8582b5937e13548e5215d25a8e2c845e7b351fdaa13139896b5e42ab3bde83918ea4e41773f10861ac languageName: node linkType: hard "@babel/runtime@npm:^7.23.9": - version: 7.24.4 - resolution: "@babel/runtime@npm:7.24.4" + version: 7.24.8 + resolution: "@babel/runtime@npm:7.24.8" dependencies: regenerator-runtime: "npm:^0.14.0" - checksum: 10/8ec8ce2c145bc7e31dd39ab66df124f357f65c11489aefacb30f431bae913b9aaa66aa5efe5321ea2bf8878af3fcee338c87e7599519a952e3a6f83aa1b03308 - languageName: node - linkType: hard - -"@babel/template@npm:^7.22.15, @babel/template@npm:^7.24.0": - version: 7.24.0 - resolution: "@babel/template@npm:7.24.0" - dependencies: - "@babel/code-frame": "npm:^7.23.5" - "@babel/parser": "npm:^7.24.0" - "@babel/types": "npm:^7.24.0" - checksum: 10/8c538338c7de8fac8ada691a5a812bdcbd60bd4a4eb5adae2cc9ee19773e8fb1a724312a00af9e1ce49056ffd3c3475e7287b5668cf6360bfb3f8ac827a06ffe + checksum: 10/e6f335e472a8a337379effc15815dd0eddf6a7d0c00b50deb4f9e9585819b45431d0ff3c2d3d0fa58c227a9b04dcc4a85e7245fb57493adb2863b5208c769cbd languageName: node linkType: hard @@ -686,7 +513,7 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.23.2, @babel/traverse@npm:^7.24.7, @babel/traverse@npm:^7.7.2": +"@babel/traverse@npm:^7.23.2, @babel/traverse@npm:^7.24.7, @babel/traverse@npm:^7.24.8, @babel/traverse@npm:^7.7.2": version: 7.24.8 resolution: "@babel/traverse@npm:7.24.8" dependencies: @@ -704,24 +531,6 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.24.1": - version: 7.24.1 - resolution: "@babel/traverse@npm:7.24.1" - dependencies: - "@babel/code-frame": "npm:^7.24.1" - "@babel/generator": "npm:^7.24.1" - "@babel/helper-environment-visitor": "npm:^7.22.20" - "@babel/helper-function-name": "npm:^7.23.0" - "@babel/helper-hoist-variables": "npm:^7.22.5" - "@babel/helper-split-export-declaration": "npm:^7.22.6" - "@babel/parser": "npm:^7.24.1" - "@babel/types": "npm:^7.24.0" - debug: "npm:^4.3.1" - globals: "npm:^11.1.0" - checksum: 10/b9b0173c286ef549e179f3725df3c4958069ad79fe5b9840adeb99692eb4a5a08db4e735c0f086aab52e7e08ec711cee9e7c06cb908d8035641d1382172308d3 - languageName: node - linkType: hard - "@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.23.0, @babel/types@npm:^7.24.7, @babel/types@npm:^7.24.8, @babel/types@npm:^7.24.9, @babel/types@npm:^7.3.3, @babel/types@npm:^7.8.3": version: 7.24.9 resolution: "@babel/types@npm:7.24.9" @@ -733,17 +542,6 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.22.5, @babel/types@npm:^7.24.0": - version: 7.24.0 - resolution: "@babel/types@npm:7.24.0" - dependencies: - "@babel/helper-string-parser": "npm:^7.23.4" - "@babel/helper-validator-identifier": "npm:^7.22.20" - to-fast-properties: "npm:^2.0.0" - checksum: 10/a0b4875ce2e132f9daff0d5b27c7f4c4fcc97f2b084bdc5834e92c9d32592778489029e65d99d00c406da612d87b72d7a236c0afccaa1435c028d0c94c9b6da4 - languageName: node - linkType: hard - "@bcoe/v8-coverage@npm:^0.2.3": version: 0.2.3 resolution: "@bcoe/v8-coverage@npm:0.2.3" @@ -751,40 +549,33 @@ __metadata: languageName: node linkType: hard -"@contentful/content-source-maps@npm:^0.5.0": - version: 0.5.0 - resolution: "@contentful/content-source-maps@npm:0.5.0" +"@contentful/content-source-maps@npm:^0.6.0": + version: 0.6.1 + resolution: "@contentful/content-source-maps@npm:0.6.1" dependencies: "@vercel/stega": "npm:^0.1.2" json-pointer: "npm:^0.6.2" - checksum: 10/c3981024f1831ae928a96e4b37817f198794cbce7ae037aa61bf4c5ff8a37bf4481968313eae47fe0e957bbf211c82f397eca06e4a910bef83f5018512a3e0b4 + checksum: 10/c29ae369959504f6e57912d1419c76c9e42549588f560f5f3b105fafd39ee709d57dc73d85afaf122acb5aa7964c301831292c654985133737592bb3d4f47482 languageName: node linkType: hard "@contentful/rich-text-html-renderer@npm:^16.5.2": - version: 16.5.2 - resolution: "@contentful/rich-text-html-renderer@npm:16.5.2" + version: 16.6.6 + resolution: "@contentful/rich-text-html-renderer@npm:16.6.6" dependencies: - "@contentful/rich-text-types": "npm:^16.5.2" + "@contentful/rich-text-types": "npm:^16.8.1" escape-html: "npm:^1.0.3" - checksum: 10/9eac4ecf08a00316fce02285e0451b8100eaa87454d5f1426fd93887318368dfee71d62d92e5b43d3f8265393c745f72593617d2aa0a1d6620e22f4985d21aba + checksum: 10/665c2ea358b0d431922376395431a8faf2f42263d0e375fa23cda1200d4628e5856f609b427b52401713a02545d10a19b10a81a74f786c8da72ce8cee38477cf languageName: node linkType: hard -"@contentful/rich-text-types@npm:^16.0.2": +"@contentful/rich-text-types@npm:^16.0.2, @contentful/rich-text-types@npm:^16.8.1": version: 16.8.1 resolution: "@contentful/rich-text-types@npm:16.8.1" checksum: 10/e12b77a8239ad635af731ba595b53754470f8bc38329428463eb3a35c6f5eb51ad77c665927e545f54543d8a92051c129d9628dd3c4575eb5126c2bc85f4d183 languageName: node linkType: hard -"@contentful/rich-text-types@npm:^16.5.2": - version: 16.5.2 - resolution: "@contentful/rich-text-types@npm:16.5.2" - checksum: 10/16878d4e2f4eab68bf4e75383a83b49af310def162f29a590e35387264a1ffa9c3e450b48aa737b36128c1202324438ab01181705b076539afc91400a22a5e06 - languageName: node - linkType: hard - "@cspotcode/source-map-support@npm:^0.8.0": version: 0.8.1 resolution: "@cspotcode/source-map-support@npm:0.8.1" @@ -794,10 +585,10 @@ __metadata: languageName: node linkType: hard -"@endo/env-options@npm:^1.1.3": - version: 1.1.3 - resolution: "@endo/env-options@npm:1.1.3" - checksum: 10/df80dec8dbc69a3a134eaa01354ca6e2e4b2b9b1a7637d1f771ea9450296782f398b8e54434ad10ab869b5ec614eef132657604e7568e7c7a90e7821fc56e419 +"@endo/env-options@npm:^1.1.4": + version: 1.1.4 + resolution: "@endo/env-options@npm:1.1.4" + checksum: 10/718c15ae91b5e3d00c8b90f64d07930cc6bc58297cbda55097c339121a8c63045cd909f4bd7a4c39635354d228fa68f6182eb43ac0b0eaa62b05618484ec8209 languageName: node linkType: hard @@ -985,9 +776,9 @@ __metadata: linkType: hard "@eslint-community/regexpp@npm:^4.4.0, @eslint-community/regexpp@npm:^4.6.1": - version: 4.10.0 - resolution: "@eslint-community/regexpp@npm:4.10.0" - checksum: 10/8c36169c815fc5d726078e8c71a5b592957ee60d08c6470f9ce0187c8046af1a00afbda0a065cc40ff18d5d83f82aed9793c6818f7304a74a7488dc9f3ecbd42 + version: 4.11.0 + resolution: "@eslint-community/regexpp@npm:4.11.0" + checksum: 10/f053f371c281ba173fe6ee16dbc4fe544c84870d58035ccca08dba7f6ce1830d895ce3237a0db89ba37616524775dca82f1c502066b58e2d5712d7f87f5ba17c languageName: node linkType: hard @@ -1354,18 +1145,18 @@ __metadata: languageName: node linkType: hard -"@firebase/analytics-compat@npm:0.2.10": - version: 0.2.10 - resolution: "@firebase/analytics-compat@npm:0.2.10" +"@firebase/analytics-compat@npm:0.2.12": + version: 0.2.12 + resolution: "@firebase/analytics-compat@npm:0.2.12" dependencies: - "@firebase/analytics": "npm:0.10.4" + "@firebase/analytics": "npm:0.10.6" "@firebase/analytics-types": "npm:0.8.2" - "@firebase/component": "npm:0.6.7" - "@firebase/util": "npm:1.9.6" + "@firebase/component": "npm:0.6.8" + "@firebase/util": "npm:1.9.7" tslib: "npm:^2.1.0" peerDependencies: "@firebase/app-compat": 0.x - checksum: 10/6dd463dab0279d22d8413accee1a1326e4f1937440b96fb25ae96490ee6ad6c5b32836ac1202f0baaf63d3bdb634e2470a970e7a37e9020b22d6323d4a59b134 + checksum: 10/a3141dfaf24666cee30033b67f7a659f7e9279ee4144bfec2d258d252a8def0d72d73518b03c0425162a5419689287ef125bb8215c17c1fb4d7ca604e254a535 languageName: node linkType: hard @@ -1376,34 +1167,35 @@ __metadata: languageName: node linkType: hard -"@firebase/analytics@npm:0.10.4": - version: 0.10.4 - resolution: "@firebase/analytics@npm:0.10.4" +"@firebase/analytics@npm:0.10.6": + version: 0.10.6 + resolution: "@firebase/analytics@npm:0.10.6" dependencies: - "@firebase/component": "npm:0.6.7" - "@firebase/installations": "npm:0.6.7" + "@firebase/component": "npm:0.6.8" + "@firebase/installations": "npm:0.6.8" "@firebase/logger": "npm:0.4.2" - "@firebase/util": "npm:1.9.6" + "@firebase/util": "npm:1.9.7" + safevalues: "npm:0.6.0" tslib: "npm:^2.1.0" peerDependencies: "@firebase/app": 0.x - checksum: 10/0b8f75ff08e7547399ae0f7bc68a76f54b55ef02f50b603abb0ffe36d60de2c006ecde8db825aff9a48cbf047b4225f8063afe9051383d28dae6498d76317132 + checksum: 10/07d4778ba0566418955223f0cebb1acf91ecc0792f8749ff92df84c1930c94c330f052a20a352cf64eebe44f57afd81a5f8175d8ffacf1f6adec1f2e230c3bc7 languageName: node linkType: hard -"@firebase/app-check-compat@npm:0.3.11": - version: 0.3.11 - resolution: "@firebase/app-check-compat@npm:0.3.11" +"@firebase/app-check-compat@npm:0.3.13": + version: 0.3.13 + resolution: "@firebase/app-check-compat@npm:0.3.13" dependencies: - "@firebase/app-check": "npm:0.8.4" + "@firebase/app-check": "npm:0.8.6" "@firebase/app-check-types": "npm:0.5.2" - "@firebase/component": "npm:0.6.7" + "@firebase/component": "npm:0.6.8" "@firebase/logger": "npm:0.4.2" - "@firebase/util": "npm:1.9.6" + "@firebase/util": "npm:1.9.7" tslib: "npm:^2.1.0" peerDependencies: "@firebase/app-compat": 0.x - checksum: 10/db8f2342ad4b7b3053d082472fe2b4b38572406228828513189adf13a827c80a65d4ce0b19c5f695baadea452fb695b774c9ae5b639a4b733a002df44cb3bbb1 + checksum: 10/2228accc13f78a8ee8a5cefad96b394355976ba2893fc603091234aeb03a522c6e5d7ecb44325574e43f79f44d4a3eb135fbc26bea5ea58643e3673316469294 languageName: node linkType: hard @@ -1421,30 +1213,31 @@ __metadata: languageName: node linkType: hard -"@firebase/app-check@npm:0.8.4": - version: 0.8.4 - resolution: "@firebase/app-check@npm:0.8.4" +"@firebase/app-check@npm:0.8.6": + version: 0.8.6 + resolution: "@firebase/app-check@npm:0.8.6" dependencies: - "@firebase/component": "npm:0.6.7" + "@firebase/component": "npm:0.6.8" "@firebase/logger": "npm:0.4.2" - "@firebase/util": "npm:1.9.6" + "@firebase/util": "npm:1.9.7" + safevalues: "npm:0.6.0" tslib: "npm:^2.1.0" peerDependencies: "@firebase/app": 0.x - checksum: 10/0f4ea3c930141ae08c98dc67463d7b6c9b67c0a98a10768a6be7ba1ec56181ffcdeb5e26f60f6cb721794bd03d7e6132541148417f8da45473139c8a138a7d94 + checksum: 10/227bca93b8d7f04564a332501da78f9ffb0fdaaaf2fd766217414d6be900c542718d163392a2e10be41be3863ad73a946b43fe5dadb25b8283a151e389ca9ef6 languageName: node linkType: hard -"@firebase/app-compat@npm:0.2.35": - version: 0.2.35 - resolution: "@firebase/app-compat@npm:0.2.35" +"@firebase/app-compat@npm:0.2.37": + version: 0.2.37 + resolution: "@firebase/app-compat@npm:0.2.37" dependencies: - "@firebase/app": "npm:0.10.5" - "@firebase/component": "npm:0.6.7" + "@firebase/app": "npm:0.10.7" + "@firebase/component": "npm:0.6.8" "@firebase/logger": "npm:0.4.2" - "@firebase/util": "npm:1.9.6" + "@firebase/util": "npm:1.9.7" tslib: "npm:^2.1.0" - checksum: 10/6de5c8666af2941b56e550f4207b151fade057b91c995cccc5a1f66abb809b9b92c30b3932d473903d17c57b0fa454bae432c3855ac5a5c6b2785e387852b4a2 + checksum: 10/f73a5b3b06c480e17d95ca2685b02d4f44bf4a6a772bce06efbfdbf88ee4d489dc0e7fabf0f12172dd68160deedb5415134aab36d3f620892044142a7ec8084b languageName: node linkType: hard @@ -1455,32 +1248,32 @@ __metadata: languageName: node linkType: hard -"@firebase/app@npm:0.10.5": - version: 0.10.5 - resolution: "@firebase/app@npm:0.10.5" +"@firebase/app@npm:0.10.7": + version: 0.10.7 + resolution: "@firebase/app@npm:0.10.7" dependencies: - "@firebase/component": "npm:0.6.7" + "@firebase/component": "npm:0.6.8" "@firebase/logger": "npm:0.4.2" - "@firebase/util": "npm:1.9.6" + "@firebase/util": "npm:1.9.7" idb: "npm:7.1.1" tslib: "npm:^2.1.0" - checksum: 10/55c4190e578efd083c8e22a7ba7b078b2c4c4911e08e79d5187b2779121f9552ee35490373d2f8a101285e6c57c4b8ea1b019137b0274eaee72aa10a790fb564 + checksum: 10/34680a2b33dfdd1013ded0185edb959142e08ea32269c144495574dcf7c75f5cf7c0f21f38add03e232d75180d36f0a4ce3bd39b2e83fa47fd8905f9fb9f88b2 languageName: node linkType: hard -"@firebase/auth-compat@npm:0.5.9": - version: 0.5.9 - resolution: "@firebase/auth-compat@npm:0.5.9" +"@firebase/auth-compat@npm:0.5.10": + version: 0.5.10 + resolution: "@firebase/auth-compat@npm:0.5.10" dependencies: - "@firebase/auth": "npm:1.7.4" + "@firebase/auth": "npm:1.7.5" "@firebase/auth-types": "npm:0.12.2" - "@firebase/component": "npm:0.6.7" - "@firebase/util": "npm:1.9.6" + "@firebase/component": "npm:0.6.8" + "@firebase/util": "npm:1.9.7" tslib: "npm:^2.1.0" undici: "npm:5.28.4" peerDependencies: "@firebase/app-compat": 0.x - checksum: 10/671203fc6e2fe7d4fc4332129848f552839a771f405fff18502f5035b4e9eda76ad067c2e75b1cbe3e2d4d365d899b092a5f0bd1b395c0ac43c1464e213992fe + checksum: 10/16d85eccb138ee53c36b20a99c1462f83124d7eb2d2e49a7c2efb72cdf7d15c3f6dbf7c8fd8519ec6ec224a66c2c347235b1b254f4375fae2bfe1653b1f78551 languageName: node linkType: hard @@ -1501,13 +1294,13 @@ __metadata: languageName: node linkType: hard -"@firebase/auth@npm:1.7.4": - version: 1.7.4 - resolution: "@firebase/auth@npm:1.7.4" +"@firebase/auth@npm:1.7.5": + version: 1.7.5 + resolution: "@firebase/auth@npm:1.7.5" dependencies: - "@firebase/component": "npm:0.6.7" + "@firebase/component": "npm:0.6.8" "@firebase/logger": "npm:0.4.2" - "@firebase/util": "npm:1.9.6" + "@firebase/util": "npm:1.9.7" tslib: "npm:^2.1.0" undici: "npm:5.28.4" peerDependencies: @@ -1516,71 +1309,71 @@ __metadata: peerDependenciesMeta: "@react-native-async-storage/async-storage": optional: true - checksum: 10/e61a500b1d67cc1cdc3fff824f20400deb480a0015db1eaee5744026329bbc87d8c755ae6da8b22b9aed0fd42e0d6684c67cc1bb47697152f3e82bb7ae416b26 + checksum: 10/b8e66c7a272b0a6a5e63768425761a28995dda1104a80d6a4590b250145b2ec5fc5852306916921970f4b0a7e76dc399cf1c44fb0d40f81688a0a9377aabad88 languageName: node linkType: hard -"@firebase/component@npm:0.6.7": - version: 0.6.7 - resolution: "@firebase/component@npm:0.6.7" +"@firebase/component@npm:0.6.8": + version: 0.6.8 + resolution: "@firebase/component@npm:0.6.8" dependencies: - "@firebase/util": "npm:1.9.6" + "@firebase/util": "npm:1.9.7" tslib: "npm:^2.1.0" - checksum: 10/b41a1c654c6da7ed2570c403dac118b3633d05dc8d9769057fdb66ec04f2f3a8dcb7cff4a87dd5a0b9fa614854586d4376b5aadee6876450c1ee851b59d5b0da + checksum: 10/0df2a61a9d3a32981a82889b4f23923c9adc468e89cadec5984b52d2422bb2b184c1219ed78dc7ec0b7f973ac0b7c2e8f486dee4a32a6741c0627648960e4314 languageName: node linkType: hard -"@firebase/database-compat@npm:1.0.5": - version: 1.0.5 - resolution: "@firebase/database-compat@npm:1.0.5" +"@firebase/database-compat@npm:1.0.6": + version: 1.0.6 + resolution: "@firebase/database-compat@npm:1.0.6" dependencies: - "@firebase/component": "npm:0.6.7" - "@firebase/database": "npm:1.0.5" - "@firebase/database-types": "npm:1.0.3" + "@firebase/component": "npm:0.6.8" + "@firebase/database": "npm:1.0.6" + "@firebase/database-types": "npm:1.0.4" "@firebase/logger": "npm:0.4.2" - "@firebase/util": "npm:1.9.6" + "@firebase/util": "npm:1.9.7" tslib: "npm:^2.1.0" - checksum: 10/e7256f2675860340d560143ef40af9d9e1d941798c166e97b95be3c278068524dfceac9a59fe689a8c6c3704a84faae674601f8504a481521dff43dbc3063b1f + checksum: 10/a4bd30a47bb17de6c1b9508b0141f94a25fa8b73c7f85451200390fcdb6500cdb5e1f14bc699e001798f5552b75a7884cf59361d0eb450374504781e64393a66 languageName: node linkType: hard -"@firebase/database-types@npm:1.0.3": - version: 1.0.3 - resolution: "@firebase/database-types@npm:1.0.3" +"@firebase/database-types@npm:1.0.4": + version: 1.0.4 + resolution: "@firebase/database-types@npm:1.0.4" dependencies: "@firebase/app-types": "npm:0.9.2" - "@firebase/util": "npm:1.9.6" - checksum: 10/ecc36c54552b42b063011d8c58d1645ac3a09a7da36b2cebd25774a1be7d8aa79f752671f75025df44c079289d0c4f42de3cfcdb48cd7fc73539ba93fdcfb476 + "@firebase/util": "npm:1.9.7" + checksum: 10/d76125998d322d1fa31a6bf028e21ba03eafb26d7ae3b408ea8f84f52caf1dea716a236a21c64deb857c5eb091ea53cf148b9a2b99f4e97efc5b7c8cabae9acd languageName: node linkType: hard -"@firebase/database@npm:1.0.5": - version: 1.0.5 - resolution: "@firebase/database@npm:1.0.5" +"@firebase/database@npm:1.0.6": + version: 1.0.6 + resolution: "@firebase/database@npm:1.0.6" dependencies: "@firebase/app-check-interop-types": "npm:0.3.2" "@firebase/auth-interop-types": "npm:0.2.3" - "@firebase/component": "npm:0.6.7" + "@firebase/component": "npm:0.6.8" "@firebase/logger": "npm:0.4.2" - "@firebase/util": "npm:1.9.6" + "@firebase/util": "npm:1.9.7" faye-websocket: "npm:0.11.4" tslib: "npm:^2.1.0" - checksum: 10/1ea0bb014af9e1134f68868a2cfe97befae5c252cc728342d2f8b51ce06df2085dc87b443339eebd4a622c49abdd583bbe2ebf3bd3ac6cad0c148f4a61612c42 + checksum: 10/9d55b61624934703f1636692e4e6765ade127cb636a0d3adda75889cb2be4735eb03347caf4f0184eaef829ade11c5ab210df0327b1ae629f8f0484faa1f8124 languageName: node linkType: hard -"@firebase/firestore-compat@npm:0.3.32": - version: 0.3.32 - resolution: "@firebase/firestore-compat@npm:0.3.32" +"@firebase/firestore-compat@npm:0.3.33": + version: 0.3.33 + resolution: "@firebase/firestore-compat@npm:0.3.33" dependencies: - "@firebase/component": "npm:0.6.7" - "@firebase/firestore": "npm:4.6.3" + "@firebase/component": "npm:0.6.8" + "@firebase/firestore": "npm:4.6.4" "@firebase/firestore-types": "npm:3.0.2" - "@firebase/util": "npm:1.9.6" + "@firebase/util": "npm:1.9.7" tslib: "npm:^2.1.0" peerDependencies: "@firebase/app-compat": 0.x - checksum: 10/65ff915c6c1bb3ccc580887c91d7bb6470fbbb4035f3db24dc91ce297391ceb3d12ce2e311b8c7bcac7976e625089e8871cfe0e6a3c22d85945bb34b3022dc0a + checksum: 10/9e0ebf53487af218ad4f3ef67caf4acb29baac087530a0cbcc7296a194aa03da53572f7792bf1db5a819cec2b6a5336b4187cc63084deac518802fe44aee385b languageName: node linkType: hard @@ -1594,36 +1387,36 @@ __metadata: languageName: node linkType: hard -"@firebase/firestore@npm:4.6.3": - version: 4.6.3 - resolution: "@firebase/firestore@npm:4.6.3" +"@firebase/firestore@npm:4.6.4": + version: 4.6.4 + resolution: "@firebase/firestore@npm:4.6.4" dependencies: - "@firebase/component": "npm:0.6.7" + "@firebase/component": "npm:0.6.8" "@firebase/logger": "npm:0.4.2" - "@firebase/util": "npm:1.9.6" - "@firebase/webchannel-wrapper": "npm:1.0.0" + "@firebase/util": "npm:1.9.7" + "@firebase/webchannel-wrapper": "npm:1.0.1" "@grpc/grpc-js": "npm:~1.9.0" "@grpc/proto-loader": "npm:^0.7.8" tslib: "npm:^2.1.0" undici: "npm:5.28.4" peerDependencies: "@firebase/app": 0.x - checksum: 10/a2edab1abb1ff2abb891c490bb81aa698128493847256c3ae7f20eefa97b51442c2689c0659c8e1daeb7f054d06687b6ae3db0680396a338d3228e8865933bd2 + checksum: 10/31df78e2157f74dfa00c634eeea27853923db679df438786432503fc9099616a2ac42dd32d9098d14a6f221b0e2572c9e29fd0231c34651c8216352729d6cc0c languageName: node linkType: hard -"@firebase/functions-compat@npm:0.3.11": - version: 0.3.11 - resolution: "@firebase/functions-compat@npm:0.3.11" +"@firebase/functions-compat@npm:0.3.12": + version: 0.3.12 + resolution: "@firebase/functions-compat@npm:0.3.12" dependencies: - "@firebase/component": "npm:0.6.7" - "@firebase/functions": "npm:0.11.5" + "@firebase/component": "npm:0.6.8" + "@firebase/functions": "npm:0.11.6" "@firebase/functions-types": "npm:0.6.2" - "@firebase/util": "npm:1.9.6" + "@firebase/util": "npm:1.9.7" tslib: "npm:^2.1.0" peerDependencies: "@firebase/app-compat": 0.x - checksum: 10/271609ea2d95a333b1fab1ed1d4dcec9b92299253f9a310519b1ee70d61be87029da75dd852ce736179c21886bd2f04bb7aee299e731ef88d9ebe3d00540b61a + checksum: 10/d9803c909e848dc381c892d36885a9519b5b025a46afb2c096bf099e840c5c0afc2290b5660a9d763cf6a8bf0ad6f303f85d3011abfc02d75348452bfe3200c8 languageName: node linkType: hard @@ -1634,35 +1427,35 @@ __metadata: languageName: node linkType: hard -"@firebase/functions@npm:0.11.5": - version: 0.11.5 - resolution: "@firebase/functions@npm:0.11.5" +"@firebase/functions@npm:0.11.6": + version: 0.11.6 + resolution: "@firebase/functions@npm:0.11.6" dependencies: "@firebase/app-check-interop-types": "npm:0.3.2" "@firebase/auth-interop-types": "npm:0.2.3" - "@firebase/component": "npm:0.6.7" + "@firebase/component": "npm:0.6.8" "@firebase/messaging-interop-types": "npm:0.2.2" - "@firebase/util": "npm:1.9.6" + "@firebase/util": "npm:1.9.7" tslib: "npm:^2.1.0" undici: "npm:5.28.4" peerDependencies: "@firebase/app": 0.x - checksum: 10/839dd2d9a4a9321bb53d210729246fe4db97e7c26dc562ee1b0c95579b111534fd4a27962e19d2a84cd35f62e0ef75d89c06ffa1177d0a178740cfdcf5325162 + checksum: 10/c1ac2887dd986c8abc408db1da26531f5f9d252f2cd4ee36352239ed0a0fc11902dd1f85db9ec21818028fd737c5a09461c01c0701c85f9ea96b9dc8dfc69f03 languageName: node linkType: hard -"@firebase/installations-compat@npm:0.2.7": - version: 0.2.7 - resolution: "@firebase/installations-compat@npm:0.2.7" +"@firebase/installations-compat@npm:0.2.8": + version: 0.2.8 + resolution: "@firebase/installations-compat@npm:0.2.8" dependencies: - "@firebase/component": "npm:0.6.7" - "@firebase/installations": "npm:0.6.7" + "@firebase/component": "npm:0.6.8" + "@firebase/installations": "npm:0.6.8" "@firebase/installations-types": "npm:0.5.2" - "@firebase/util": "npm:1.9.6" + "@firebase/util": "npm:1.9.7" tslib: "npm:^2.1.0" peerDependencies: "@firebase/app-compat": 0.x - checksum: 10/b32f9ba4b7d6cdb6c932da28ebbd29a7c398065ec4bf353ac40cb2fc8b2f5a77608759635ade41b6d5e2c8f2047e1710beeb3babdfe116d8fc9c6b693ce922aa + checksum: 10/b0eece054763ac6d229b2fca7ead9cbdd10e6c8429be9f03d95e8ed4a488fcf1cbc3a136f49800b07f2b82fb0f5ff1fecb41bee923aa045314ed854bada256d8 languageName: node linkType: hard @@ -1675,17 +1468,17 @@ __metadata: languageName: node linkType: hard -"@firebase/installations@npm:0.6.7": - version: 0.6.7 - resolution: "@firebase/installations@npm:0.6.7" +"@firebase/installations@npm:0.6.8": + version: 0.6.8 + resolution: "@firebase/installations@npm:0.6.8" dependencies: - "@firebase/component": "npm:0.6.7" - "@firebase/util": "npm:1.9.6" + "@firebase/component": "npm:0.6.8" + "@firebase/util": "npm:1.9.7" idb: "npm:7.1.1" tslib: "npm:^2.1.0" peerDependencies: "@firebase/app": 0.x - checksum: 10/643bc8f1e908b2edadbda422e86753c8599e94744f1e2c4e4ae60059d32c4001c76d3fd63377a53bc49e31e991fbfa2f0c31a46dd69ed3151cc7c96316184b6c + checksum: 10/84cdf30d393fad859f035b276c0ef372cd94907fe042498cdd7c24a719d786866a2f976834b90a49c543e37ae317edd669676830ba7cd83f3a3d34b6f2c0f1ee languageName: node linkType: hard @@ -1698,17 +1491,17 @@ __metadata: languageName: node linkType: hard -"@firebase/messaging-compat@npm:0.2.9": - version: 0.2.9 - resolution: "@firebase/messaging-compat@npm:0.2.9" +"@firebase/messaging-compat@npm:0.2.10": + version: 0.2.10 + resolution: "@firebase/messaging-compat@npm:0.2.10" dependencies: - "@firebase/component": "npm:0.6.7" - "@firebase/messaging": "npm:0.12.9" - "@firebase/util": "npm:1.9.6" + "@firebase/component": "npm:0.6.8" + "@firebase/messaging": "npm:0.12.10" + "@firebase/util": "npm:1.9.7" tslib: "npm:^2.1.0" peerDependencies: "@firebase/app-compat": 0.x - checksum: 10/15140fc298bdd28b28fd052eb630770c4388887a9a1d29afa11d4e74cccd3238826fae97e7beb4196a286fe0555023f836442508f13776c6e371bf610819aa5d + checksum: 10/3565546cc935f553c0331dc86e593cd39e09c198c549e801dfb9d4ee53152fcc3bab277355bf1a82a063506b8462038dbd3d2122178b9c1edd39c08a0503244d languageName: node linkType: hard @@ -1719,35 +1512,35 @@ __metadata: languageName: node linkType: hard -"@firebase/messaging@npm:0.12.9": - version: 0.12.9 - resolution: "@firebase/messaging@npm:0.12.9" +"@firebase/messaging@npm:0.12.10": + version: 0.12.10 + resolution: "@firebase/messaging@npm:0.12.10" dependencies: - "@firebase/component": "npm:0.6.7" - "@firebase/installations": "npm:0.6.7" + "@firebase/component": "npm:0.6.8" + "@firebase/installations": "npm:0.6.8" "@firebase/messaging-interop-types": "npm:0.2.2" - "@firebase/util": "npm:1.9.6" + "@firebase/util": "npm:1.9.7" idb: "npm:7.1.1" tslib: "npm:^2.1.0" peerDependencies: "@firebase/app": 0.x - checksum: 10/67ace395630a858f990513398ea2055cbbf6c167f47068c62cc9eb342a6ab459a3fcb0f2f7b26c1c7027cb79a5e67e4124602723ed9e2ac483153981b91206e0 + checksum: 10/c883b465da2cdee0e08f37119bcb4eb1152bec482fe87136f4dbc6fc9ffbbba1f7c25fff70948844633949ab77bc38d81afd31e7227bf398863cccb878be3095 languageName: node linkType: hard -"@firebase/performance-compat@npm:0.2.7": - version: 0.2.7 - resolution: "@firebase/performance-compat@npm:0.2.7" +"@firebase/performance-compat@npm:0.2.8": + version: 0.2.8 + resolution: "@firebase/performance-compat@npm:0.2.8" dependencies: - "@firebase/component": "npm:0.6.7" + "@firebase/component": "npm:0.6.8" "@firebase/logger": "npm:0.4.2" - "@firebase/performance": "npm:0.6.7" + "@firebase/performance": "npm:0.6.8" "@firebase/performance-types": "npm:0.2.2" - "@firebase/util": "npm:1.9.6" + "@firebase/util": "npm:1.9.7" tslib: "npm:^2.1.0" peerDependencies: "@firebase/app-compat": 0.x - checksum: 10/346cc57378384377c4001dfc84322ece4bcebfbcfb7547e9629576ee527c89b257f763afd2e72640c32732006360976abd26c3e17734dd23398b1165bd3e0b9a + checksum: 10/354a31f31c0d07df10a2b33f4ef2b34ba931cb51738c533c5af9e85c1645d9421f2262ec72eb54a3821f1df52644254f92b866d836c528d6a8ff91b399a2aad4 languageName: node linkType: hard @@ -1758,34 +1551,34 @@ __metadata: languageName: node linkType: hard -"@firebase/performance@npm:0.6.7": - version: 0.6.7 - resolution: "@firebase/performance@npm:0.6.7" +"@firebase/performance@npm:0.6.8": + version: 0.6.8 + resolution: "@firebase/performance@npm:0.6.8" dependencies: - "@firebase/component": "npm:0.6.7" - "@firebase/installations": "npm:0.6.7" + "@firebase/component": "npm:0.6.8" + "@firebase/installations": "npm:0.6.8" "@firebase/logger": "npm:0.4.2" - "@firebase/util": "npm:1.9.6" + "@firebase/util": "npm:1.9.7" tslib: "npm:^2.1.0" peerDependencies: "@firebase/app": 0.x - checksum: 10/760b942ad58d73fc6a6b28ff90d59c67fbf5405dc99e2c0fbaa604eecb1fb82419992763c0e59a92147e6cf7d43c1e32ee7cccd39d0706416263bf9684b19a27 + checksum: 10/5c989d154daea84f009f221245231bf5050cf0d96688bf1b20add98429088c815cc6e76e91180394c9d7fe5759094bbe4261c13604ff349c67e3d7113959b146 languageName: node linkType: hard -"@firebase/remote-config-compat@npm:0.2.7": - version: 0.2.7 - resolution: "@firebase/remote-config-compat@npm:0.2.7" +"@firebase/remote-config-compat@npm:0.2.8": + version: 0.2.8 + resolution: "@firebase/remote-config-compat@npm:0.2.8" dependencies: - "@firebase/component": "npm:0.6.7" + "@firebase/component": "npm:0.6.8" "@firebase/logger": "npm:0.4.2" - "@firebase/remote-config": "npm:0.4.7" + "@firebase/remote-config": "npm:0.4.8" "@firebase/remote-config-types": "npm:0.3.2" - "@firebase/util": "npm:1.9.6" + "@firebase/util": "npm:1.9.7" tslib: "npm:^2.1.0" peerDependencies: "@firebase/app-compat": 0.x - checksum: 10/d8e729bb1e5da61a6d0c23dc410a43c404acdd2794cc880a311571354727299ec64936f3dc31b7081786a18cb4e39e12299d8ac4d264af6527fdaecbc5bf0fb3 + checksum: 10/342b27720635c7f68ce8953cd22a5badfc628c3fb9414b32d40a2eaacb2feb2068079eac15c3c811f4327a06d01aa71631a6f2cfe8b80460f1910cfd37126342 languageName: node linkType: hard @@ -1796,33 +1589,33 @@ __metadata: languageName: node linkType: hard -"@firebase/remote-config@npm:0.4.7": - version: 0.4.7 - resolution: "@firebase/remote-config@npm:0.4.7" +"@firebase/remote-config@npm:0.4.8": + version: 0.4.8 + resolution: "@firebase/remote-config@npm:0.4.8" dependencies: - "@firebase/component": "npm:0.6.7" - "@firebase/installations": "npm:0.6.7" + "@firebase/component": "npm:0.6.8" + "@firebase/installations": "npm:0.6.8" "@firebase/logger": "npm:0.4.2" - "@firebase/util": "npm:1.9.6" + "@firebase/util": "npm:1.9.7" tslib: "npm:^2.1.0" peerDependencies: "@firebase/app": 0.x - checksum: 10/9fc4699921d20fbd030e1349fd53baaf06faf67c097158993249be295e51fc93117b8efd6c8f13d07353b72a1dd282cbf5ddd12f7bdcaa8f2ea35e3769210be1 + checksum: 10/58f934b8cdc8582f260a8caf5b903d484d74aecba420221bf57e5df28f8ba7d3f9ca1ab58946c90ab2c29659bbfdad679fc59247468068063a5329d92cd27613 languageName: node linkType: hard -"@firebase/storage-compat@npm:0.3.8": - version: 0.3.8 - resolution: "@firebase/storage-compat@npm:0.3.8" +"@firebase/storage-compat@npm:0.3.9": + version: 0.3.9 + resolution: "@firebase/storage-compat@npm:0.3.9" dependencies: - "@firebase/component": "npm:0.6.7" - "@firebase/storage": "npm:0.12.5" + "@firebase/component": "npm:0.6.8" + "@firebase/storage": "npm:0.12.6" "@firebase/storage-types": "npm:0.8.2" - "@firebase/util": "npm:1.9.6" + "@firebase/util": "npm:1.9.7" tslib: "npm:^2.1.0" peerDependencies: "@firebase/app-compat": 0.x - checksum: 10/9c92e3f8e72946cc926c8ceb4098589df72d41e677dd7177a27517f98a3d6bda094b96c3370eca1c21bcb59166fbc4480d053255ae81649b7c12b10bed9fdb7b + checksum: 10/4b5a35bcac07684dacc181e9cd7a6ba0350df1acc0d7c18b4dae861dc2876194224a0595965dc94ee3424fadeacd28fdbf808a7d19162f3c2f9b35242fc56ff3 languageName: node linkType: hard @@ -1836,49 +1629,49 @@ __metadata: languageName: node linkType: hard -"@firebase/storage@npm:0.12.5": - version: 0.12.5 - resolution: "@firebase/storage@npm:0.12.5" +"@firebase/storage@npm:0.12.6": + version: 0.12.6 + resolution: "@firebase/storage@npm:0.12.6" dependencies: - "@firebase/component": "npm:0.6.7" - "@firebase/util": "npm:1.9.6" + "@firebase/component": "npm:0.6.8" + "@firebase/util": "npm:1.9.7" tslib: "npm:^2.1.0" undici: "npm:5.28.4" peerDependencies: "@firebase/app": 0.x - checksum: 10/e8d3241f1851338f33ab954eb636b464fdd75f2800bdba56d52e807c99f5c6b4ac3f59ff35a836377fb28f72709310af7ba5daba9b8138f9173a621fc2231ca0 + checksum: 10/4f00195374b083b6144a2e7f608391a0ac22e4165235177a447b4211199bf10f37b78a493b6fda164fe09381c309346faa341a8d216757ccd693843c899c7697 languageName: node linkType: hard -"@firebase/util@npm:1.9.6": - version: 1.9.6 - resolution: "@firebase/util@npm:1.9.6" +"@firebase/util@npm:1.9.7": + version: 1.9.7 + resolution: "@firebase/util@npm:1.9.7" dependencies: tslib: "npm:^2.1.0" - checksum: 10/4037241991fefd28df19a38a638b8befb01e3d23b111623986256113604c485c3cca4c761de9888f6271da736dc10f0e8311b47f693d574ea46323c5bfd9abdb + checksum: 10/c31290f45794af68a3ab571db1c0e3cb4d15443adfdc50107b835274b4ad525f839ee79a0da2898dd8b31e64ff811c126d338b0bab117be59c0a065ce984a89a languageName: node linkType: hard -"@firebase/vertexai-preview@npm:0.0.2": - version: 0.0.2 - resolution: "@firebase/vertexai-preview@npm:0.0.2" +"@firebase/vertexai-preview@npm:0.0.3": + version: 0.0.3 + resolution: "@firebase/vertexai-preview@npm:0.0.3" dependencies: "@firebase/app-check-interop-types": "npm:0.3.2" - "@firebase/component": "npm:0.6.7" + "@firebase/component": "npm:0.6.8" "@firebase/logger": "npm:0.4.2" - "@firebase/util": "npm:1.9.6" + "@firebase/util": "npm:1.9.7" tslib: "npm:^2.1.0" peerDependencies: "@firebase/app": 0.x "@firebase/app-types": 0.x - checksum: 10/00ebde87389d85023101b727f2a54af0263a1ab2ad238a9d395672aeb135a023c980a862713b7df6343669be8a10cf8560a1f77da8ceddb0aef2703918c90973 + checksum: 10/490ea78f153b764e117989cb0ee9abeb0f456c6daefc58aa949147b1404a2d90d49c84a04556f8d84a729692ca99ed670b9dd9b37169b93ac01dc8d9242dac13 languageName: node linkType: hard -"@firebase/webchannel-wrapper@npm:1.0.0": - version: 1.0.0 - resolution: "@firebase/webchannel-wrapper@npm:1.0.0" - checksum: 10/007307141753c87d62f94ebe3365bc6d610ccbd1ac13b1b4584ed74452f874a830db69b781e4decc509c1327133df1169eecd39bdccb3c7b3b34ea30f2d884a5 +"@firebase/webchannel-wrapper@npm:1.0.1": + version: 1.0.1 + resolution: "@firebase/webchannel-wrapper@npm:1.0.1" + checksum: 10/22fc7e1e6dd36ab7c13f3a6c1ff51f4d405304424dc323cb146109e7a3ab3b592e2ddb29f53197ee5719a8448cdedb98d9e86a080f9365e389f8429b1c6555c2 languageName: node linkType: hard @@ -2275,9 +2068,9 @@ __metadata: linkType: hard "@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.15": - version: 1.4.15 - resolution: "@jridgewell/sourcemap-codec@npm:1.4.15" - checksum: 10/89960ac087781b961ad918978975bcdf2051cd1741880469783c42de64239703eab9db5230d776d8e6a09d73bb5e4cb964e07d93ee6e2e7aea5a7d726e865c09 + version: 1.5.0 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.0" + checksum: 10/4ed6123217569a1484419ac53f6ea0d9f3b57e5b57ab30d7c267bdb27792a27eb0e4b08e84a2680aa55cc2f2b411ffd6ec3db01c44fdc6dc43aca4b55f8374fd languageName: node linkType: hard @@ -2302,9 +2095,9 @@ __metadata: linkType: hard "@json-rpc-specification/meta-schema@npm:^1.0.6": - version: 1.0.6 - resolution: "@json-rpc-specification/meta-schema@npm:1.0.6" - checksum: 10/56366effca31e75a02cd3898f16288c1f34f5390e8d0ae4de54f53ff221b08e6932cbf9af1ef60a1b5056860a61f90b3cf027d61e0a7904403832c02978a6e3f + version: 1.0.7 + resolution: "@json-rpc-specification/meta-schema@npm:1.0.7" + checksum: 10/16453697727e7c6ea6ac48ea2347dcc09b4bdec2b0baec948fc605de66b87321ce87b58cf94d15b3e60828b7499151fe7b0dcfc86cea98bff05ed09435720965 languageName: node linkType: hard @@ -2366,43 +2159,33 @@ __metadata: languageName: node linkType: hard -"@lavamoat/aa@npm:^4.2.0": - version: 4.2.0 - resolution: "@lavamoat/aa@npm:4.2.0" +"@lavamoat/aa@npm:^4.3.0": + version: 4.3.0 + resolution: "@lavamoat/aa@npm:4.3.0" dependencies: resolve: "npm:1.22.8" bin: lavamoat-ls: src/cli.js - checksum: 10/13901bfe71b74fefac707d6f94651a1f26d72825f24391b59cc712d33e671dd492123891071c900104cf806a48b543b8d19c9f04110c532b3acfa6928fd00cc0 + checksum: 10/c6c24ea88194ad06a83cc2a9e0b6918ee41ab40abcc5c889e1a33f214e48eb160dd0c4cea7b0e299f86d472810ef80e7caf0b2600499222b108690516d9f8123 languageName: node linkType: hard "@lavamoat/allow-scripts@npm:^3.0.4": - version: 3.0.4 - resolution: "@lavamoat/allow-scripts@npm:3.0.4" + version: 3.1.0 + resolution: "@lavamoat/allow-scripts@npm:3.1.0" dependencies: - "@lavamoat/aa": "npm:^4.2.0" + "@lavamoat/aa": "npm:^4.3.0" "@npmcli/run-script": "npm:7.0.4" - bin-links: "npm:4.0.3" + bin-links: "npm:4.0.4" npm-normalize-package-bin: "npm:3.0.1" yargs: "npm:17.7.2" bin: allow-scripts: src/cli.js - checksum: 10/02975c9187d9cfe2c2bd0d2b645a49a04f5bd7c8cc95fa10fbc7fb15988f170d27e76bb6f977908957c2c369447eca35d3c3f3ad7e91d685c33d22ae39a86a98 + checksum: 10/0722e0be74bf2f592a8ed44dc26e93e0cd0cc9f33263fb68add85d533eb9fe6186d10f5919cb94b9254a0a7a3b7459ebb268ac9245887fbfa9f620e8ad87fa8f languageName: node linkType: hard -"@metamask/abi-utils@npm:^2.0.2": - version: 2.0.2 - resolution: "@metamask/abi-utils@npm:2.0.2" - dependencies: - "@metamask/utils": "npm:^8.0.0" - superstruct: "npm:^1.0.3" - checksum: 10/150218e81d4e494196ce967f203a4fa6c03c07dc4e319cf72429cb37586e851adf9b0b89e341faeab38c5f03f6f8dff175486653e9a6da6c7fa9e4c9f96430e9 - languageName: node - linkType: hard - -"@metamask/abi-utils@npm:^2.0.3": +"@metamask/abi-utils@npm:^2.0.3, @metamask/abi-utils@npm:^2.0.4": version: 2.0.4 resolution: "@metamask/abi-utils@npm:2.0.4" dependencies: @@ -2815,12 +2598,12 @@ __metadata: linkType: soft "@metamask/create-release-branch@npm:^3.0.0": - version: 3.0.0 - resolution: "@metamask/create-release-branch@npm:3.0.0" + version: 3.0.1 + resolution: "@metamask/create-release-branch@npm:3.0.1" dependencies: "@metamask/action-utils": "npm:^1.0.0" "@metamask/auto-changelog": "npm:~3.3.0" - "@metamask/utils": "npm:^8.2.1" + "@metamask/utils": "npm:^9.0.0" debug: "npm:^4.3.4" execa: "npm:^8.0.1" pony-cause: "npm:^2.1.9" @@ -2832,7 +2615,7 @@ __metadata: prettier: ^2 bin: create-release-branch: bin/create-release-branch.js - checksum: 10/7d335888feda41996b5213fad9b5d9b1d836972e0d1409268dc7bf901975e5555381f016097cc8cf5dafa3beac90700bc07d2e8f4c8640cbfc100c0f0d0ffe58 + checksum: 10/7a0b0044d64d9446bfb5de17311d06f6f52544c79367d4bb3963c060b43e9d6dde5215ef555d590a37175e1a6aa9313d2042eb06faed4c8eb73c4d17593ab47e languageName: node linkType: hard @@ -2909,7 +2692,7 @@ __metadata: languageName: node linkType: hard -"@metamask/eth-block-tracker@npm:^9.0.2, @metamask/eth-block-tracker@npm:^9.0.3": +"@metamask/eth-block-tracker@npm:^9.0.3": version: 9.0.3 resolution: "@metamask/eth-block-tracker@npm:9.0.3" dependencies: @@ -2949,19 +2732,19 @@ __metadata: linkType: hard "@metamask/eth-json-rpc-middleware@npm:^12.1.1": - version: 12.1.1 - resolution: "@metamask/eth-json-rpc-middleware@npm:12.1.1" + version: 12.1.2 + resolution: "@metamask/eth-json-rpc-middleware@npm:12.1.2" dependencies: - "@metamask/eth-block-tracker": "npm:^9.0.2" - "@metamask/eth-json-rpc-provider": "npm:^2.1.0" + "@metamask/eth-block-tracker": "npm:^9.0.3" + "@metamask/eth-json-rpc-provider": "npm:^3.0.2" "@metamask/eth-sig-util": "npm:^7.0.0" - "@metamask/json-rpc-engine": "npm:^7.1.1" + "@metamask/json-rpc-engine": "npm:^8.0.2" "@metamask/rpc-errors": "npm:^6.0.0" "@metamask/utils": "npm:^8.1.0" klona: "npm:^2.0.6" pify: "npm:^5.0.0" safe-stable-stringify: "npm:^2.4.3" - checksum: 10/0018da198a4f8fbdeab25aa8184377b3215e365b2a631d0f8d7f0577e281c860a1d19fc58ad310afb6d005291c0797dabfe14bdb4adb16300c7f28b11fb26cbc + checksum: 10/1c0f186a35765394a28695bcade84c636b0c92cf3252219d1e9cbdd31231ad09fea5ec7bff7d31e2c7fe4d2158f15b54a5e42166549b69af1f7e475a1c7ae536 languageName: node linkType: hard @@ -3022,16 +2805,16 @@ __metadata: linkType: hard "@metamask/eth-sig-util@npm:^7.0.0, @metamask/eth-sig-util@npm:^7.0.1": - version: 7.0.1 - resolution: "@metamask/eth-sig-util@npm:7.0.1" + version: 7.0.3 + resolution: "@metamask/eth-sig-util@npm:7.0.3" dependencies: "@ethereumjs/util": "npm:^8.1.0" - "@metamask/abi-utils": "npm:^2.0.2" - "@metamask/utils": "npm:^8.1.0" + "@metamask/abi-utils": "npm:^2.0.4" + "@metamask/utils": "npm:^9.0.0" + "@scure/base": "npm:~1.1.3" ethereum-cryptography: "npm:^2.1.2" tweetnacl: "npm:^1.0.3" - tweetnacl-util: "npm:^0.15.1" - checksum: 10/e2aa3f0ad4db75b872705863a502414e17cd72d70c8b2a3f49c700ee237c428919161f345d42c6d00c5267fe87909ec021dee601cc02cb65bab04db2ec771997 + checksum: 10/a71b28607b0815d609cf27ab2d8535393d0a7e7f2c6b7a23d92669b770c664c14e2f539129351147339172b0bb865bb977e7cfb30624870eedab5d7ab700beff languageName: node linkType: hard @@ -3579,7 +3362,7 @@ __metadata: languageName: node linkType: hard -"@metamask/permission-controller@npm:^10.0.1": +"@metamask/permission-controller@npm:^10.0.0, @metamask/permission-controller@npm:^10.0.1": version: 10.0.1 resolution: "@metamask/permission-controller@npm:10.0.1" dependencies: @@ -3727,12 +3510,12 @@ __metadata: linkType: soft "@metamask/post-message-stream@npm:^8.1.0": - version: 8.1.0 - resolution: "@metamask/post-message-stream@npm:8.1.0" + version: 8.1.1 + resolution: "@metamask/post-message-stream@npm:8.1.1" dependencies: - "@metamask/utils": "npm:^8.1.0" + "@metamask/utils": "npm:^9.0.0" readable-stream: "npm:3.6.2" - checksum: 10/0a4f06250e2162b86d0b618e234e3caa3361767af95d0fd86c654aa33352f1948477a1e39d8a40804614f6d8ee29916e63b4f6c70792a0bfc93b2fffaf6fd4c2 + checksum: 10/8218d321abe734522aefaf6b44e4203966c3feaf83e2de6e68eef9dbe92b7fb47fe7fd82eae362147b1d741cc58d78bcc95d8bf02058e260ad2fb978104c96cf languageName: node linkType: hard @@ -3787,7 +3570,7 @@ __metadata: languageName: unknown linkType: soft -"@metamask/providers@npm:17.1.1": +"@metamask/providers@npm:17.1.1, @metamask/providers@npm:^17.0.0": version: 17.1.1 resolution: "@metamask/providers@npm:17.1.1" dependencies: @@ -3808,27 +3591,6 @@ __metadata: languageName: node linkType: hard -"@metamask/providers@npm:^17.0.0": - version: 17.0.0 - resolution: "@metamask/providers@npm:17.0.0" - dependencies: - "@metamask/json-rpc-engine": "npm:^8.0.1" - "@metamask/json-rpc-middleware-stream": "npm:^7.0.1" - "@metamask/object-multiplex": "npm:^2.0.0" - "@metamask/rpc-errors": "npm:^6.2.1" - "@metamask/safe-event-emitter": "npm:^3.1.1" - "@metamask/utils": "npm:^8.3.0" - detect-browser: "npm:^5.2.0" - extension-port-stream: "npm:^3.0.0" - fast-deep-equal: "npm:^3.1.3" - is-stream: "npm:^2.0.0" - readable-stream: "npm:^3.6.2" - peerDependencies: - webextension-polyfill: ^0.10.0 || ^0.11.0 || ^0.12.0 - checksum: 10/9e6b84120276035c9d7e776da4f7e18243116c53fd0c33efca17034971981164720e808dff055e871d08b9ed9d78dc9ebd0d974238baa599d7d91e1560fc1abb - languageName: node - linkType: hard - "@metamask/queued-request-controller@workspace:packages/queued-request-controller": version: 0.0.0-use.local resolution: "@metamask/queued-request-controller@workspace:packages/queued-request-controller" @@ -3968,8 +3730,8 @@ __metadata: linkType: hard "@metamask/snaps-controllers@npm:^8.1.1": - version: 8.3.1 - resolution: "@metamask/snaps-controllers@npm:8.3.1" + version: 8.4.0 + resolution: "@metamask/snaps-controllers@npm:8.4.0" dependencies: "@metamask/approval-controller": "npm:^6.0.2" "@metamask/base-controller": "npm:^5.0.2" @@ -3982,8 +3744,8 @@ __metadata: "@metamask/rpc-errors": "npm:^6.2.1" "@metamask/snaps-registry": "npm:^3.1.0" "@metamask/snaps-rpc-methods": "npm:^9.1.2" - "@metamask/snaps-sdk": "npm:^4.4.1" - "@metamask/snaps-utils": "npm:^7.4.1" + "@metamask/snaps-sdk": "npm:^4.4.2" + "@metamask/snaps-utils": "npm:^7.5.0" "@metamask/utils": "npm:^8.3.0" "@xstate/fsm": "npm:^2.0.0" browserify-zlib: "npm:^0.2.0" @@ -4000,7 +3762,7 @@ __metadata: peerDependenciesMeta: "@metamask/snaps-execution-environments": optional: true - checksum: 10/42c0fa6ac827ac69abce753fe22031cffde60dfa50b54450cb9a6db9f6105bc150650655af41ade41dfa2a6b257f38c9d64ef8360b08b0a844e1d99a17ad0021 + checksum: 10/12943073dea4c9c6c06294ade19aac6ad5b132f9eb7b39587a974ef5fe62ad58abb8c0b22e018cf49090f5f6435e5204ce01912485da77889170fa9aea3d4267 languageName: node linkType: hard @@ -4070,22 +3832,22 @@ __metadata: linkType: hard "@metamask/snaps-rpc-methods@npm:^9.1.2": - version: 9.1.2 - resolution: "@metamask/snaps-rpc-methods@npm:9.1.2" + version: 9.1.4 + resolution: "@metamask/snaps-rpc-methods@npm:9.1.4" dependencies: "@metamask/key-tree": "npm:^9.1.1" - "@metamask/permission-controller": "npm:^9.0.2" + "@metamask/permission-controller": "npm:^10.0.0" "@metamask/rpc-errors": "npm:^6.2.1" - "@metamask/snaps-sdk": "npm:^4.4.1" - "@metamask/snaps-utils": "npm:^7.4.1" + "@metamask/snaps-sdk": "npm:^6.0.0" + "@metamask/snaps-utils": "npm:^7.7.0" "@metamask/utils": "npm:^8.3.0" "@noble/hashes": "npm:^1.3.1" superstruct: "npm:^1.0.3" - checksum: 10/c24bcf81c4a53dc600ba63bddbaf4b307ac53517081a47417970ccc5e50e5ef385228ec1a18df7f24151e438040a20e7a3dd982d683720a4143ccbedc67ad057 + checksum: 10/db4963c2eaf1763ca48be4f095b0adae29596efc9ebf7876ac92fa6e3ef8d2bc5d45634293566b8a4703109c569f2666b4350700139d0566cb29f65be77a17f1 languageName: node linkType: hard -"@metamask/snaps-sdk@npm:^4.2.0": +"@metamask/snaps-sdk@npm:^4.2.0, @metamask/snaps-sdk@npm:^4.4.2": version: 4.4.2 resolution: "@metamask/snaps-sdk@npm:4.4.2" dependencies: @@ -4099,20 +3861,6 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-sdk@npm:^4.4.1": - version: 4.4.1 - resolution: "@metamask/snaps-sdk@npm:4.4.1" - dependencies: - "@metamask/key-tree": "npm:^9.1.1" - "@metamask/providers": "npm:^17.0.0" - "@metamask/rpc-errors": "npm:^6.2.1" - "@metamask/utils": "npm:^8.3.0" - fast-xml-parser: "npm:^4.3.4" - superstruct: "npm:^1.0.3" - checksum: 10/2b64e1ed131544e7db7cd3eef2afdb26c9d5778eb1f83863bb6192871b1e88b726c16c6a403587adfdef2a46aad62c46a8aa3df6166fabca089480b6e544b5f9 - languageName: node - linkType: hard - "@metamask/snaps-sdk@npm:^6.0.0, @metamask/snaps-sdk@npm:^6.1.0": version: 6.1.0 resolution: "@metamask/snaps-sdk@npm:6.1.0" @@ -4126,7 +3874,7 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-utils@npm:^7.4.0, @metamask/snaps-utils@npm:^7.8.0": +"@metamask/snaps-utils@npm:^7.4.0, @metamask/snaps-utils@npm:^7.5.0, @metamask/snaps-utils@npm:^7.7.0, @metamask/snaps-utils@npm:^7.8.0": version: 7.8.0 resolution: "@metamask/snaps-utils@npm:7.8.0" dependencies: @@ -4157,36 +3905,6 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-utils@npm:^7.4.1": - version: 7.4.1 - resolution: "@metamask/snaps-utils@npm:7.4.1" - dependencies: - "@babel/core": "npm:^7.23.2" - "@babel/types": "npm:^7.23.0" - "@metamask/base-controller": "npm:^5.0.2" - "@metamask/key-tree": "npm:^9.1.1" - "@metamask/permission-controller": "npm:^9.0.2" - "@metamask/rpc-errors": "npm:^6.2.1" - "@metamask/slip44": "npm:^3.1.0" - "@metamask/snaps-registry": "npm:^3.1.0" - "@metamask/snaps-sdk": "npm:^4.4.1" - "@metamask/utils": "npm:^8.3.0" - "@noble/hashes": "npm:^1.3.1" - "@scure/base": "npm:^1.1.1" - chalk: "npm:^4.1.2" - cron-parser: "npm:^4.5.0" - fast-deep-equal: "npm:^3.1.3" - fast-json-stable-stringify: "npm:^2.1.0" - marked: "npm:^12.0.1" - rfdc: "npm:^1.3.0" - semver: "npm:^7.5.4" - ses: "npm:^1.1.0" - superstruct: "npm:^1.0.3" - validate-npm-package-name: "npm:^5.0.0" - checksum: 10/f79306a1abe07fdb9869eed923090d43a00cc45123615d5a66a0ae6b57a438019e49ad5f76cc854d1af5d40072d05db5e3dba8b53d0eee81e7d047e8fef9de3d - languageName: node - linkType: hard - "@metamask/superstruct@npm:^3.0.0, @metamask/superstruct@npm:^3.1.0": version: 3.1.0 resolution: "@metamask/superstruct@npm:3.1.0" @@ -4291,23 +4009,6 @@ __metadata: languageName: unknown linkType: soft -"@metamask/utils@npm:^8.0.0, @metamask/utils@npm:^8.2.1": - version: 8.4.0 - resolution: "@metamask/utils@npm:8.4.0" - dependencies: - "@ethereumjs/tx": "npm:^4.2.0" - "@noble/hashes": "npm:^1.3.1" - "@scure/base": "npm:^1.1.3" - "@types/debug": "npm:^4.1.7" - debug: "npm:^4.3.4" - pony-cause: "npm:^2.1.10" - semver: "npm:^7.5.4" - superstruct: "npm:^1.0.3" - uuid: "npm:^9.0.1" - checksum: 10/5ce14d4e1630bf9269ab3b5cf3e05f393776b806c5be10a503128a3bc1d3aee891465d1f3937f537fdecec355a202a99ab70ae74f9bd37d51d3730c98c8f3dfc - languageName: node - linkType: hard - "@metamask/utils@npm:^8.1.0, @metamask/utils@npm:^8.2.0, @metamask/utils@npm:^8.3.0, @metamask/utils@npm:^8.4.0": version: 8.5.0 resolution: "@metamask/utils@npm:8.5.0" @@ -4343,8 +4044,8 @@ __metadata: linkType: hard "@ngraveio/bc-ur@npm:^1.1.5": - version: 1.1.12 - resolution: "@ngraveio/bc-ur@npm:1.1.12" + version: 1.1.13 + resolution: "@ngraveio/bc-ur@npm:1.1.13" dependencies: "@keystonehq/alias-sampling": "npm:^0.1.1" assert: "npm:^2.0.0" @@ -4353,14 +4054,14 @@ __metadata: crc: "npm:^3.8.0" jsbi: "npm:^3.1.5" sha.js: "npm:^2.4.11" - checksum: 10/43f030019afc3074546acd61041827c0d6a81a523239f336a25e1540f5353bc69af8a83c9f0989a0bafdb72ebbd32de79965f001f092227caef8e96ee472e21e + checksum: 10/0d3301b673a0bd9a069dae1f017cfd03010fddf19c1449d1a9e986b9b879ee4611f5af690ace9f59b75707573d1d3d6a4983166207db743425974a736689c6a0 languageName: node linkType: hard "@noble/ciphers@npm:^0.5.2": - version: 0.5.2 - resolution: "@noble/ciphers@npm:0.5.2" - checksum: 10/47a5958954249d5edb49aff48ae6fbfff4d4e5a6bb221c010ebc8e7470c410e9208a2f3f6bf8b7eca83057277478f4ccbdbdcf1bfd324608b334b9f9d28a9fbb + version: 0.5.3 + resolution: "@noble/ciphers@npm:0.5.3" + checksum: 10/af0ad96b5807feace93e63549e05de6f5e305b36e2e95f02d90532893fbc3af3f19b9621b6de4caa98303659e5df2e7aa082064e5d4a82e6f38c728d48dfae5d languageName: node linkType: hard @@ -4373,21 +4074,12 @@ __metadata: languageName: node linkType: hard -"@noble/curves@npm:1.3.0, @noble/curves@npm:~1.3.0": - version: 1.3.0 - resolution: "@noble/curves@npm:1.3.0" - dependencies: - "@noble/hashes": "npm:1.3.3" - checksum: 10/f3cbdd1af00179e30146eac5539e6df290228fb857a7a8ba36d1a772cbe59288a2ca83d06f175d3446ef00db3a80d7fd8b8347f7de9c2d4d5bf3865d8bb78252 - languageName: node - linkType: hard - -"@noble/curves@npm:^1.2.0": - version: 1.4.0 - resolution: "@noble/curves@npm:1.4.0" +"@noble/curves@npm:1.4.2, @noble/curves@npm:^1.2.0, @noble/curves@npm:~1.4.0": + version: 1.4.2 + resolution: "@noble/curves@npm:1.4.2" dependencies: "@noble/hashes": "npm:1.4.0" - checksum: 10/b21b30a36ff02bfcc0f5e6163d245cdbaf7f640511fff97ccf83fc207ee79cfd91584b4d97977374de04cb118a55eb63a7964c82596a64162bbc42bc685ae6d9 + checksum: 10/f433a2e8811ae345109388eadfa18ef2b0004c1f79417553241db4f0ad0d59550be6298a4f43d989c627e9f7551ffae6e402a4edf0173981e6da95fc7cab5123 languageName: node linkType: hard @@ -4398,20 +4090,20 @@ __metadata: languageName: node linkType: hard -"@noble/hashes@npm:1.3.3, @noble/hashes@npm:~1.3.2": - version: 1.3.3 - resolution: "@noble/hashes@npm:1.3.3" - checksum: 10/1025ddde4d24630e95c0818e63d2d54ee131b980fe113312d17ed7468bc18f54486ac86c907685759f8a7e13c2f9b9e83ec7b67d1cc20836f36b5e4a65bb102d - languageName: node - linkType: hard - -"@noble/hashes@npm:1.4.0, @noble/hashes@npm:^1.1.2, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:^1.3.2, @noble/hashes@npm:^1.4.0": +"@noble/hashes@npm:1.4.0, @noble/hashes@npm:^1.1.2, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:^1.3.2, @noble/hashes@npm:^1.4.0, @noble/hashes@npm:~1.4.0": version: 1.4.0 resolution: "@noble/hashes@npm:1.4.0" checksum: 10/e156e65794c473794c52fa9d06baf1eb20903d0d96719530f523cc4450f6c721a957c544796e6efd0197b2296e7cd70efeb312f861465e17940a3e3c7e0febc6 languageName: node linkType: hard +"@noble/hashes@npm:~1.3.2": + version: 1.3.3 + resolution: "@noble/hashes@npm:1.3.3" + checksum: 10/1025ddde4d24630e95c0818e63d2d54ee131b980fe113312d17ed7468bc18f54486ac86c907685759f8a7e13c2f9b9e83ec7b67d1cc20836f36b5e4a65bb102d + languageName: node + linkType: hard + "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -4453,27 +4145,28 @@ __metadata: linkType: hard "@npmcli/fs@npm:^3.1.0": - version: 3.1.0 - resolution: "@npmcli/fs@npm:3.1.0" + version: 3.1.1 + resolution: "@npmcli/fs@npm:3.1.1" dependencies: semver: "npm:^7.3.5" - checksum: 10/f3a7ab3a31de65e42aeb6ed03ed035ef123d2de7af4deb9d4a003d27acc8618b57d9fb9d259fe6c28ca538032a028f37337264388ba27d26d37fff7dde22476e + checksum: 10/1e0e04087049b24b38bc0b30d87a9388ee3ca1d3fdfc347c2f77d84fcfe6a51f250bc57ba2c1f614d7e4285c6c62bf8c769bc19aa0949ea39e5b043ee023b0bd languageName: node linkType: hard "@npmcli/git@npm:^5.0.0": - version: 5.0.5 - resolution: "@npmcli/git@npm:5.0.5" + version: 5.0.8 + resolution: "@npmcli/git@npm:5.0.8" dependencies: "@npmcli/promise-spawn": "npm:^7.0.0" + ini: "npm:^4.1.3" lru-cache: "npm:^10.0.1" npm-pick-manifest: "npm:^9.0.0" - proc-log: "npm:^3.0.0" + proc-log: "npm:^4.0.0" promise-inflight: "npm:^1.0.1" promise-retry: "npm:^2.0.1" semver: "npm:^7.3.5" which: "npm:^4.0.0" - checksum: 10/e9c594c3a880fa9172b39211f2921c1ab798ca9a62088e12334e0eee45f120fa326afa8c8d3cd7da321abe961420dda0a0ceffdf3728b8135b6ab645fe5c0dda + checksum: 10/e6f94175fb9dde13d84849b29b32ffb4c4df968822cc85df2aebfca13bf8ca76f33b1d281911f5bcddc95bccba2f9e795669c736a38de4d9c76efb5047ffb4fb languageName: node linkType: hard @@ -4485,26 +4178,26 @@ __metadata: linkType: hard "@npmcli/package-json@npm:^5.0.0": - version: 5.0.2 - resolution: "@npmcli/package-json@npm:5.0.2" + version: 5.2.0 + resolution: "@npmcli/package-json@npm:5.2.0" dependencies: "@npmcli/git": "npm:^5.0.0" glob: "npm:^10.2.2" hosted-git-info: "npm:^7.0.0" json-parse-even-better-errors: "npm:^3.0.0" normalize-package-data: "npm:^6.0.0" - proc-log: "npm:^3.0.0" + proc-log: "npm:^4.0.0" semver: "npm:^7.5.3" - checksum: 10/178a93fb498a05cce37bb3fdeef777f509912c41d5a8470a5d1d4ecb79745f79e17041eb4226d34a302bbbb20be3a278533c96d0af4a61bd4c24f54121cfcfa7 + checksum: 10/c3d2218877bfc005bca3b7a11f53825bf16a68811b8e8ed0c9b219cceb8e8e646d70efab8c5d6decbd8007f286076468b3f456dab4d41d648aff73a5f3a6fce2 languageName: node linkType: hard "@npmcli/promise-spawn@npm:^7.0.0": - version: 7.0.1 - resolution: "@npmcli/promise-spawn@npm:7.0.1" + version: 7.0.2 + resolution: "@npmcli/promise-spawn@npm:7.0.2" dependencies: which: "npm:^4.0.0" - checksum: 10/7cbfc3c5e0bcad28e362dc34418b7507afea4fa82d692b802d9b8999ebdc99ceb2686f5959b5b9890e424983cee801401d3e972638f6942f75a2976a2c61774c + checksum: 10/94cbbbeeb20342026c3b68fc8eb09e1600b7645d4e509f2588ef5ea7cff977eb01e628cc8e014595d04a6af4b4bc5c467c950a8135920f39f7c7b57fba43f4e9 languageName: node linkType: hard @@ -4608,150 +4301,143 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-android-arm-eabi@npm:4.16.4": - version: 4.16.4 - resolution: "@rollup/rollup-android-arm-eabi@npm:4.16.4" +"@rollup/rollup-android-arm-eabi@npm:4.19.0": + version: 4.19.0 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.19.0" conditions: os=android & cpu=arm languageName: node linkType: hard -"@rollup/rollup-android-arm64@npm:4.16.4": - version: 4.16.4 - resolution: "@rollup/rollup-android-arm64@npm:4.16.4" +"@rollup/rollup-android-arm64@npm:4.19.0": + version: 4.19.0 + resolution: "@rollup/rollup-android-arm64@npm:4.19.0" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-arm64@npm:4.16.4": - version: 4.16.4 - resolution: "@rollup/rollup-darwin-arm64@npm:4.16.4" +"@rollup/rollup-darwin-arm64@npm:4.19.0": + version: 4.19.0 + resolution: "@rollup/rollup-darwin-arm64@npm:4.19.0" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-x64@npm:4.16.4": - version: 4.16.4 - resolution: "@rollup/rollup-darwin-x64@npm:4.16.4" +"@rollup/rollup-darwin-x64@npm:4.19.0": + version: 4.19.0 + resolution: "@rollup/rollup-darwin-x64@npm:4.19.0" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-linux-arm-gnueabihf@npm:4.16.4": - version: 4.16.4 - resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.16.4" +"@rollup/rollup-linux-arm-gnueabihf@npm:4.19.0": + version: 4.19.0 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.19.0" conditions: os=linux & cpu=arm & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm-musleabihf@npm:4.16.4": - version: 4.16.4 - resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.16.4" +"@rollup/rollup-linux-arm-musleabihf@npm:4.19.0": + version: 4.19.0 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.19.0" conditions: os=linux & cpu=arm & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-arm64-gnu@npm:4.16.4": - version: 4.16.4 - resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.16.4" +"@rollup/rollup-linux-arm64-gnu@npm:4.19.0": + version: 4.19.0 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.19.0" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm64-musl@npm:4.16.4": - version: 4.16.4 - resolution: "@rollup/rollup-linux-arm64-musl@npm:4.16.4" +"@rollup/rollup-linux-arm64-musl@npm:4.19.0": + version: 4.19.0 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.19.0" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-powerpc64le-gnu@npm:4.16.4": - version: 4.16.4 - resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.16.4" +"@rollup/rollup-linux-powerpc64le-gnu@npm:4.19.0": + version: 4.19.0 + resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.19.0" conditions: os=linux & cpu=ppc64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-gnu@npm:4.16.4": - version: 4.16.4 - resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.16.4" +"@rollup/rollup-linux-riscv64-gnu@npm:4.19.0": + version: 4.19.0 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.19.0" conditions: os=linux & cpu=riscv64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-s390x-gnu@npm:4.16.4": - version: 4.16.4 - resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.16.4" +"@rollup/rollup-linux-s390x-gnu@npm:4.19.0": + version: 4.19.0 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.19.0" conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-gnu@npm:4.16.4": - version: 4.16.4 - resolution: "@rollup/rollup-linux-x64-gnu@npm:4.16.4" +"@rollup/rollup-linux-x64-gnu@npm:4.19.0": + version: 4.19.0 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.19.0" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-musl@npm:4.16.4": - version: 4.16.4 - resolution: "@rollup/rollup-linux-x64-musl@npm:4.16.4" +"@rollup/rollup-linux-x64-musl@npm:4.19.0": + version: 4.19.0 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.19.0" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-win32-arm64-msvc@npm:4.16.4": - version: 4.16.4 - resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.16.4" +"@rollup/rollup-win32-arm64-msvc@npm:4.19.0": + version: 4.19.0 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.19.0" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-win32-ia32-msvc@npm:4.16.4": - version: 4.16.4 - resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.16.4" +"@rollup/rollup-win32-ia32-msvc@npm:4.19.0": + version: 4.19.0 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.19.0" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@rollup/rollup-win32-x64-msvc@npm:4.16.4": - version: 4.16.4 - resolution: "@rollup/rollup-win32-x64-msvc@npm:4.16.4" +"@rollup/rollup-win32-x64-msvc@npm:4.19.0": + version: 4.19.0 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.19.0" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@scure/base@npm:^1.0.0, @scure/base@npm:^1.1.1, @scure/base@npm:^1.1.3, @scure/base@npm:~1.1.3": +"@scure/base@npm:^1.0.0, @scure/base@npm:^1.1.1, @scure/base@npm:^1.1.3, @scure/base@npm:~1.1.3, @scure/base@npm:~1.1.6": version: 1.1.7 resolution: "@scure/base@npm:1.1.7" checksum: 10/fc50ffaab36cb46ff9fa4dc5052a06089ab6a6707f63d596bb34aaaec76173c9a564ac312a0b981b5e7a5349d60097b8878673c75d6cbfc4da7012b63a82099b languageName: node linkType: hard -"@scure/base@npm:~1.1.4": - version: 1.1.6 - resolution: "@scure/base@npm:1.1.6" - checksum: 10/814fd1cce24f1e152751fabca2853d26aaa96ff8a9349c43d9aebc3b3d8ca88dd902966e1c289590a37f35d4c4436c6aedb1b386924b2909072045af4c3e9fe4 - languageName: node - linkType: hard - -"@scure/bip32@npm:1.3.3": - version: 1.3.3 - resolution: "@scure/bip32@npm:1.3.3" +"@scure/bip32@npm:1.4.0": + version: 1.4.0 + resolution: "@scure/bip32@npm:1.4.0" dependencies: - "@noble/curves": "npm:~1.3.0" - "@noble/hashes": "npm:~1.3.2" - "@scure/base": "npm:~1.1.4" - checksum: 10/4b8b75567866ff7d6b3ba154538add02d2951e9433e8dd7f0014331ac500cda5a88fe3d39b408fcc36e86b633682013f172b967af022c2e4e4ab07336801d688 + "@noble/curves": "npm:~1.4.0" + "@noble/hashes": "npm:~1.4.0" + "@scure/base": "npm:~1.1.6" + checksum: 10/6cd5062d902564d9e970597ec8b1adacb415b2eadfbb95aee1a1a0480a52eb0de4d294d3753aa8b48548064c9795ed108d348a31a8ce3fc88785377bb12c63b9 languageName: node linkType: hard -"@scure/bip39@npm:1.2.2": - version: 1.2.2 - resolution: "@scure/bip39@npm:1.2.2" +"@scure/bip39@npm:1.3.0": + version: 1.3.0 + resolution: "@scure/bip39@npm:1.3.0" dependencies: - "@noble/hashes": "npm:~1.3.2" - "@scure/base": "npm:~1.1.4" - checksum: 10/f71aceda10a7937bf3779fd2b4c4156c95ec9813269470ddca464cb8ab610d2451b173037f4b1e6dac45414e406e7adc7b5814c51279f4474d5d38140bbee542 + "@noble/hashes": "npm:~1.4.0" + "@scure/base": "npm:~1.1.6" + checksum: 10/7d71fd58153de22fe8cd65b525f6958a80487bc9d0fbc32c71c328aeafe41fa259f989d2f1e0fa4fdfeaf83b8fcf9310d52ed9862987e46c2f2bfb9dd8cf9fc1 languageName: node linkType: hard @@ -4932,11 +4618,11 @@ __metadata: linkType: hard "@types/babel__traverse@npm:*, @types/babel__traverse@npm:^7.0.4, @types/babel__traverse@npm:^7.0.6": - version: 7.20.5 - resolution: "@types/babel__traverse@npm:7.20.5" + version: 7.20.6 + resolution: "@types/babel__traverse@npm:7.20.6" dependencies: "@babel/types": "npm:^7.20.7" - checksum: 10/f0352d537448e1e37f27e6bb8c962d7893720a92fde9d8601a68a93dbc14e15c088b4c0c8f71021d0966d09fba802ef3de11fdb6766c33993f8cf24f1277c6a9 + checksum: 10/63d13a3789aa1e783b87a8b03d9fb2c2c90078de7782422feff1631b8c2a25db626e63a63ac5a1465d47359201c73069dacb4b52149d17c568187625da3064ae languageName: node linkType: hard @@ -4966,12 +4652,12 @@ __metadata: linkType: hard "@types/eslint@npm:^8.44.7": - version: 8.56.8 - resolution: "@types/eslint@npm:8.56.8" + version: 8.56.10 + resolution: "@types/eslint@npm:8.56.10" dependencies: "@types/estree": "npm:*" "@types/json-schema": "npm:*" - checksum: 10/2f1514f9510cbf5662142b067e6a6641b04f65f5c7d3ea67c63d48a7788376322ed0c4557c4200923cfaf5059b50521384476ba92a3c3108ec9dc5164b4fbda1 + checksum: 10/0cdd914b944ebba51c35827d3ef95bc3e16eb82b4c2741f6437fa57cdb00a4407c77f89c220afe9e4c9566982ec8a0fb9b97c956ac3bd4623a3b6af32eed8424 languageName: node linkType: hard @@ -5060,9 +4746,9 @@ __metadata: linkType: hard "@types/lodash@npm:^4.14.191": - version: 4.17.0 - resolution: "@types/lodash@npm:4.17.0" - checksum: 10/2053203292b5af99352d108656ceb15d39da5922fc3fb8186e1552d65c82d6e545372cc97f36c95873aa7186404d59d9305e9d49254d4ae55e77df1e27ab7b5d + version: 4.17.7 + resolution: "@types/lodash@npm:4.17.7" + checksum: 10/b8177f19cf962414a66989837481b13f546afc2e98e8d465bec59e6ac03a59c584eb7053ce511cde3a09c5f3096d22a5ae22cfb56b23f3b0da75b0743b6b1a44 languageName: node linkType: hard @@ -5081,11 +4767,11 @@ __metadata: linkType: hard "@types/node@npm:*, @types/node@npm:>=12.12.47, @types/node@npm:>=13.7.0": - version: 20.14.2 - resolution: "@types/node@npm:20.14.2" + version: 20.14.11 + resolution: "@types/node@npm:20.14.11" dependencies: undici-types: "npm:~5.26.4" - checksum: 10/c38e47b190fa0a8bdfde24b036dddcf9401551f2fb170a90ff33625c7d6f218907e81c74e0fa6e394804a32623c24c60c50e249badc951007830f0d02c48ee0f + checksum: 10/344e1ce1ed16c86ed1c4209ab4d1de67db83dd6b694a6fabe295c47144dde2c58dabddae9f39a0a2bdd246e95f8d141ccfe848e464884b48b8918df4f7788025 languageName: node linkType: hard @@ -5097,9 +4783,9 @@ __metadata: linkType: hard "@types/node@npm:^16.18.54": - version: 16.18.96 - resolution: "@types/node@npm:16.18.96" - checksum: 10/6b6d1250c777f40bbc665024b13912c8f65ebe64e7dfe8369084aa65a8ebeaec7037f7a87f280b211bdb0b2b8a4173619be89e642962a1f58690d3bac08bfa33 + version: 16.18.103 + resolution: "@types/node@npm:16.18.103" + checksum: 10/3ec07c68f828d2ce788c1e367a65efafd34b2313e5db35a9a6c0cc48676919a61adaa35ac8e7160912ab64d70c3cd05f2aacda7ec41057400ef5ceec7d78c678 languageName: node linkType: hard @@ -5365,60 +5051,60 @@ __metadata: languageName: node linkType: hard -"@vue/compiler-core@npm:3.4.21": - version: 3.4.21 - resolution: "@vue/compiler-core@npm:3.4.21" +"@vue/compiler-core@npm:3.4.33": + version: 3.4.33 + resolution: "@vue/compiler-core@npm:3.4.33" dependencies: - "@babel/parser": "npm:^7.23.9" - "@vue/shared": "npm:3.4.21" + "@babel/parser": "npm:^7.24.7" + "@vue/shared": "npm:3.4.33" entities: "npm:^4.5.0" estree-walker: "npm:^2.0.2" - source-map-js: "npm:^1.0.2" - checksum: 10/ccc059def59d353c9a994ed42f7198dd495dc77ab5d35e967ad9e5ba7a633585986407ad47994b65dc54d63690081a3eaf66cde519333a55858b7982645aa665 + source-map-js: "npm:^1.2.0" + checksum: 10/91580713a537687244891f56671a1db356517088a715d745a68a4021799d4a0ff69de7b265f4f4f4ab82d24f5a08b62a2c0844d8cb557d6936b5fef154d791f1 languageName: node linkType: hard -"@vue/compiler-dom@npm:3.4.21": - version: 3.4.21 - resolution: "@vue/compiler-dom@npm:3.4.21" +"@vue/compiler-dom@npm:3.4.33": + version: 3.4.33 + resolution: "@vue/compiler-dom@npm:3.4.33" dependencies: - "@vue/compiler-core": "npm:3.4.21" - "@vue/shared": "npm:3.4.21" - checksum: 10/f1c0bf9731bc84b78a5ce1105160288a834d041bd0000a61e6377313cc49712bea5a839b304304f3d8203227cf828aa80ec57c6a840c2a59bf11d7e83dd1bb0f + "@vue/compiler-core": "npm:3.4.33" + "@vue/shared": "npm:3.4.33" + checksum: 10/3003393487f800c4f2978cc248c285efc9e1f984a7e8f1ff36146bc26a5914c6978e1e8305cf0ee1103c7bae1202259c02a51a0bf069772af5f125724c055cdf languageName: node linkType: hard "@vue/compiler-sfc@npm:^3.3.4": - version: 3.4.21 - resolution: "@vue/compiler-sfc@npm:3.4.21" - dependencies: - "@babel/parser": "npm:^7.23.9" - "@vue/compiler-core": "npm:3.4.21" - "@vue/compiler-dom": "npm:3.4.21" - "@vue/compiler-ssr": "npm:3.4.21" - "@vue/shared": "npm:3.4.21" + version: 3.4.33 + resolution: "@vue/compiler-sfc@npm:3.4.33" + dependencies: + "@babel/parser": "npm:^7.24.7" + "@vue/compiler-core": "npm:3.4.33" + "@vue/compiler-dom": "npm:3.4.33" + "@vue/compiler-ssr": "npm:3.4.33" + "@vue/shared": "npm:3.4.33" estree-walker: "npm:^2.0.2" - magic-string: "npm:^0.30.7" - postcss: "npm:^8.4.35" - source-map-js: "npm:^1.0.2" - checksum: 10/b340edc6db559f21b3205011e0846534e7ee3bba88bb026b073184c8457702fbccd5e41ec7d91ceac5be6b4966084bc12ff293842ee95269debafe525794369f + magic-string: "npm:^0.30.10" + postcss: "npm:^8.4.39" + source-map-js: "npm:^1.2.0" + checksum: 10/af59aca33af931f5f1af4e92cd04c1b87cd722368f617b62bcdd7e8f132db1253f22e003c64e089b8caafd756c190a54d0bf42a2218dc9369b00985302de3914 languageName: node linkType: hard -"@vue/compiler-ssr@npm:3.4.21": - version: 3.4.21 - resolution: "@vue/compiler-ssr@npm:3.4.21" +"@vue/compiler-ssr@npm:3.4.33": + version: 3.4.33 + resolution: "@vue/compiler-ssr@npm:3.4.33" dependencies: - "@vue/compiler-dom": "npm:3.4.21" - "@vue/shared": "npm:3.4.21" - checksum: 10/9400a5ff95fb6f5672e190a1b43804eb0c5131c7012ac90c0df069b75ea0a5bf02d9fc5ded01c786dadf1ee922b75d985c50d0b0b092fb0bc491ab291ec140c7 + "@vue/compiler-dom": "npm:3.4.33" + "@vue/shared": "npm:3.4.33" + checksum: 10/347a5faac078018dfead2cc04e88ec37d82850815607b6a758a5d497b96f906d8cae69c055feced79b56f7d1615888b5e311a1dace306bdef2cf1a516c3a7e86 languageName: node linkType: hard -"@vue/shared@npm:3.4.21": - version: 3.4.21 - resolution: "@vue/shared@npm:3.4.21" - checksum: 10/38e8cca37437841717f92e562bf7ae1b07865d33f24b65b63e1a0107c75ab5a4584f0847317275bfeb03b3a7a06b7d9e68009d0588aab8c432715ac50f07f77a +"@vue/shared@npm:3.4.33": + version: 3.4.33 + resolution: "@vue/shared@npm:3.4.33" + checksum: 10/0cb9f1c4841f3da14ee9bab0a0fb169ddf1a1979b1aa70a4c7b6279e7fc7e5296c7c63f56bbdb1f43e4264aab3d7daa3e2355905caf727cf5f9088f99dd1a8c4 languageName: node linkType: hard @@ -5479,9 +5165,11 @@ __metadata: linkType: hard "acorn-walk@npm:^8.1.1": - version: 8.3.2 - resolution: "acorn-walk@npm:8.3.2" - checksum: 10/57dbe2fd8cf744f562431775741c5c087196cd7a65ce4ccb3f3981cdfad25cd24ad2bad404997b88464ac01e789a0a61e5e355b2a84876f13deef39fb39686ca + version: 8.3.3 + resolution: "acorn-walk@npm:8.3.3" + dependencies: + acorn: "npm:^8.11.0" + checksum: 10/59701dcb7070679622ba8e9c7f37577b4935565747ca0fd7c1c3ad30b3f1b1b008276282664e323b5495eb49f77fa12d3816fd06dc68e18f90fbebe759f71450 languageName: node linkType: hard @@ -5494,12 +5182,12 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.2.4, acorn@npm:^8.4.1, acorn@npm:^8.9.0": - version: 8.11.3 - resolution: "acorn@npm:8.11.3" +"acorn@npm:^8.11.0, acorn@npm:^8.2.4, acorn@npm:^8.4.1, acorn@npm:^8.9.0": + version: 8.12.1 + resolution: "acorn@npm:8.12.1" bin: acorn: bin/acorn - checksum: 10/b688e7e3c64d9bfb17b596e1b35e4da9d50553713b3b3630cf5690f2b023a84eac90c56851e6912b483fe60e8b4ea28b254c07e92f17ef83d72d78745a8352dd + checksum: 10/d08c2d122bba32d0861e0aa840b2ee25946c286d5dc5990abca991baf8cdbfbe199b05aacb221b979411a2fea36f83e26b5ac4f6b4e0ce49038c62316c1848f0 languageName: node linkType: hard @@ -5558,14 +5246,14 @@ __metadata: linkType: hard "ajv@npm:^8.0.1": - version: 8.12.0 - resolution: "ajv@npm:8.12.0" + version: 8.17.1 + resolution: "ajv@npm:8.17.1" dependencies: - fast-deep-equal: "npm:^3.1.1" + fast-deep-equal: "npm:^3.1.3" + fast-uri: "npm:^3.0.1" json-schema-traverse: "npm:^1.0.0" require-from-string: "npm:^2.0.2" - uri-js: "npm:^4.2.2" - checksum: 10/b406f3b79b5756ac53bfe2c20852471b08e122bc1ee4cde08ae4d6a800574d9cd78d60c81c69c63ff81e4da7cd0b638fafbb2303ae580d49cf1600b9059efb85 + checksum: 10/ee3c62162c953e91986c838f004132b6a253d700f1e51253b99791e2dbfdb39161bc950ebdc2f156f8568035bb5ed8be7bd78289cd9ecbf3381fe8f5b82e3f33 languageName: node linkType: hard @@ -5674,9 +5362,9 @@ __metadata: linkType: hard "apg-js@npm:^4.1.1, apg-js@npm:^4.3.0": - version: 4.3.0 - resolution: "apg-js@npm:4.3.0" - checksum: 10/c82798abdfdc1a67222cec46b5968b352e8a187e9a3c979fca1be9c74fe7c0fb5d6301c355c832a21a3d7973a7228ec7501b5290a2442644601a14f5abe3543e + version: 4.4.0 + resolution: "apg-js@npm:4.4.0" + checksum: 10/425f19096026742f5f156f26542b68f55602aa60f0c4ae2d72a0a888cf15fe9622223191202262dd8979d76a6125de9d8fd164d56c95fb113f49099f405eb08c languageName: node linkType: hard @@ -5943,18 +5631,18 @@ __metadata: linkType: hard "bare-events@npm:^2.2.0": - version: 2.2.2 - resolution: "bare-events@npm:2.2.2" - checksum: 10/79d50a739d9f2173e881e0957f9b0ee64befde3d7b6f955b1450de06a4c131f095415beaafa9772caa23c2ddfd70c56def0a3c5841b21488b7ff2c91d9f9898a + version: 2.4.2 + resolution: "bare-events@npm:2.4.2" + checksum: 10/c1006ad13b7e62a412466d4eac8466b4ceb46ce84a5e2fc164cd4b10edaaa5016adc684147134b67a6a3865aaf5aa007191647bdb5dbf859b1d5735d2a9ddf3b languageName: node linkType: hard "base-x@npm:^3.0.2": - version: 3.0.9 - resolution: "base-x@npm:3.0.9" + version: 3.0.10 + resolution: "base-x@npm:3.0.10" dependencies: safe-buffer: "npm:^5.0.1" - checksum: 10/957101d6fd09e1903e846fd8f69fd7e5e3e50254383e61ab667c725866bec54e5ece5ba49ce385128ae48f9ec93a26567d1d5ebb91f4d56ef4a9cc0d5a5481e8 + checksum: 10/52307739559e81d9980889de2359cb4f816cc0eb9a463028fa3ab239ab913d9044a1b47b4520f98e68453df32a457b8ba58b8d0ee7e757fc3fb971f3fa7a1482 languageName: node linkType: hard @@ -5993,15 +5681,15 @@ __metadata: languageName: node linkType: hard -"bin-links@npm:4.0.3": - version: 4.0.3 - resolution: "bin-links@npm:4.0.3" +"bin-links@npm:4.0.4": + version: 4.0.4 + resolution: "bin-links@npm:4.0.4" dependencies: cmd-shim: "npm:^6.0.0" npm-normalize-package-bin: "npm:^3.0.0" read-cmd-shim: "npm:^4.0.0" write-file-atomic: "npm:^5.0.0" - checksum: 10/8b4eec67e5d000768cc5a8cd4399d3af55eab059b2b6f864f96ad69bd73d8c4702a9f002a54747d11643cbd3e2a043d91f12bceedcfdcd96321cf186cfb33802 + checksum: 10/58d62143aacdbb783b076e9bdd970d8470f2750e1076d6fd1ae559fa532c4647478dd2550a911ba22d4c9e6339881451046e2fbc4b8958f4bf3bf8e5144d1e4d languageName: node linkType: hard @@ -6086,7 +5774,7 @@ __metadata: languageName: node linkType: hard -"braces@npm:^3.0.2, braces@npm:~3.0.2": +"braces@npm:^3.0.3, braces@npm:~3.0.2": version: 3.0.3 resolution: "braces@npm:3.0.3" dependencies: @@ -6132,17 +5820,17 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.22.2": - version: 4.23.0 - resolution: "browserslist@npm:4.23.0" +"browserslist@npm:^4.23.1": + version: 4.23.2 + resolution: "browserslist@npm:4.23.2" dependencies: - caniuse-lite: "npm:^1.0.30001587" - electron-to-chromium: "npm:^1.4.668" + caniuse-lite: "npm:^1.0.30001640" + electron-to-chromium: "npm:^1.4.820" node-releases: "npm:^2.0.14" - update-browserslist-db: "npm:^1.0.13" + update-browserslist-db: "npm:^1.1.0" bin: browserslist: cli.js - checksum: 10/496c3862df74565dd942b4ae65f502c575cbeba1fa4a3894dad7aa3b16130dc3033bc502d8848147f7b625154a284708253d9598bcdbef5a1e34cf11dc7bad8e + checksum: 10/326a98b1c39bcc9a99b197f15790dc28e122b1aead3257c837421899377ac96239123f26868698085b3d9be916d72540602738e1f857e86a387e810af3fda6e5 languageName: node linkType: hard @@ -6218,7 +5906,7 @@ __metadata: languageName: node linkType: hard -"builtins@npm:^5.0.0, builtins@npm:^5.0.1": +"builtins@npm:^5.0.1": version: 5.1.0 resolution: "builtins@npm:5.1.0" dependencies: @@ -6228,13 +5916,13 @@ __metadata: linkType: hard "bundle-require@npm:^4.0.0": - version: 4.0.2 - resolution: "bundle-require@npm:4.0.2" + version: 4.2.1 + resolution: "bundle-require@npm:4.2.1" dependencies: load-tsconfig: "npm:^0.2.3" peerDependencies: esbuild: ">=0.17" - checksum: 10/22178607249adb52cc76e409add67930b81cdc6507ed8cbd7b162dc2824ce53c51b669d01bf073e9b7cd9d98f10f3dbf9a3285345813085b856d437cdc97e162 + checksum: 10/e49cb6528373d4e086723bc37fb037e05e9cd529e1b3aa1c4da6c495c4725a0f74ae9cc461de35163d65dd3a6c41a0474c6e52b74b8ded4fe829c951d0784ec1 languageName: node linkType: hard @@ -6246,8 +5934,8 @@ __metadata: linkType: hard "cacache@npm:^18.0.0": - version: 18.0.2 - resolution: "cacache@npm:18.0.2" + version: 18.0.4 + resolution: "cacache@npm:18.0.4" dependencies: "@npmcli/fs": "npm:^3.1.0" fs-minipass: "npm:^3.0.0" @@ -6261,7 +5949,7 @@ __metadata: ssri: "npm:^10.0.0" tar: "npm:^6.1.11" unique-filename: "npm:^3.0.0" - checksum: 10/5ca58464f785d4d64ac2019fcad95451c8c89bea25949f63acd8987fcc3493eaef1beccc0fa39e673506d879d3fc1ab420760f8a14f8ddf46ea2d121805a5e96 + checksum: 10/ca2f7b2d3003f84d362da9580b5561058ccaecd46cba661cbcff0375c90734b610520d46b472a339fd032d91597ad6ed12dde8af81571197f3c9772b5d35b104 languageName: node linkType: hard @@ -6313,10 +6001,10 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.30001587": - version: 1.0.30001608 - resolution: "caniuse-lite@npm:1.0.30001608" - checksum: 10/302f278db93491b61de8f79047f0e05289be4a4993eed54b5c5c1e19ecf800339a17084ac6eae9051973f922cd0094e9b371d8b0551a34778c130041a3648513 +"caniuse-lite@npm:^1.0.30001640": + version: 1.0.30001643 + resolution: "caniuse-lite@npm:1.0.30001643" + checksum: 10/dddbda29fa24fbc435873309c71070461cbfc915d9bce3216180524c20c5637b2bee1a14b45972e9ac19e1fdf63fba3f63608b9e7d68de32f5ee1953c8c69e05 languageName: node linkType: hard @@ -6413,9 +6101,9 @@ __metadata: linkType: hard "cjs-module-lexer@npm:^1.0.0": - version: 1.2.3 - resolution: "cjs-module-lexer@npm:1.2.3" - checksum: 10/f96a5118b0a012627a2b1c13bd2fcb92509778422aaa825c5da72300d6dcadfb47134dd2e9d97dfa31acd674891dd91642742772d19a09a8adc3e56bd2f5928c + version: 1.3.1 + resolution: "cjs-module-lexer@npm:1.3.1" + checksum: 10/6629188d5ce74b57e5dce2222db851b5496a8d65b533a05957fb24089a3cec8d769378013c375a954c5a0f7522cde6a36d5a65bfd88f5575cb2de3176046fa8e languageName: node linkType: hard @@ -6486,9 +6174,9 @@ __metadata: linkType: hard "cmd-shim@npm:^6.0.0": - version: 6.0.2 - resolution: "cmd-shim@npm:6.0.2" - checksum: 10/2649e08b3c9a6ce93f61f86b3378a65bc5971a93b6962c9c076930323508aa8d153eb51cf00c0fad0dabac65d3f206d2f471497dae190e569f01ee1f48c189b7 + version: 6.0.3 + resolution: "cmd-shim@npm:6.0.3" + checksum: 10/791c9779cf57deae978ef24daf7e49e7fdb2070cc273aa7d691ed258a660ad3861edbc9f39daa2b6e5f72a64526b6812c04f08becc54402618b99946ccad7d71 languageName: node linkType: hard @@ -6500,9 +6188,9 @@ __metadata: linkType: hard "cockatiel@npm:^3.1.2": - version: 3.1.2 - resolution: "cockatiel@npm:3.1.2" - checksum: 10/52d8fd0038fc5fe5579962947243fe971fa827db5ad1acecec5dbde854d7018696d3c6967a4860d8399e39d0f53e9d762c00cad5e2b3694c554d19e7d08b3f6a + version: 3.2.0 + resolution: "cockatiel@npm:3.2.0" + checksum: 10/b60c694c564e73f0f84ce58cf30f83a3eb6c73ceef9add91616cd1ebbef95e9e541e52006c3adf7e22b3b8c854747de95613e722a7b01f1c6ca2b4e05085e9cb languageName: node linkType: hard @@ -6609,39 +6297,39 @@ __metadata: linkType: hard "contentful-resolve-response@npm:^1.8.1": - version: 1.8.1 - resolution: "contentful-resolve-response@npm:1.8.1" + version: 1.8.2 + resolution: "contentful-resolve-response@npm:1.8.2" dependencies: fast-copy: "npm:^2.1.7" - checksum: 10/6023824e98843d47c900501d2252336dd1dfebe8d868cb81b650252f5946963063cbe2b466e8e2238d41634a779dbfe4a43ba6c7996ca77124824c2554f66cab + checksum: 10/e8936ec7c1300109f94f5301e6476d9754de71b26d4cd46f2de82e13d2096d22ca74f84ef043baadbce41f4e898b448892d56b05f8dc8715ab0f4c7964bdc309 languageName: node linkType: hard "contentful-sdk-core@npm:^8.1.0": - version: 8.1.4 - resolution: "contentful-sdk-core@npm:8.1.4" + version: 8.3.1 + resolution: "contentful-sdk-core@npm:8.3.1" dependencies: fast-copy: "npm:^2.1.7" lodash.isplainobject: "npm:^4.0.6" lodash.isstring: "npm:^4.0.1" p-throttle: "npm:^4.1.1" qs: "npm:^6.11.2" - checksum: 10/187cc9875d5dcc8c1cce90caa19820c252b37ab65e6cd9f3e043a94f0287bb87961b173f5c4b1da00dfa0c13307bd471c9bca152e06ddc273aa20fbe50f57296 + checksum: 10/645d3a5d296d0e2a5ce87cceb04cf1ddf572183b5946cb1b3b717436bc7be96864216225fb845e61850d580436021c6284e7c95da0600a16c89c0af81a5f0d2c languageName: node linkType: hard "contentful@npm:^10.3.6": - version: 10.11.11 - resolution: "contentful@npm:10.11.11" + version: 10.12.13 + resolution: "contentful@npm:10.12.13" dependencies: - "@contentful/content-source-maps": "npm:^0.5.0" + "@contentful/content-source-maps": "npm:^0.6.0" "@contentful/rich-text-types": "npm:^16.0.2" axios: "npm:~1.6.8" contentful-resolve-response: "npm:^1.8.1" contentful-sdk-core: "npm:^8.1.0" json-stringify-safe: "npm:^5.0.1" type-fest: "npm:^4.0.0" - checksum: 10/6e6fc6c19f6376b7c6b2a941b1f6be1e262e6c2ff41a62a4fe49368a6be33c71e04a99d25a0fc3c390cc30aa0c9f4b932faf9ee276896a8af697df6e3f968bdd + checksum: 10/e4c601cbcecb2851c872bc8cc9805120af6775fe579d868621040f4ec19cf9aa380454584bb2055581b63be92564f3a00eecefd57c1f37fb91a368c10b9e2f3c languageName: node linkType: hard @@ -6819,14 +6507,14 @@ __metadata: linkType: hard "debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4": - version: 4.3.4 - resolution: "debug@npm:4.3.4" + version: 4.3.5 + resolution: "debug@npm:4.3.5" dependencies: ms: "npm:2.1.2" peerDependenciesMeta: supports-color: optional: true - checksum: 10/0073c3bcbd9cb7d71dd5f6b55be8701af42df3e56e911186dfa46fac3a5b9eb7ce7f377dd1d3be6db8977221f8eb333d945216f645cf56f6b688cd484837d255 + checksum: 10/cb6eab424c410e07813ca1392888589972ce9a32b8829c6508f5e1f25f3c3e70a76731610ae55b4bbe58d1a2fffa1424b30e97fa8d394e49cd2656a9643aedd2 languageName: node linkType: hard @@ -7067,10 +6755,10 @@ __metadata: languageName: node linkType: hard -"electron-to-chromium@npm:^1.4.668": - version: 1.4.733 - resolution: "electron-to-chromium@npm:1.4.733" - checksum: 10/5482f20ffc76172356d2cfb1a56ea0fd69d2d5b89c0536d3ea6746951b0d61cbdd41bd3029cfbf99d9739774faf16a86c3b046cc7715ecaf5c2be7e6d9e58d52 +"electron-to-chromium@npm:^1.4.820": + version: 1.4.832 + resolution: "electron-to-chromium@npm:1.4.832" + checksum: 10/795eaae1a445283dea93ffd6e2482a794405191bcf30710b0350808e79befdd1f14fc7aed13359cf68d90e24cad93bd7c0965d011293ae183326b54915dd0319 languageName: node linkType: hard @@ -7090,8 +6778,8 @@ __metadata: linkType: hard "elliptic@npm:^6.5.4": - version: 6.5.5 - resolution: "elliptic@npm:6.5.5" + version: 6.5.6 + resolution: "elliptic@npm:6.5.6" dependencies: bn.js: "npm:^4.11.9" brorand: "npm:^1.1.0" @@ -7100,7 +6788,7 @@ __metadata: inherits: "npm:^2.0.4" minimalistic-assert: "npm:^1.0.1" minimalistic-crypto-utils: "npm:^1.0.1" - checksum: 10/5444b4f18e0c0fdfa14de26f69f7dbc44c78a211e91825823d698dcc91071ef1a3954d87730f364183fc83b0a86d8affed864e347da2e549bdcead3b46de126f + checksum: 10/09377ec924fdb37775d63e5d7e5ebb2845842e6f08880b68265b1108863e968970c4a4e1c43df622078c8262417deec9a04aeb9d34e8d09a9693e19b5454e1df languageName: node linkType: hard @@ -7364,7 +7052,7 @@ __metadata: languageName: node linkType: hard -"escalade@npm:^3.1.1": +"escalade@npm:^3.1.1, escalade@npm:^3.1.2": version: 3.1.2 resolution: "escalade@npm:3.1.2" checksum: 10/a1e07fea2f15663c30e40b9193d658397846ffe28ce0a3e4da0d8e485fedfeca228ab846aee101a05015829adf39f9934ff45b2a3fca47bed37a29646bd05cd3 @@ -7606,11 +7294,11 @@ __metadata: linkType: hard "eslint-plugin-promise@npm:^6.1.1": - version: 6.1.1 - resolution: "eslint-plugin-promise@npm:6.1.1" + version: 6.6.0 + resolution: "eslint-plugin-promise@npm:6.6.0" peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - checksum: 10/216c4348f796c5e90984224532d42a8f8d0455b8cbb1955bcb328b3aa10a52e9718f6fb044b6fe19825eda3a2d62a32b1042d9cbb10731353cf61b7a6cab2d71 + eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 + checksum: 10/c2b5604efd7e1390c132fcbf06cb2f072c956ffa65c14a991cb74ba1e2327357797239cb5b9b292d5e4010301bb897bd85a6273d7873fb157edc46aa2d95cbd9 languageName: node linkType: hard @@ -7745,11 +7433,11 @@ __metadata: linkType: hard "esquery@npm:^1.4.0, esquery@npm:^1.4.2": - version: 1.5.0 - resolution: "esquery@npm:1.5.0" + version: 1.6.0 + resolution: "esquery@npm:1.6.0" dependencies: estraverse: "npm:^5.1.0" - checksum: 10/e65fcdfc1e0ff5effbf50fb4f31ea20143ae5df92bb2e4953653d8d40aa4bc148e0d06117a592ce4ea53eeab1dafdfded7ea7e22a5be87e82d73757329a1b01d + checksum: 10/c587fb8ec9ed83f2b1bc97cf2f6854cc30bf784a79d62ba08c6e358bf22280d69aee12827521cf38e69ae9761d23fb7fde593ce315610f85655c139d99b05e5a languageName: node linkType: hard @@ -7845,14 +7533,14 @@ __metadata: linkType: hard "ethereum-cryptography@npm:^2.0.0, ethereum-cryptography@npm:^2.1.2": - version: 2.1.3 - resolution: "ethereum-cryptography@npm:2.1.3" + version: 2.2.1 + resolution: "ethereum-cryptography@npm:2.2.1" dependencies: - "@noble/curves": "npm:1.3.0" - "@noble/hashes": "npm:1.3.3" - "@scure/bip32": "npm:1.3.3" - "@scure/bip39": "npm:1.2.2" - checksum: 10/cc5aa9a4368dc1dd7680ba921957c098ced7b3d7dbb1666334013ab2f8d4cd25a785ad84e66fd9f5c5a9b6de337930ea24ff8c722938f36a9c00cec597ca16b5 + "@noble/curves": "npm:1.4.2" + "@noble/hashes": "npm:1.4.0" + "@scure/bip32": "npm:1.4.0" + "@scure/bip39": "npm:1.3.0" + checksum: 10/ab123bbfe843500ac2d645ce9edc4bc814962ffb598db6bf8bf01fbecac656e6c81ff4cf2472f1734844bbcbad2bf658d8b699cb7248d768e0f06ae13ecf43b8 languageName: node linkType: hard @@ -7886,8 +7574,8 @@ __metadata: linkType: hard "ethers@npm:^6.12.0": - version: 6.13.0 - resolution: "ethers@npm:6.13.0" + version: 6.13.1 + resolution: "ethers@npm:6.13.1" dependencies: "@adraffy/ens-normalize": "npm:1.10.1" "@noble/curves": "npm:1.2.0" @@ -7895,8 +7583,8 @@ __metadata: "@types/node": "npm:18.15.13" aes-js: "npm:4.0.0-beta.5" tslib: "npm:2.4.0" - ws: "npm:8.5.0" - checksum: 10/390918da6955d6b9fcb9a57198ba4864df677e08fc478fbb7a74a02fd5ce0ca1b647782e0ae94c4cf056a205ebb86c64751a3712ffbc426fd53f11167f8cd4fe + ws: "npm:8.17.1" + checksum: 10/efc3e8d4d13101cad01823ba524dad797a23f60088ca9f8677bd6dbfad5087e4187ede121e43aa0758d704525976f935860c5d5d27183a4247deaccf7cf19950 languageName: node linkType: hard @@ -8067,7 +7755,7 @@ __metadata: languageName: node linkType: hard -"fast-fifo@npm:^1.1.0, fast-fifo@npm:^1.2.0": +"fast-fifo@npm:^1.2.0, fast-fifo@npm:^1.3.2": version: 1.3.2 resolution: "fast-fifo@npm:1.3.2" checksum: 10/6bfcba3e4df5af7be3332703b69a7898a8ed7020837ec4395bb341bd96cc3a6d86c3f6071dd98da289618cf2234c70d84b2a6f09a33dd6f988b1ff60d8e54275 @@ -8115,14 +7803,21 @@ __metadata: languageName: node linkType: hard +"fast-uri@npm:^3.0.1": + version: 3.0.1 + resolution: "fast-uri@npm:3.0.1" + checksum: 10/e8ee4712270de0d29eb0fbf41ffad0ac80952e8797be760e8bb62c4707f08f50a86fe2d7829681ca133c07d6eb4b4a75389a5fc36674c5b254a3ac0891a68fc7 + languageName: node + linkType: hard + "fast-xml-parser@npm:^4.3.4": - version: 4.3.6 - resolution: "fast-xml-parser@npm:4.3.6" + version: 4.4.0 + resolution: "fast-xml-parser@npm:4.4.0" dependencies: strnum: "npm:^1.0.5" bin: fxparser: src/cli/cli.js - checksum: 10/3e431e594960f04996e60a01fb51d8f4346138a7ba60d97244bf7866a3072eaf2f6dc73008d7b07871b98b606a8d7db955efdeae787992f685dd0e5bcc67c36a + checksum: 10/f1592fa810d3923c46c7037d5adf1c309580d1d14780312019f33e4967f6ffcb5632168a5e889fe9d30794f6a087a0a095bb21cdf62320a96c6b304395212658 languageName: node linkType: hard @@ -8231,37 +7926,37 @@ __metadata: linkType: hard "firebase@npm:^10.11.0": - version: 10.12.2 - resolution: "firebase@npm:10.12.2" - dependencies: - "@firebase/analytics": "npm:0.10.4" - "@firebase/analytics-compat": "npm:0.2.10" - "@firebase/app": "npm:0.10.5" - "@firebase/app-check": "npm:0.8.4" - "@firebase/app-check-compat": "npm:0.3.11" - "@firebase/app-compat": "npm:0.2.35" + version: 10.12.4 + resolution: "firebase@npm:10.12.4" + dependencies: + "@firebase/analytics": "npm:0.10.6" + "@firebase/analytics-compat": "npm:0.2.12" + "@firebase/app": "npm:0.10.7" + "@firebase/app-check": "npm:0.8.6" + "@firebase/app-check-compat": "npm:0.3.13" + "@firebase/app-compat": "npm:0.2.37" "@firebase/app-types": "npm:0.9.2" - "@firebase/auth": "npm:1.7.4" - "@firebase/auth-compat": "npm:0.5.9" - "@firebase/database": "npm:1.0.5" - "@firebase/database-compat": "npm:1.0.5" - "@firebase/firestore": "npm:4.6.3" - "@firebase/firestore-compat": "npm:0.3.32" - "@firebase/functions": "npm:0.11.5" - "@firebase/functions-compat": "npm:0.3.11" - "@firebase/installations": "npm:0.6.7" - "@firebase/installations-compat": "npm:0.2.7" - "@firebase/messaging": "npm:0.12.9" - "@firebase/messaging-compat": "npm:0.2.9" - "@firebase/performance": "npm:0.6.7" - "@firebase/performance-compat": "npm:0.2.7" - "@firebase/remote-config": "npm:0.4.7" - "@firebase/remote-config-compat": "npm:0.2.7" - "@firebase/storage": "npm:0.12.5" - "@firebase/storage-compat": "npm:0.3.8" - "@firebase/util": "npm:1.9.6" - "@firebase/vertexai-preview": "npm:0.0.2" - checksum: 10/427d404993a86832ee528a9bb68c12c1f1e81b07b6d61fd721351e64c8ba3025a91d4464361cff9160ca0ef994b2f9d409ecbe0f685430fbe4ce2606822e96d6 + "@firebase/auth": "npm:1.7.5" + "@firebase/auth-compat": "npm:0.5.10" + "@firebase/database": "npm:1.0.6" + "@firebase/database-compat": "npm:1.0.6" + "@firebase/firestore": "npm:4.6.4" + "@firebase/firestore-compat": "npm:0.3.33" + "@firebase/functions": "npm:0.11.6" + "@firebase/functions-compat": "npm:0.3.12" + "@firebase/installations": "npm:0.6.8" + "@firebase/installations-compat": "npm:0.2.8" + "@firebase/messaging": "npm:0.12.10" + "@firebase/messaging-compat": "npm:0.2.10" + "@firebase/performance": "npm:0.6.8" + "@firebase/performance-compat": "npm:0.2.8" + "@firebase/remote-config": "npm:0.4.8" + "@firebase/remote-config-compat": "npm:0.2.8" + "@firebase/storage": "npm:0.12.6" + "@firebase/storage-compat": "npm:0.3.9" + "@firebase/util": "npm:1.9.7" + "@firebase/vertexai-preview": "npm:0.0.3" + checksum: 10/4a8b380b858ef6d546547c3c2764af070aa2262231a55951c71c307c9d2c70569e4f3d1e0cb3f0212f08b805989f0eb32b5cb70516b281583a59e19b3e7976aa languageName: node linkType: hard @@ -8310,12 +8005,12 @@ __metadata: linkType: hard "foreground-child@npm:^3.1.0": - version: 3.1.1 - resolution: "foreground-child@npm:3.1.1" + version: 3.2.1 + resolution: "foreground-child@npm:3.2.1" dependencies: cross-spawn: "npm:^7.0.0" signal-exit: "npm:^4.0.1" - checksum: 10/087edd44857d258c4f73ad84cb8df980826569656f2550c341b27adf5335354393eec24ea2fabd43a253233fb27cee177ebe46bd0b7ea129c77e87cb1e9936fb + checksum: 10/77b33b3c438a499201727ca84de39a66350ccd54a8805df712773e963cefb5c4632dbc4386109e97a0df8fb1585aee95fa35acb07587e3e04cfacabfc0ae15dc languageName: node linkType: hard @@ -8589,11 +8284,12 @@ __metadata: linkType: hard "globalthis@npm:^1.0.3": - version: 1.0.3 - resolution: "globalthis@npm:1.0.3" + version: 1.0.4 + resolution: "globalthis@npm:1.0.4" dependencies: - define-properties: "npm:^1.1.3" - checksum: 10/45ae2f3b40a186600d0368f2a880ae257e8278b4c7704f0417d6024105ad7f7a393661c5c2fa1334669cd485ea44bc883a08fdd4516df2428aec40c99f52aa89 + define-properties: "npm:^1.2.1" + gopd: "npm:^1.0.1" + checksum: 10/1f1fd078fb2f7296306ef9dd51019491044ccf17a59ed49d375b576ca108ff37e47f3d29aead7add40763574a992f16a5367dd1e2173b8634ef18556ab719ac4 languageName: node linkType: hard @@ -8770,11 +8466,11 @@ __metadata: linkType: hard "hosted-git-info@npm:^7.0.0": - version: 7.0.1 - resolution: "hosted-git-info@npm:7.0.1" + version: 7.0.2 + resolution: "hosted-git-info@npm:7.0.2" dependencies: lru-cache: "npm:^10.0.1" - checksum: 10/5f740ecf3c70838e27446ff433a9a9a583de8747f7b661390b373ad12ca47edb937136e79999a4f953d0953079025a11df173f1fd9f7d52b0277b2fb9433e1c7 + checksum: 10/8f085df8a4a637d995f357f48b1e3f6fc1f9f92e82b33fb406415b5741834ed431a510a09141071001e8deea2eee43ce72786463e2aa5e5a70db8648c0eedeab languageName: node linkType: hard @@ -8840,12 +8536,12 @@ __metadata: linkType: hard "https-proxy-agent@npm:^7.0.1": - version: 7.0.4 - resolution: "https-proxy-agent@npm:7.0.4" + version: 7.0.5 + resolution: "https-proxy-agent@npm:7.0.5" dependencies: agent-base: "npm:^7.0.2" debug: "npm:4" - checksum: 10/405fe582bba461bfe5c7e2f8d752b384036854488b828ae6df6a587c654299cbb2c50df38c4b6ab303502c3c5e029a793fbaac965d1e86ee0be03faceb554d63 + checksum: 10/6679d46159ab3f9a5509ee80c3a3fc83fba3a920a5e18d32176c3327852c3c00ad640c0c4210a8fd70ea3c4a6d3a1b375bf01942516e7df80e2646bdc77658ab languageName: node linkType: hard @@ -8929,14 +8625,14 @@ __metadata: linkType: hard "import-local@npm:^3.0.2": - version: 3.1.0 - resolution: "import-local@npm:3.1.0" + version: 3.2.0 + resolution: "import-local@npm:3.2.0" dependencies: pkg-dir: "npm:^4.2.0" resolve-cwd: "npm:^3.0.0" bin: import-local-fixture: fixtures/cli.js - checksum: 10/bfcdb63b5e3c0e245e347f3107564035b128a414c4da1172a20dc67db2504e05ede4ac2eee1252359f78b0bfd7b19ef180aec427c2fce6493ae782d73a04cddd + checksum: 10/0b0b0b412b2521739fbb85eeed834a3c34de9bc67e670b3d0b86248fc460d990a7b116ad056c084b87a693ef73d1f17268d6a5be626bb43c998a8b1c8a230004 languageName: node linkType: hard @@ -8985,6 +8681,13 @@ __metadata: languageName: node linkType: hard +"ini@npm:^4.1.3": + version: 4.1.3 + resolution: "ini@npm:4.1.3" + checksum: 10/f536b414d1442e5b233429e2b56efcdb354109b2d65ddd489e5939d8f0f5ad23c88aa2b19c92987249d0dd63ba8192e9aeb1a02b0459549c5a9ff31acd729a5d + languageName: node + linkType: hard + "internal-slot@npm:^1.0.7": version: 1.0.7 resolution: "internal-slot@npm:1.0.7" @@ -9080,11 +8783,11 @@ __metadata: linkType: hard "is-core-module@npm:^2.11.0, is-core-module@npm:^2.12.0, is-core-module@npm:^2.13.0, is-core-module@npm:^2.8.1": - version: 2.13.1 - resolution: "is-core-module@npm:2.13.1" + version: 2.15.0 + resolution: "is-core-module@npm:2.15.0" dependencies: - hasown: "npm:^2.0.0" - checksum: 10/d53bd0cc24b0a0351fb4b206ee3908f71b9bbf1c47e9c9e14e5f06d292af1663704d2abd7e67700d6487b2b7864e0d0f6f10a1edf1892864bdffcb197d1845a2 + hasown: "npm:^2.0.2" + checksum: 10/70e962543e5d3a97c07cb29144a86792d545a21f28e67da5401d85878a0193d46fbab8d97bc3ca680e2778705dca66e7b6ca840c493497a27ca0e8c5f3ac3d1d languageName: node linkType: hard @@ -10177,9 +9880,9 @@ __metadata: linkType: hard "json-parse-even-better-errors@npm:^3.0.0": - version: 3.0.1 - resolution: "json-parse-even-better-errors@npm:3.0.1" - checksum: 10/bf74fa3f715e56699ccd68b80a7d20908de432a3fae2d5aa2ed530a148e9d9ccdf8e6983b93d9966a553aa70dcf003ce3a7ffec2c0ce74d2a6173e3691a426f0 + version: 3.0.2 + resolution: "json-parse-even-better-errors@npm:3.0.2" + checksum: 10/6f04ea6c9ccb783630a59297959247e921cc90b917b8351197ca7fd058fccc7079268fd9362be21ba876fc26aa5039369dd0a2280aae49aae425784794a94927 languageName: node linkType: hard @@ -10248,9 +9951,9 @@ __metadata: linkType: hard "jsonc-parser@npm:^3.2.0": - version: 3.2.1 - resolution: "jsonc-parser@npm:3.2.1" - checksum: 10/fe2df6f39e21653781d52cae20c5b9e0ab62461918d97f9430b216cea9b6500efc1d8b42c6584cc0a7548b4c996055e9cdc39f09b9782fa6957af2f45306c530 + version: 3.3.1 + resolution: "jsonc-parser@npm:3.3.1" + checksum: 10/9b0dc391f20b47378f843ef1e877e73ec652a5bdc3c5fa1f36af0f119a55091d147a86c1ee86a232296f55c929bba174538c2bf0312610e0817a22de131cc3f4 languageName: node linkType: hard @@ -10321,9 +10024,9 @@ __metadata: linkType: hard "lilconfig@npm:^3.0.0": - version: 3.1.1 - resolution: "lilconfig@npm:3.1.1" - checksum: 10/c80fbf98ae7d1daf435e16a83fe3c63743b9d92804cac6dc53ee081c7c265663645c3162d8a0d04ff1874f9c07df145519743317dee67843234c6ed279300f83 + version: 3.1.2 + resolution: "lilconfig@npm:3.1.2" + checksum: 10/8058403850cfad76d6041b23db23f730e52b6c17a8c28d87b90766639ca0ee40c748a3e85c2d7bd133d572efabff166c4b015e5d25e01fd666cb4b13cfada7f0 languageName: node linkType: hard @@ -10456,9 +10159,9 @@ __metadata: linkType: hard "lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0": - version: 10.2.0 - resolution: "lru-cache@npm:10.2.0" - checksum: 10/502ec42c3309c0eae1ce41afca471f831c278566d45a5273a0c51102dee31e0e250a62fa9029c3370988df33a14188a38e682c16143b794de78668de3643e302 + version: 10.4.3 + resolution: "lru-cache@npm:10.4.3" + checksum: 10/e6e90267360476720fa8e83cc168aa2bf0311f3f2eea20a6ba78b90a885ae72071d9db132f40fda4129c803e7dcec3a6b6a6fbb44ca90b081630b810b5d6a41a languageName: node linkType: hard @@ -10485,7 +10188,7 @@ __metadata: languageName: node linkType: hard -"magic-string@npm:^0.30.7": +"magic-string@npm:^0.30.10": version: 0.30.10 resolution: "magic-string@npm:0.30.10" dependencies: @@ -10511,8 +10214,8 @@ __metadata: linkType: hard "make-fetch-happen@npm:^13.0.0": - version: 13.0.0 - resolution: "make-fetch-happen@npm:13.0.0" + version: 13.0.1 + resolution: "make-fetch-happen@npm:13.0.1" dependencies: "@npmcli/agent": "npm:^2.0.0" cacache: "npm:^18.0.0" @@ -10523,9 +10226,10 @@ __metadata: minipass-flush: "npm:^1.0.5" minipass-pipeline: "npm:^1.2.4" negotiator: "npm:^0.6.3" + proc-log: "npm:^4.2.0" promise-retry: "npm:^2.0.1" ssri: "npm:^10.0.0" - checksum: 10/ded5a91a02b76381b06a4ec4d5c1d23ebbde15d402b3c3e4533b371dac7e2f7ca071ae71ae6dae72aa261182557b7b1b3fd3a705b39252dc17f74fa509d3e76f + checksum: 10/11bae5ad6ac59b654dbd854f30782f9de052186c429dfce308eda42374528185a100ee40ac9ffdc36a2b6c821ecaba43913e4730a12f06f15e895ea9cb23fa59 languageName: node linkType: hard @@ -10539,11 +10243,11 @@ __metadata: linkType: hard "marked@npm:^12.0.1": - version: 12.0.1 - resolution: "marked@npm:12.0.1" + version: 12.0.2 + resolution: "marked@npm:12.0.2" bin: marked: bin/marked.js - checksum: 10/34fd0044ebeda28b3f3f94f340e2388666408315557f125d561b59b49baec4c6e6777f54b6fb12aa5c2bf3b75a4aa9f1809679bfb6502da73053d0461c1a232d + checksum: 10/24d4fc58d37c1779197fa7f93c504d8c71d4df54eb69cbbc14a55ba2a8e2ad83d723801fc25452c21ce74b38a483c5863c53449f130253a597be9e9c1d3e7e2b languageName: node linkType: hard @@ -10589,12 +10293,12 @@ __metadata: linkType: hard "micromatch@npm:^4.0.2, micromatch@npm:^4.0.4": - version: 4.0.5 - resolution: "micromatch@npm:4.0.5" + version: 4.0.7 + resolution: "micromatch@npm:4.0.7" dependencies: - braces: "npm:^3.0.2" + braces: "npm:^3.0.3" picomatch: "npm:^2.3.1" - checksum: 10/a749888789fc15cac0e03273844dbd749f9f8e8d64e70c564bcf06a033129554c789bb9e30d7566d7ff6596611a08e58ac12cf2a05f6e3c9c47c50c4c7e12fa2 + checksum: 10/a11ed1cb67dcbbe9a5fc02c4062cf8bb0157d73bf86956003af8dcfdf9b287f9e15ec0f6d6925ff6b8b5b496202335e497b01de4d95ef6cf06411bc5e5c474a0 languageName: node linkType: hard @@ -10686,8 +10390,8 @@ __metadata: linkType: hard "minipass-fetch@npm:^3.0.0": - version: 3.0.4 - resolution: "minipass-fetch@npm:3.0.4" + version: 3.0.5 + resolution: "minipass-fetch@npm:3.0.5" dependencies: encoding: "npm:^0.1.13" minipass: "npm:^7.0.3" @@ -10696,7 +10400,7 @@ __metadata: dependenciesMeta: encoding: optional: true - checksum: 10/3edf72b900e30598567eafe96c30374432a8709e61bb06b87198fa3192d466777e2ec21c52985a0999044fa6567bd6f04651585983a1cbb27e2c1770a07ed2a2 + checksum: 10/c669948bec1373313aaa8f104b962a3ced9f45c49b26366a4b0ae27ccdfa9c5740d72c8a84d3f8623d7a61c5fc7afdfda44789008c078f61a62441142efc4a97 languageName: node linkType: hard @@ -10906,19 +10610,19 @@ __metadata: linkType: hard "node-gyp-build@npm:^4.2.0": - version: 4.8.0 - resolution: "node-gyp-build@npm:4.8.0" + version: 4.8.1 + resolution: "node-gyp-build@npm:4.8.1" bin: node-gyp-build: bin.js node-gyp-build-optional: optional.js node-gyp-build-test: build-test.js - checksum: 10/80f410ab412df38e84171d3634a5716b6c6f14ecfa4eb971424d289381fb76f8bcbe1b666419ceb2c81060e558fd7c6d70cc0f60832bcca6a1559098925d9657 + checksum: 10/b9297770f96a92e5f2b854f3fd5e4bd418df81d7785a81ab60cec5cf2e5e72dc2c3319808978adc572cfa3885e6b12338cb5f4034bed2cab35f0d76a4b75ccdf languageName: node linkType: hard "node-gyp@npm:^10.0.0, node-gyp@npm:latest": - version: 10.1.0 - resolution: "node-gyp@npm:10.1.0" + version: 10.2.0 + resolution: "node-gyp@npm:10.2.0" dependencies: env-paths: "npm:^2.2.0" exponential-backoff: "npm:^3.1.1" @@ -10926,13 +10630,13 @@ __metadata: graceful-fs: "npm:^4.2.6" make-fetch-happen: "npm:^13.0.0" nopt: "npm:^7.0.0" - proc-log: "npm:^3.0.0" + proc-log: "npm:^4.1.0" semver: "npm:^7.3.5" - tar: "npm:^6.1.2" + tar: "npm:^6.2.1" which: "npm:^4.0.0" bin: node-gyp: bin/node-gyp.js - checksum: 10/89e105e495e66cd4568af3cf79cdeb67d670eb069e33163c7781d3366470a30367c9bd8dea59e46db16370020139e5bf78b1fbc03284cb571754dfaa59744db5 + checksum: 10/41773093b1275751dec942b985982fd4e7a69b88cae719b868babcef3880ee6168aaec8dcaa8cd0b9fa7c84873e36cc549c6cac6a124ee65ba4ce1f1cc108cfe languageName: node linkType: hard @@ -10944,32 +10648,31 @@ __metadata: linkType: hard "node-releases@npm:^2.0.14": - version: 2.0.14 - resolution: "node-releases@npm:2.0.14" - checksum: 10/0f7607ec7db5ef1dc616899a5f24ae90c869b6a54c2d4f36ff6d84a282ab9343c7ff3ca3670fe4669171bb1e8a9b3e286e1ef1c131f09a83d70554f855d54f24 + version: 2.0.18 + resolution: "node-releases@npm:2.0.18" + checksum: 10/241e5fa9556f1c12bafb83c6c3e94f8cf3d8f2f8f904906ecef6e10bcaa1d59aa61212d4651bec70052015fc54bd3fdcdbe7fc0f638a17e6685aa586c076ec4e languageName: node linkType: hard "nopt@npm:^7.0.0": - version: 7.2.0 - resolution: "nopt@npm:7.2.0" + version: 7.2.1 + resolution: "nopt@npm:7.2.1" dependencies: abbrev: "npm:^2.0.0" bin: nopt: bin/nopt.js - checksum: 10/1e7489f17cbda452c8acaf596a8defb4ae477d2a9953b76eb96f4ec3f62c6b421cd5174eaa742f88279871fde9586d8a1d38fb3f53fa0c405585453be31dff4c + checksum: 10/95a1f6dec8a81cd18cdc2fed93e6f0b4e02cf6bdb4501c848752c6e34f9883d9942f036a5e3b21a699047d8a448562d891e67492df68ec9c373e6198133337ae languageName: node linkType: hard "normalize-package-data@npm:^6.0.0": - version: 6.0.0 - resolution: "normalize-package-data@npm:6.0.0" + version: 6.0.2 + resolution: "normalize-package-data@npm:6.0.2" dependencies: hosted-git-info: "npm:^7.0.0" - is-core-module: "npm:^2.8.1" semver: "npm:^7.3.5" validate-npm-package-license: "npm:^3.0.4" - checksum: 10/e31e31a2ebaef93ef107feb9408f105044eeae9cb7d0d4619544ab2323cd4b15ca648b0d558ac29db2fece161c7b8658206bb27ebe9340df723f7174b3e2759d + checksum: 10/7c4216a2426aa76c0197f8372f06b23a0484d62b3518fb5c0f6ebccb16376bdfab29ceba96f95c75f60506473198f1337fe337b945c8df0541fe32b8049ab4c9 languageName: node linkType: hard @@ -10997,26 +10700,26 @@ __metadata: linkType: hard "npm-package-arg@npm:^11.0.0": - version: 11.0.1 - resolution: "npm-package-arg@npm:11.0.1" + version: 11.0.2 + resolution: "npm-package-arg@npm:11.0.2" dependencies: hosted-git-info: "npm:^7.0.0" - proc-log: "npm:^3.0.0" + proc-log: "npm:^4.0.0" semver: "npm:^7.3.5" validate-npm-package-name: "npm:^5.0.0" - checksum: 10/a16e632703e106b3e9a6b4902d14a3493c8371745bcf8ba8f4ea9f152e12d5ed927487931e9adf817d05ba97b04941b33fec1d140dbd7da09181b546fde35b3c + checksum: 10/ce4c51900a73aadb408c9830c38a61b1930e1ab08509ec5ebbcf625ad14326ee33b014df289c942039bd28071ab17e813368f68d26a4ccad0eb6e9928f8ad03c languageName: node linkType: hard "npm-pick-manifest@npm:^9.0.0": - version: 9.0.0 - resolution: "npm-pick-manifest@npm:9.0.0" + version: 9.1.0 + resolution: "npm-pick-manifest@npm:9.1.0" dependencies: npm-install-checks: "npm:^6.0.0" npm-normalize-package-bin: "npm:^3.0.0" npm-package-arg: "npm:^11.0.0" semver: "npm:^7.3.5" - checksum: 10/29dca2a838ed35c714df1a76f76616df2df51ce31bc3ca5943a0668b2eca2a5aab448f9f89cadf7a77eb5e3831c554cebaf7802f3e432838acb34c1a74fa2786 + checksum: 10/e759e4fe4076da9169cf522964a80bbc096d50cd24c8c44b50b44706c4479bd9d9d018fbdb76c6ea0c6037e012e07c6c917a1ecaa7ae1a1169cddfae1c0f24b6 languageName: node linkType: hard @@ -11049,9 +10752,9 @@ __metadata: linkType: hard "nwsapi@npm:^2.2.0": - version: 2.2.7 - resolution: "nwsapi@npm:2.2.7" - checksum: 10/22c002080f0297121ad138aba5a6509e724774d6701fe2c4777627bd939064ecd9e1b6dc1c2c716bb7ca0b9f16247892ff2f664285202ac7eff6ec9543725320 + version: 2.2.12 + resolution: "nwsapi@npm:2.2.12" + checksum: 10/172119e9ef492467ebfb337f9b5fd12a94d2b519377cde3f6ec2f74a86f6d5c00ef3873539bed7142f908ffca4e35383179be2319d04a563071d146bfa3f1673 languageName: node linkType: hard @@ -11063,9 +10766,9 @@ __metadata: linkType: hard "object-inspect@npm:^1.13.1": - version: 1.13.1 - resolution: "object-inspect@npm:1.13.1" - checksum: 10/92f4989ed83422d56431bc39656d4c780348eb15d397ce352ade6b7fec08f973b53744bd41b94af021901e61acaf78fcc19e65bf464ecc0df958586a672700f0 + version: 1.13.2 + resolution: "object-inspect@npm:1.13.2" + checksum: 10/7ef65583b6397570a17c56f0c1841e0920e83900f2c94638927abb7b81ac08a19c7aae135bd9dcca96208cac0c7332b4650fb927f027b0cf92d71df2990d0561 languageName: node linkType: hard @@ -11137,16 +10840,16 @@ __metadata: linkType: hard "optionator@npm:^0.9.3": - version: 0.9.3 - resolution: "optionator@npm:0.9.3" + version: 0.9.4 + resolution: "optionator@npm:0.9.4" dependencies: - "@aashutoshrathi/word-wrap": "npm:^1.2.3" deep-is: "npm:^0.1.3" fast-levenshtein: "npm:^2.0.6" levn: "npm:^0.4.1" prelude-ls: "npm:^1.2.1" type-check: "npm:^0.4.0" - checksum: 10/fa28d3016395974f7fc087d6bbf0ac7f58ac3489f4f202a377e9c194969f329a7b88c75f8152b33fb08794a30dcd5c079db6bb465c28151357f113d80bbf67da + word-wrap: "npm:^1.2.5" + checksum: 10/a8398559c60aef88d7f353a4f98dcdff6090a4e70f874c827302bf1213d9106a1c4d5fcb68dacb1feb3c30a04c4102f41047aa55d4c576b863d6fc876e001af6 languageName: node linkType: hard @@ -11374,7 +11077,7 @@ __metadata: languageName: node linkType: hard -"picocolors@npm:^1.0.0": +"picocolors@npm:^1.0.0, picocolors@npm:^1.0.1": version: 1.0.1 resolution: "picocolors@npm:1.0.1" checksum: 10/fa68166d1f56009fc02a34cdfd112b0dd3cf1ef57667ac57281f714065558c01828cdf4f18600ad6851cbe0093952ed0660b1e0156bddf2184b6aaf5817553a5 @@ -11430,9 +11133,9 @@ __metadata: linkType: hard "pony-cause@npm:^2.1.10, pony-cause@npm:^2.1.9": - version: 2.1.10 - resolution: "pony-cause@npm:2.1.10" - checksum: 10/906563565030996d0c40ba79a584e2f298391931acc59c98510f9fd583d72cd9e9c58b0fb5a25bbae19daf16840f94cb9c1ee72c7ed5ef249ecba147cee40495 + version: 2.1.11 + resolution: "pony-cause@npm:2.1.11" + checksum: 10/ed7d0bb6e3e69f753080bf736b71f40e6ae4c13ec0c8c473ff73345345c088819966fdd68a62ad7482d464bf41176cf9421f5f63715d1a4532005eedc099db55 languageName: node linkType: hard @@ -11461,14 +11164,14 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.4.35": - version: 8.4.38 - resolution: "postcss@npm:8.4.38" +"postcss@npm:^8.4.39": + version: 8.4.39 + resolution: "postcss@npm:8.4.39" dependencies: nanoid: "npm:^3.3.7" - picocolors: "npm:^1.0.0" + picocolors: "npm:^1.0.1" source-map-js: "npm:^1.2.0" - checksum: 10/6e44a7ed835ffa9a2b096e8d3e5dfc6bcf331a25c48aeb862dd54e3aaecadf814fa22be224fd308f87d08adf2299164f88c5fd5ab1c4ef6cbd693ceb295377f4 + checksum: 10/ad9c1add892c96433b9a5502878201ede4a20c4ce02d056251f61f8d9a3e5426dab3683fe5a086edfa78a1a19f2b4988c8cea02c5122136d29758cb5a17e2621 languageName: node linkType: hard @@ -11489,17 +11192,17 @@ __metadata: linkType: hard "prettier-plugin-packagejson@npm:^2.4.5": - version: 2.5.0 - resolution: "prettier-plugin-packagejson@npm:2.5.0" + version: 2.5.1 + resolution: "prettier-plugin-packagejson@npm:2.5.1" dependencies: sort-package-json: "npm:2.10.0" - synckit: "npm:0.9.0" + synckit: "npm:0.9.1" peerDependencies: prettier: ">= 1.16.0" peerDependenciesMeta: prettier: optional: true - checksum: 10/0b05b02e96173abc1220d11a5ae6fbdefd45823ad86e5aba70bd52377f555db82fbe41c67bdfb186fa3f4c2ef5d12d4803b7215cb301533829a8389b411bb99a + checksum: 10/ddf202a1b758ff419611cd4fffd13f61956592dde60075ea233d33827a25611bee22f5c698921afecad6bca701c441af8404a8e8edbd41432509270ba2c37558 languageName: node linkType: hard @@ -11534,10 +11237,10 @@ __metadata: languageName: node linkType: hard -"proc-log@npm:^3.0.0": - version: 3.0.0 - resolution: "proc-log@npm:3.0.0" - checksum: 10/02b64e1b3919e63df06f836b98d3af002b5cd92655cab18b5746e37374bfb73e03b84fe305454614b34c25b485cc687a9eebdccf0242cda8fda2475dd2c97e02 +"proc-log@npm:^4.0.0, proc-log@npm:^4.1.0, proc-log@npm:^4.2.0": + version: 4.2.0 + resolution: "proc-log@npm:4.2.0" + checksum: 10/4e1394491b717f6c1ade15c570ecd4c2b681698474d3ae2d303c1e4b6ab9455bd5a81566211e82890d5a5ae9859718cc6954d5150bb18b09b72ecb297beae90a languageName: node linkType: hard @@ -11641,11 +11344,11 @@ __metadata: linkType: hard "qs@npm:^6.11.2": - version: 6.12.1 - resolution: "qs@npm:6.12.1" + version: 6.12.3 + resolution: "qs@npm:6.12.3" dependencies: side-channel: "npm:^1.0.6" - checksum: 10/035bcad2a1ab0175bac7a74c904c15913bdac252834149ccff988c93a51de02642fe7be10e43058ba4dc4094bb28ce9b59d12b9e91d40997f445cfde3ecc1c29 + checksum: 10/486d80cfa5e12886de6fe15a5aa2b3c7023bf4461f949a742022c3ae608499dbaebcb57b1f15c1f59d86356772969028768b33c1a7c01e76d99f149239e63d59 languageName: node linkType: hard @@ -11687,9 +11390,9 @@ __metadata: linkType: hard "react-is@npm:^18.0.0": - version: 18.2.0 - resolution: "react-is@npm:18.2.0" - checksum: 10/200cd65bf2e0be7ba6055f647091b725a45dd2a6abef03bf2380ce701fd5edccee40b49b9d15edab7ac08a762bf83cb4081e31ec2673a5bfb549a36ba21570df + version: 18.3.1 + resolution: "react-is@npm:18.3.1" + checksum: 10/d5f60c87d285af24b1e1e7eaeb123ec256c3c8bdea7061ab3932e3e14685708221bf234ec50b21e10dd07f008f1b966a2730a0ce4ff67905b3872ff2042aec22 languageName: node linkType: hard @@ -11894,9 +11597,9 @@ __metadata: linkType: hard "rfdc@npm:^1.3.0": - version: 1.3.1 - resolution: "rfdc@npm:1.3.1" - checksum: 10/44cc6a82e2fe1db13b7d3c54e9ffd0b40ef070cbde69ffbfbb38dab8cee46bd68ba686784b96365ff08d04798bc121c3465663a0c91f2c421c90546c4366f4a6 + version: 1.4.1 + resolution: "rfdc@npm:1.4.1" + checksum: 10/2f3d11d3d8929b4bfeefc9acb03aae90f971401de0add5ae6c5e38fec14f0405e6a4aad8fdb76344bfdd20c5193110e3750cbbd28ba86d73729d222b6cf4a729 languageName: node linkType: hard @@ -11953,25 +11656,25 @@ __metadata: linkType: hard "rollup@npm:^4.0.2": - version: 4.16.4 - resolution: "rollup@npm:4.16.4" - dependencies: - "@rollup/rollup-android-arm-eabi": "npm:4.16.4" - "@rollup/rollup-android-arm64": "npm:4.16.4" - "@rollup/rollup-darwin-arm64": "npm:4.16.4" - "@rollup/rollup-darwin-x64": "npm:4.16.4" - "@rollup/rollup-linux-arm-gnueabihf": "npm:4.16.4" - "@rollup/rollup-linux-arm-musleabihf": "npm:4.16.4" - "@rollup/rollup-linux-arm64-gnu": "npm:4.16.4" - "@rollup/rollup-linux-arm64-musl": "npm:4.16.4" - "@rollup/rollup-linux-powerpc64le-gnu": "npm:4.16.4" - "@rollup/rollup-linux-riscv64-gnu": "npm:4.16.4" - "@rollup/rollup-linux-s390x-gnu": "npm:4.16.4" - "@rollup/rollup-linux-x64-gnu": "npm:4.16.4" - "@rollup/rollup-linux-x64-musl": "npm:4.16.4" - "@rollup/rollup-win32-arm64-msvc": "npm:4.16.4" - "@rollup/rollup-win32-ia32-msvc": "npm:4.16.4" - "@rollup/rollup-win32-x64-msvc": "npm:4.16.4" + version: 4.19.0 + resolution: "rollup@npm:4.19.0" + dependencies: + "@rollup/rollup-android-arm-eabi": "npm:4.19.0" + "@rollup/rollup-android-arm64": "npm:4.19.0" + "@rollup/rollup-darwin-arm64": "npm:4.19.0" + "@rollup/rollup-darwin-x64": "npm:4.19.0" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.19.0" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.19.0" + "@rollup/rollup-linux-arm64-gnu": "npm:4.19.0" + "@rollup/rollup-linux-arm64-musl": "npm:4.19.0" + "@rollup/rollup-linux-powerpc64le-gnu": "npm:4.19.0" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.19.0" + "@rollup/rollup-linux-s390x-gnu": "npm:4.19.0" + "@rollup/rollup-linux-x64-gnu": "npm:4.19.0" + "@rollup/rollup-linux-x64-musl": "npm:4.19.0" + "@rollup/rollup-win32-arm64-msvc": "npm:4.19.0" + "@rollup/rollup-win32-ia32-msvc": "npm:4.19.0" + "@rollup/rollup-win32-x64-msvc": "npm:4.19.0" "@types/estree": "npm:1.0.5" fsevents: "npm:~2.3.2" dependenciesMeta: @@ -12011,7 +11714,7 @@ __metadata: optional: true bin: rollup: dist/bin/rollup - checksum: 10/a5c96c264c7dbea0910dd09ef46a2c2b5f8dd7d431d41ca50c941e8fd6991f5f0645746e5dc1bf332063ca0873b5344d04bc165e276f298acb35143def534485 + checksum: 10/a5f56e60d160e727f372fb0b0adbab03c1e5b858df7af62e626459687e6510d5b9685e4badef50bb6ffd916eaf53c1684a8e12ae959dacb8e6930c77a00a0f19 languageName: node linkType: hard @@ -12082,6 +11785,13 @@ __metadata: languageName: node linkType: hard +"safevalues@npm:0.6.0": + version: 0.6.0 + resolution: "safevalues@npm:0.6.0" + checksum: 10/b0ad6307e08047c4e2f9c3feb3158c5d75dbc5a54fac1719e6bd10e2a78af33f32b1b43187aff8ad7d4472075bdc3f2a60595bf40260e80883c9c1b77af7fe52 + languageName: node + linkType: hard + "saxes@npm:^5.0.1": version: 5.0.1 resolution: "saxes@npm:5.0.1" @@ -12118,11 +11828,11 @@ __metadata: linkType: hard "semver@npm:7.x, semver@npm:^7.0.0, semver@npm:^7.1.1, semver@npm:^7.3.2, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0": - version: 7.6.2 - resolution: "semver@npm:7.6.2" + version: 7.6.3 + resolution: "semver@npm:7.6.3" bin: semver: bin/semver.js - checksum: 10/296b17d027f57a87ef645e9c725bff4865a38dfc9caf29b26aa084b85820972fbe7372caea1ba6857162fa990702c6d9c1d82297cecb72d56c78ab29070d2ca2 + checksum: 10/36b1fbe1a2b6f873559cd57b238f1094a053dbfd997ceeb8757d79d1d2089c56d1321b9f1069ce263dc64cfa922fa1d2ad566b39426fe1ac6c723c1487589e10 languageName: node linkType: hard @@ -12136,11 +11846,11 @@ __metadata: linkType: hard "ses@npm:^1.1.0": - version: 1.4.1 - resolution: "ses@npm:1.4.1" + version: 1.5.0 + resolution: "ses@npm:1.5.0" dependencies: - "@endo/env-options": "npm:^1.1.3" - checksum: 10/b85b2fc167ac0859db87377dfc092babb9e02e3a43a005be1ad48e5253a3510fd4a7e85b205b9cac83bd9f765d90bf202e62b378a9d383d6701e5d98b7bd4b86 + "@endo/env-options": "npm:^1.1.4" + checksum: 10/6f2cd8f3607f98838d6458cc4b77e6c1cffe8fc30b923212c1dde73c89e9a0e1eaa8f44ac38811d73a6961560e5be4155a0e58879f6dfe69d3319433251891da languageName: node linkType: hard @@ -12334,17 +12044,17 @@ __metadata: linkType: hard "socks-proxy-agent@npm:^8.0.3": - version: 8.0.3 - resolution: "socks-proxy-agent@npm:8.0.3" + version: 8.0.4 + resolution: "socks-proxy-agent@npm:8.0.4" dependencies: agent-base: "npm:^7.1.1" debug: "npm:^4.3.4" - socks: "npm:^2.7.1" - checksum: 10/c2112c66d6322e497d68e913c3780f3683237fd394bfd480b9283486a86e36095d0020db96145d88f8ccd9cc73261b98165b461f9c1bf5dc17abfe75c18029ce + socks: "npm:^2.8.3" + checksum: 10/c8e7c2b398338b49a0a0f4d2bae5c0602aeeca6b478b99415927b6c5db349ca258448f2c87c6958ebf83eea17d42cbc5d1af0bfecb276cac10b9658b0f07f7d7 languageName: node linkType: hard -"socks@npm:^2.7.1": +"socks@npm:^2.8.3": version: 2.8.3 resolution: "socks@npm:2.8.3" dependencies: @@ -12379,7 +12089,7 @@ __metadata: languageName: node linkType: hard -"source-map-js@npm:^1.0.2, source-map-js@npm:^1.2.0": +"source-map-js@npm:^1.2.0": version: 1.2.0 resolution: "source-map-js@npm:1.2.0" checksum: 10/74f331cfd2d121c50790c8dd6d3c9de6be21926de80583b23b37029b0f37aefc3e019fa91f9a10a5e120c08135297e1ecf312d561459c45908cb1e0e365f49e5 @@ -12447,9 +12157,9 @@ __metadata: linkType: hard "spdx-license-ids@npm:^3.0.0": - version: 3.0.17 - resolution: "spdx-license-ids@npm:3.0.17" - checksum: 10/8f6c6ae02ebb25b4ca658b8990d9e8a8f8d8a95e1d8b9fd84d87eed80a7dc8f8073d6a8d50b8a0295c0e8399e1f8814f5c00e2985e6bf3731540a16f7241cbf1 + version: 3.0.18 + resolution: "spdx-license-ids@npm:3.0.18" + checksum: 10/45fdbb50c4bbe364720ef0acd19f4fc1914d73ba1e2b1ce9db21ee12d7f9e8bf14336289f6ad3d5acac3dc5b91aafe61e9c652d5806b31cbb8518a14979a16ff languageName: node linkType: hard @@ -12468,11 +12178,11 @@ __metadata: linkType: hard "ssri@npm:^10.0.0": - version: 10.0.5 - resolution: "ssri@npm:10.0.5" + version: 10.0.6 + resolution: "ssri@npm:10.0.6" dependencies: minipass: "npm:^7.0.3" - checksum: 10/453f9a1c241c13f5dfceca2ab7b4687bcff354c3ccbc932f35452687b9ef0ccf8983fd13b8a3baa5844c1a4882d6e3ddff48b0e7fd21d743809ef33b80616d79 + checksum: 10/f92c1b3cc9bfd0a925417412d07d999935917bc87049f43ebec41074661d64cf720315661844106a77da9f8204b6d55ae29f9514e673083cae39464343af2a8b languageName: node linkType: hard @@ -12495,16 +12205,17 @@ __metadata: linkType: hard "streamx@npm:^2.15.0": - version: 2.16.1 - resolution: "streamx@npm:2.16.1" + version: 2.18.0 + resolution: "streamx@npm:2.18.0" dependencies: bare-events: "npm:^2.2.0" - fast-fifo: "npm:^1.1.0" + fast-fifo: "npm:^1.3.2" queue-tick: "npm:^1.0.1" + text-decoder: "npm:^1.1.0" dependenciesMeta: bare-events: optional: true - checksum: 10/f6d0899adf089385d9c58a630fc705dc6c3931b18181c32860e5013955a339a3b763a4df62168f37c7fc56b1f7bb2a38db989fa9df487995278cb5d46f248da6 + checksum: 10/039e828e7e76399d65fed022ddaeb7ab3ee77f66d170733643b7f7510823a605315f3ee841e5c01f16df5a44dca18a97fc39460a2b42010484e7976f29c79296 languageName: node linkType: hard @@ -12728,13 +12439,13 @@ __metadata: languageName: node linkType: hard -"synckit@npm:0.9.0": - version: 0.9.0 - resolution: "synckit@npm:0.9.0" +"synckit@npm:0.9.1": + version: 0.9.1 + resolution: "synckit@npm:0.9.1" dependencies: "@pkgr/core": "npm:^0.1.0" tslib: "npm:^2.6.2" - checksum: 10/e93f3f5ee43fa71d3bb2a345049642d9034f34fa9528706b5ef26e825335ca5446143c56c2b041810afe26aa6e343583ff08525f5530618a4707375270f87be1 + checksum: 10/bff3903976baf8b699b5483228116d70223781a93b17c70e685c277ee960cdfd1a09cb5a741e6a9ec35e2428f14f4664baec41ccc99a598f267608b2a54f529b languageName: node linkType: hard @@ -12762,7 +12473,7 @@ __metadata: languageName: node linkType: hard -"tar@npm:^6.1.11, tar@npm:^6.1.2": +"tar@npm:^6.1.11, tar@npm:^6.2.1": version: 6.2.1 resolution: "tar@npm:6.2.1" dependencies: @@ -12807,6 +12518,15 @@ __metadata: languageName: node linkType: hard +"text-decoder@npm:^1.1.0": + version: 1.1.1 + resolution: "text-decoder@npm:1.1.1" + dependencies: + b4a: "npm:^1.6.4" + checksum: 10/c6981b93850daeafc8bd1dbd8f984d4fb2d14632f450de0892692b5bbee2d2f4cbef8a807142527370649fd357f58491ede4915d43669eca624cb52b8dd247b6 + languageName: node + linkType: hard + "text-table@npm:^0.2.0": version: 0.2.0 resolution: "text-table@npm:0.2.0" @@ -12863,14 +12583,14 @@ __metadata: linkType: hard "tough-cookie@npm:^4.0.0": - version: 4.1.3 - resolution: "tough-cookie@npm:4.1.3" + version: 4.1.4 + resolution: "tough-cookie@npm:4.1.4" dependencies: psl: "npm:^1.1.33" punycode: "npm:^2.1.1" universalify: "npm:^0.2.0" url-parse: "npm:^1.5.3" - checksum: 10/cf148c359b638a7069fc3ba9a5257bdc9616a6948a98736b92c3570b3f8401cf9237a42bf716878b656f372a1fb65b74dd13a46ccff8eceba14ffd053d33f72a + checksum: 10/75663f4e2cd085f16af0b217e4218772adf0617fb3227171102618a54ce0187a164e505d61f773ed7d65988f8ff8a8f935d381f87da981752c1171b076b4afac languageName: node linkType: hard @@ -13108,13 +12828,6 @@ __metadata: languageName: node linkType: hard -"tweetnacl-util@npm:^0.15.1": - version: 0.15.1 - resolution: "tweetnacl-util@npm:0.15.1" - checksum: 10/ae6aa8a52cdd21a95103a4cc10657d6a2040b36c7a6da7b9d3ab811c6750a2d5db77e8c36969e75fdee11f511aa2b91c552496c6e8e989b6e490e54aca2864fc - languageName: node - linkType: hard - "tweetnacl@npm:^1.0.3": version: 1.0.3 resolution: "tweetnacl@npm:1.0.3" @@ -13167,9 +12880,9 @@ __metadata: linkType: hard "type-fest@npm:^4.0.0": - version: 4.20.0 - resolution: "type-fest@npm:4.20.0" - checksum: 10/df037c11f6393312f27825ea6eb2c8cd62b1ba21c31144bed41854648ba2a18dcb8c68a930607c7227dd531b42006cc7c7a60f7f034668d1c92c205523ae1ea2 + version: 4.23.0 + resolution: "type-fest@npm:4.23.0" + checksum: 10/c411dea83262f9a4453e09ff82e3ac729dd26afc2e68b7a9fe93dd633b1a2bf7bf2bf3e041676497ae8b8411266019b3add91d4fe34b926a82ba09eb41e9e52b languageName: node linkType: hard @@ -13242,11 +12955,11 @@ __metadata: linkType: hard "typedoc-plugin-missing-exports@npm:^2.0.0": - version: 2.2.0 - resolution: "typedoc-plugin-missing-exports@npm:2.2.0" + version: 2.3.0 + resolution: "typedoc-plugin-missing-exports@npm:2.3.0" peerDependencies: typedoc: 0.24.x || 0.25.x - checksum: 10/db691b3494c8e112d7ea8621fcf79f1988ef8f0c9fc43eb5fc308fda0caa1b1e007c3d51db83236247cf9b52cdfec1186a7eb25ec3416fa03ef65f68b4e88e4e + checksum: 10/83ff8affd82fa39a81931e825ef31b51b7470613c71601fde6ff413a5c7571e30734698092a38a437f12c5d3264010696ce9ca806d43485aa11e8208cb4cb323 languageName: node linkType: hard @@ -13339,17 +13052,17 @@ __metadata: languageName: node linkType: hard -"update-browserslist-db@npm:^1.0.13": - version: 1.0.13 - resolution: "update-browserslist-db@npm:1.0.13" +"update-browserslist-db@npm:^1.1.0": + version: 1.1.0 + resolution: "update-browserslist-db@npm:1.1.0" dependencies: - escalade: "npm:^3.1.1" - picocolors: "npm:^1.0.0" + escalade: "npm:^3.1.2" + picocolors: "npm:^1.0.1" peerDependencies: browserslist: ">= 4.21.0" bin: update-browserslist-db: cli.js - checksum: 10/9074b4ef34d2ed931f27d390aafdd391ee7c45ad83c508e8fed6aaae1eb68f81999a768ed8525c6f88d4001a4fbf1b8c0268f099d0e8e72088ec5945ac796acf + checksum: 10/d70b9efeaf4601aadb1a4f6456a7a5d9118e0063d995866b8e0c5e0cf559482671dab6ce7b079f9536b06758a344fbd83f974b965211e1c6e8d1958540b0c24c languageName: node linkType: hard @@ -13453,11 +13166,9 @@ __metadata: linkType: hard "validate-npm-package-name@npm:^5.0.0": - version: 5.0.0 - resolution: "validate-npm-package-name@npm:5.0.0" - dependencies: - builtins: "npm:^5.0.0" - checksum: 10/5342a994986199b3c28e53a8452a14b2bb5085727691ea7aa0d284a6606b127c371e0925ae99b3f1ef7cc7d2c9de75f52eb61a3d1cc45e39bca1e3a9444cbb4e + version: 5.0.1 + resolution: "validate-npm-package-name@npm:5.0.1" + checksum: 10/0d583a1af23aeffea7748742cf22b6802458736fb8b60323ba5949763824d46f796474b0e1b9206beb716f9d75269e19dbd7795d6b038b29d561be95dd827381 languageName: node linkType: hard @@ -13521,9 +13232,9 @@ __metadata: linkType: hard "webextension-polyfill@npm:>=0.10.0 <1.0": - version: 0.10.0 - resolution: "webextension-polyfill@npm:0.10.0" - checksum: 10/51ff30ebed4b1aa802b7f0347f05021b2fe492078bb1a597223d43995fcee96e2da8f914a2f6e36f988c1877ed5ab36ca7077f2f3ab828955151a59e4c01bf7e + version: 0.12.0 + resolution: "webextension-polyfill@npm:0.12.0" + checksum: 10/77e648b958b573ef075e75a0c180e2bbd74dee17b3145e86d21fcbb168c4999e4a311654fe634b8178997bee9b35ea5808d8d3d3e5ff2ad138f197f4f0ea75d9 languageName: node linkType: hard @@ -13714,6 +13425,13 @@ __metadata: languageName: node linkType: hard +"word-wrap@npm:^1.2.5": + version: 1.2.5 + resolution: "word-wrap@npm:1.2.5" + checksum: 10/1ec6f6089f205f83037be10d0c4b34c9183b0b63fca0834a5b3cee55dd321429d73d40bb44c8fc8471b5203d6e8f8275717f49a8ff4b2b0ab41d7e1b563e0854 + languageName: node + linkType: hard + "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": version: 7.0.0 resolution: "wrap-ansi@npm:7.0.0" @@ -13780,24 +13498,24 @@ __metadata: languageName: node linkType: hard -"ws@npm:8.5.0": - version: 8.5.0 - resolution: "ws@npm:8.5.0" +"ws@npm:8.17.1": + version: 8.17.1 + resolution: "ws@npm:8.17.1" peerDependencies: bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 + utf-8-validate: ">=5.0.2" peerDependenciesMeta: bufferutil: optional: true utf-8-validate: optional: true - checksum: 10/f0ee700970a0bf925b1ec213ca3691e84fb8b435a91461fe3caf52f58c6cec57c99ed5890fbf6978824c932641932019aafc55d864cad38ac32577496efd5d3a + checksum: 10/4264ae92c0b3e59c7e309001e93079b26937aab181835fb7af79f906b22cd33b6196d96556dafb4e985742dd401e99139572242e9847661fdbc96556b9e6902d languageName: node linkType: hard "ws@npm:^7.4.6": - version: 7.5.9 - resolution: "ws@npm:7.5.9" + version: 7.5.10 + resolution: "ws@npm:7.5.10" peerDependencies: bufferutil: ^4.0.1 utf-8-validate: ^5.0.2 @@ -13806,7 +13524,7 @@ __metadata: optional: true utf-8-validate: optional: true - checksum: 10/171e35012934bd8788150a7f46f963e50bac43a4dc524ee714c20f258693ac4d3ba2abadb00838fdac42a47af9e958c7ae7e6f4bc56db047ba897b8a2268cf7c + checksum: 10/9c796b84ba80ffc2c2adcdfc9c8e9a219ba99caa435c9a8d45f9ac593bba325563b3f83edc5eb067cc6d21b9a6bf2c930adf76dd40af5f58a5ca6859e81858f0 languageName: node linkType: hard @@ -13867,11 +13585,11 @@ __metadata: linkType: hard "yaml@npm:^2.2.2, yaml@npm:^2.3.4": - version: 2.4.1 - resolution: "yaml@npm:2.4.1" + version: 2.4.5 + resolution: "yaml@npm:2.4.5" bin: yaml: bin.mjs - checksum: 10/2c54fd69ef59126758ae710f9756405a7d41abcbb61aca894250d0e81e76057c14dc9bb00a9528f72f99b8f24077f694a6f7fd09cdd6711fcec2eebfbb5df409 + checksum: 10/b09bf5a615a65276d433d76b8e34ad6b4c0320b85eb3f1a39da132c61ae6e2ff34eff4624e6458d96d49566c93cf43408ba5e568218293a8c6541a2006883f64 languageName: node linkType: hard @@ -13934,8 +13652,8 @@ __metadata: linkType: hard "yocto-queue@npm:^1.0.0": - version: 1.0.0 - resolution: "yocto-queue@npm:1.0.0" - checksum: 10/2cac84540f65c64ccc1683c267edce396b26b1e931aa429660aefac8fbe0188167b7aee815a3c22fa59a28a58d898d1a2b1825048f834d8d629f4c2a5d443801 + version: 1.1.1 + resolution: "yocto-queue@npm:1.1.1" + checksum: 10/f2e05b767ed3141e6372a80af9caa4715d60969227f38b1a4370d60bffe153c9c5b33a862905609afc9b375ec57cd40999810d20e5e10229a204e8bde7ef255c languageName: node linkType: hard From 64d2d0d0a4ebc57cec57f1888fdfe204e76c437a Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Mon, 22 Jul 2024 11:44:01 -0600 Subject: [PATCH 39/49] Update changelog --- packages/network-controller/CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/network-controller/CHANGELOG.md b/packages/network-controller/CHANGELOG.md index d260660e568..e51536be555 100644 --- a/packages/network-controller/CHANGELOG.md +++ b/packages/network-controller/CHANGELOG.md @@ -12,14 +12,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **BREAKING:** Add `networkConfigurationsByChainId` to `NetworkState` (type: `Record`) ([#4268](https://github.com/MetaMask/core/pull/4286)) - This property replaces `networkConfigurations`, and, as its name implies, organizes network configurations by chain ID rather than network client ID. - If no initial state or this property is not included in initial state, the default value of this property will now include configurations for known Infura networks (Mainnet, Goerli, Sepolia, Linea Goerli, Linea Sepolia, and Linea Mainnet) by default. -- Add `getNetworkConfigurationByChainId` method and `NetworkController:getNetworkConfigurationByChainId` messenger action ([#4268](https://github.com/MetaMask/core/pull/4286)) +- Add `getNetworkConfigurationByChainId` method, `NetworkController:getNetworkConfigurationByChainId` messenger action, and `NetworkControllerGetNetworkConfigurationByNetworkClientId` type ([#4268](https://github.com/MetaMask/core/pull/4286)) - Add `addNetwork`, which replaces one half of `upsertNetworkConfiguration` and can be used to add new network clients for a chain ([#4268](https://github.com/MetaMask/core/pull/4286)) - It's worth noting that this method now publishes a `NetworkController:networkAdded` event instead of calling a `trackMetaMetricsEvent` callback. It is expected that you will subscribe to this event and create a MetaMetrics event yourself. - Add `updateNetwork`, which replaces one half of `upsertNetworkConfiguration` and can be used to recreate the network clients for an existing chain based on an updated configuration ([#4268](https://github.com/MetaMask/core/pull/4286)) - Note that it is not possible to remove the RPC endpoint from a network configuration that is currently represented by the globally selected network client. To prevent an error, you'll need to detect when such a removal is occurring and pass the `replacementSelectedRpcEndpointIndex` to `updateNetwork`. It will then switch to the designated RPC endpoint's network client on your behalf. - Add `removeNetwork`, which replaces `removeNetworkConfiguration` and can be used to remove existing network clients for a chain ([#4268](https://github.com/MetaMask/core/pull/4286)) - Add `getDefaultNetworkControllerState` function, which replaces `defaultState` and matches patterns in other controllers ([#4268](https://github.com/MetaMask/core/pull/4286)) -- Add `RpcEndpointType` type ([#4268](https://github.com/MetaMask/core/pull/4286)) +- Add `RpcEndpointType`, `AddNetworkFields`, and `UpdateNetworkFields` types ([#4268](https://github.com/MetaMask/core/pull/4286)) ### Changed @@ -48,9 +48,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **BREAKING:** Remove `networkConfigurations` from `NetworkState`, which has been replaced with `networkConfigurationsByChainId` ([#4268](https://github.com/MetaMask/core/pull/4286)) - **BREAKING:** Remove `upsertNetworkConfiguration` and `removeNetworkConfiguration`, which have been replaced with `addNetwork`, `updateNetwork`, and `removeNetwork` ([#4268](https://github.com/MetaMask/core/pull/4286)) -- **BREAKING:** Remove `defaultState`, which has been replaced with `getDefaultNetworkControllerState` ([#4268](https://github.com/MetaMask/core/pull/4286)) +- **BREAKING:** Remove `defaultState` variable, which has been replaced with a `getDefaultNetworkControllerState` function ([#4268](https://github.com/MetaMask/core/pull/4286)) - **BREAKING:** Remove `trackMetaMetricsEvent` option from the NetworkController constructor ([#4268](https://github.com/MetaMask/core/pull/4286)) - - Previously, this was used in `upsertNetworkConfiguration` to create a MetaMetrics event when a new network was added. This can now be achieved by subscribing to the `NetworkController:networkAdded` event. + - Previously, this was used in `upsertNetworkConfiguration` to create a MetaMetrics event when a new network was added. This can now be achieved by subscribing to the `NetworkController:networkAdded` event and creating the event inside of the event handler. ## [20.0.0] From 380328b699033f737a943a906e51bb4f3107c164 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Tue, 23 Jul 2024 09:00:51 -0600 Subject: [PATCH 40/49] Remove debugging code Co-authored-by: Michele Esposito <34438276+mikesposito@users.noreply.github.com> --- packages/network-controller/src/NetworkController.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index 968e4fdc104..32dbcd3337a 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -1753,18 +1753,6 @@ export class NetworkController extends BaseController< networkClientOperations, }); - /* - console.log( - 'replacementSelectedRpcEndpointIndex', - replacementSelectedRpcEndpointIndex, - 'updatedNetworkConfiguration', - updatedNetworkConfiguration, - 'networkClientOperations', - networkClientOperations, - 'this.state.selectedNetworkClientId', - this.state.selectedNetworkClientId, - ) - */ if ( replacementSelectedRpcEndpointIndex === undefined && networkClientOperations.some((networkClientOperation) => { From f25fb8c5693173b8147dba83aeb8152697af3619 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Tue, 23 Jul 2024 15:52:09 -0600 Subject: [PATCH 41/49] Move errorMessagePrefix into #validateNetworkFields --- .../network-controller/src/NetworkController.ts | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index 32dbcd3337a..4eeb6b79666 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -1519,7 +1519,6 @@ export class NetworkController extends BaseController< this.#validateNetworkFields({ mode: 'add', - errorMessagePrefix: 'Could not add network', networkFields: fields, autoManagedNetworkClientRegistry, }); @@ -1618,7 +1617,6 @@ export class NetworkController extends BaseController< this.#validateNetworkFields({ mode: 'update', - errorMessagePrefix: 'Could not update network', networkFields: fields, existingNetworkConfiguration, autoManagedNetworkClientRegistry, @@ -1963,7 +1961,6 @@ export class NetworkController extends BaseController< */ #validateNetworkFields( args: { - errorMessagePrefix: string; autoManagedNetworkClientRegistry: AutoManagedNetworkClientRegistry; } & ( | { @@ -1977,17 +1974,15 @@ export class NetworkController extends BaseController< } ), ) { - const { - mode, - networkFields, - errorMessagePrefix, - autoManagedNetworkClientRegistry, - } = args; + const { mode, networkFields, autoManagedNetworkClientRegistry } = args; const existingNetworkConfiguration = 'existingNetworkConfiguration' in args ? args.existingNetworkConfiguration : null; + const errorMessagePrefix = + mode === 'update' ? 'Could not update network' : 'Could not add network'; + if ( !isStrictHexString(networkFields.chainId) || !isSafeChainId(networkFields.chainId) From ab27c88062ce6aafcbcc7225a0ee39c4496c5d50 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Tue, 23 Jul 2024 15:58:56 -0600 Subject: [PATCH 42/49] Add TODO for moving Partialize to @metamask/utils --- packages/controller-utils/src/types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/controller-utils/src/types.ts b/packages/controller-utils/src/types.ts index 34a0bebd03f..b71139791b8 100644 --- a/packages/controller-utils/src/types.ts +++ b/packages/controller-utils/src/types.ts @@ -124,5 +124,6 @@ export type NetworkNickname = * @template Type - The Record that you want to operate on. * @template Key - The union of keys you want to make optional. */ +// TODO: Move to @metamask/utils export type Partialize = Omit & Partial>; From 49a698432ff4f63ce6a97f3572dd3bf51b2ea383 Mon Sep 17 00:00:00 2001 From: Michele Esposito Date: Wed, 14 Aug 2024 11:37:20 +0200 Subject: [PATCH 43/49] fix: ts errors --- .../tests/NetworkController.test.ts | 64 +------------------ 1 file changed, 3 insertions(+), 61 deletions(-) diff --git a/packages/network-controller/tests/NetworkController.test.ts b/packages/network-controller/tests/NetworkController.test.ts index e1338640222..a8b1c99079f 100644 --- a/packages/network-controller/tests/NetworkController.test.ts +++ b/packages/network-controller/tests/NetworkController.test.ts @@ -542,8 +542,6 @@ describe('NetworkController', () => { // TODO: Update these names const infuraNativeTokenName = NetworksTicker[infuraNetworkType]; - // False negative - this is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions describe(`when the selected network client represents the Infura network "${infuraNetworkType}"`, () => { it('sets the globally selected provider to the one from the corresponding network client', async () => { const infuraProjectId = 'some-infura-project-id'; @@ -723,11 +721,7 @@ describe('NetworkController', () => { const infuraNetworkNickname = NetworkNickname[infuraNetworkType]; const infuraNativeTokenName = NetworksTicker[infuraNetworkType]; - // False negative - this is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions describe(`when the selectedNetworkClientId is changed to represent the Infura network "${infuraNetworkType}"`, () => { - // False negative - this is a string. - // 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 now pointed to ${infuraNetworkNickname} afterward`, async () => { const infuraProjectId = 'some-infura-project-id'; @@ -1260,8 +1254,6 @@ describe('NetworkController', () => { for (const infuraNetworkType of Object.values(InfuraNetworkType)) { const infuraChainId = ChainId[infuraNetworkType]; - // False negative - this is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions describe(`when the selected network client represents the Infura network "${infuraNetworkType}"`, () => { 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 () => { @@ -1900,8 +1892,6 @@ describe('NetworkController', () => { describe('setProviderType', () => { for (const infuraNetworkType of Object.values(InfuraNetworkType)) { - // False negative - this is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions describe(`given the Infura network "${infuraNetworkType}"`, () => { refreshNetworkTests({ expectedNetworkClientConfiguration: @@ -1912,8 +1902,6 @@ describe('NetworkController', () => { }); }); - // False negative - this is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions it(`sets selectedNetworkClientId in state to "${infuraNetworkType}"`, async () => { await withController(async ({ controller }) => { mockCreateNetworkClient().mockReturnValue(buildFakeClient()); @@ -2050,8 +2038,6 @@ describe('NetworkController', () => { for (const infuraNetworkType of Object.values(InfuraNetworkType)) { const infuraChainId = ChainId[infuraNetworkType]; - // False negative - 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 "${infuraNetworkType}"`, () => { refreshNetworkTests({ expectedNetworkClientConfiguration: @@ -2076,8 +2062,6 @@ describe('NetworkController', () => { }, }); - // False negative - this is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions it(`sets selectedNetworkClientId in state to "${infuraNetworkType}"`, async () => { await withController({}, async ({ controller }) => { mockCreateNetworkClient().mockReturnValue(buildFakeClient()); @@ -2570,8 +2554,6 @@ describe('NetworkController', () => { describe('resetConnection', () => { for (const infuraNetworkType of Object.values(InfuraNetworkType)) { - // False negative - this is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions describe(`when the selected network client represents the Infura network "${infuraNetworkType}"`, () => { refreshNetworkTests({ expectedNetworkClientConfiguration: @@ -2693,8 +2675,6 @@ describe('NetworkController', () => { for (const infuraNetworkType of Object.values(InfuraNetworkType)) { const infuraChainId = ChainId[infuraNetworkType]; - // False negative - this is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions describe(`given the ID of the Infura-supported chain "${infuraNetworkType}" that a network configuration is filed under`, () => { it('returns the network configuration', async () => { const registeredNetworkConfiguration = @@ -2811,8 +2791,6 @@ describe('NetworkController', () => { for (const infuraNetworkType of Object.values(InfuraNetworkType)) { const infuraChainId = ChainId[infuraNetworkType]; - // False negative - this is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions describe(`given the ID of a network client that corresponds to an RPC endpoint for the Infura network "${infuraNetworkType}" in a network configuration`, () => { it('returns the network configuration', async () => { const registeredNetworkConfiguration = @@ -3070,8 +3048,6 @@ describe('NetworkController', () => { const infuraNetworkNickname = NetworkNickname[infuraNetworkType]; const infuraChainId = ChainId[infuraNetworkType]; - // False negative - this is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions it(`throws if rpcEndpoints contains an Infura RPC endpoint which is already present in the network configuration for the Infura-supported chain ${infuraChainId}`, async () => { const infuraRpcEndpoint = buildInfuraRpcEndpoint(infuraNetworkType); @@ -3432,9 +3408,7 @@ describe('NetworkController', () => { name: infuraNetworkNickname, networkClientId: infuraNetworkType, type: RpcEndpointType.Infura as const, - // False negative - this is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - url: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`, + url: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}` as const, }, ], }); @@ -3462,8 +3436,6 @@ describe('NetworkController', () => { name: infuraNetworkNickname, networkClientId: infuraNetworkType, type: RpcEndpointType.Infura as const, - // False negative - this is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions url: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`, }, ], @@ -3509,9 +3481,7 @@ describe('NetworkController', () => { name: infuraNetworkNickname, networkClientId: infuraNetworkType, type: RpcEndpointType.Infura as const, - // False negative - this is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - url: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`, + url: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}` as const, }, ], }); @@ -3528,8 +3498,6 @@ describe('NetworkController', () => { name: infuraNetworkNickname, networkClientId: infuraNetworkType, type: RpcEndpointType.Infura as const, - // False negative - this is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions url: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`, }, ], @@ -3569,9 +3537,7 @@ describe('NetworkController', () => { name: infuraNetworkNickname, networkClientId: infuraNetworkType, type: RpcEndpointType.Infura as const, - // False negative - this is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - url: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`, + url: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}` as const, }, ], }); @@ -3588,8 +3554,6 @@ describe('NetworkController', () => { name: infuraNetworkNickname, networkClientId: infuraNetworkType, type: RpcEndpointType.Infura as const, - // False negative - this is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions url: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`, }, ], @@ -4217,8 +4181,6 @@ describe('NetworkController', () => { const infuraNetworkNickname = NetworkNickname[infuraNetworkType]; const infuraChainId = ChainId[infuraNetworkType]; - // False negative - this is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions it(`throws if an Infura RPC endpoint is being added which is already present in the network configuration for the Infura-supported chain ${infuraChainId}`, async () => { const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ chainId: '0x1337', @@ -4531,8 +4493,6 @@ describe('NetworkController', () => { const infuraChainId = ChainId[infuraNetworkType]; const infuraNativeTokenName = NetworksTicker[infuraNetworkType]; - // False negative - this is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions describe(`if the existing chain ID is the Infura-supported chain ${infuraChainId} and is not being changed`, () => { describe('when a new Infura RPC endpoint is being added', () => { it('creates and registers a new network client for the RPC endpoint', async () => { @@ -8290,11 +8250,7 @@ describe('NetworkController', () => { const anotherInfuraNetworkNickname = NetworkNickname[anotherInfuraNetworkType]; - // False negative - this is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions describe(`if the chain ID is being changed from a non-Infura-supported chain to the Infura-supported chain ${infuraChainId}`, () => { - // False negative - this is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions it(`throws if a network configuration for the Infura network "${infuraNetworkNickname}" is already registered under the new chain ID`, async () => { const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ chainId: '0x1337' }); @@ -8912,8 +8868,6 @@ describe('NetworkController', () => { }); }); - // False negative - this is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions describe(`if the chain ID is being changed from the Infura-supported chain ${infuraChainId} to a non-Infura-supported chain`, () => { it('throws if a network configuration for a custom network is already registered under the new chain ID', async () => { const networkConfigurationToUpdate = @@ -8949,8 +8903,6 @@ describe('NetworkController', () => { chainId: '0x1337', }), ).rejects.toThrow( - // False negative - this is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions `Cannot move network from chain ${infuraChainId} to 0x1337 as another network for that chain already exists ('Some Network')`, ); }, @@ -9580,11 +9532,7 @@ describe('NetworkController', () => { }); }); - // False negative - this is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions describe(`if the chain ID is being changed from the Infura-supported chain ${infuraChainId} to a different Infura-supported chain ${anotherInfuraChainId}`, () => { - // False negative - this is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions it(`throws if a network configuration for the Infura network "${infuraNetworkNickname}" is already registered under the new chain ID`, async () => { const networkConfigurationToUpdate = buildInfuraNetworkConfiguration(infuraNetworkType); @@ -11405,8 +11353,6 @@ describe('NetworkController', () => { describe('rollbackToPreviousProvider', () => { describe('when called not following any network switches', () => { for (const infuraNetworkType of Object.values(InfuraNetworkType)) { - // False negative - this is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions describe(`when the selected network client represents the Infura network "${infuraNetworkType}"`, () => { refreshNetworkTests({ expectedNetworkClientConfiguration: @@ -11455,8 +11401,6 @@ describe('NetworkController', () => { const infuraChainId = ChainId[infuraNetworkType]; const infuraNativeTokenName = NetworksTicker[infuraNetworkType]; - // False negative - this is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions describe(`when called following a switch away from the Infura network "${infuraNetworkType}"`, () => { it('emits networkWillChange with state payload', async () => { await withController( @@ -11686,8 +11630,6 @@ describe('NetworkController', () => { ); }); - // False negative - this is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions it(`initializes a provider pointed to the "${infuraNetworkType}" Infura network`, async () => { const infuraProjectId = 'some-infura-project-id'; From e998273053049c07e51302507033ae127eb44033 Mon Sep 17 00:00:00 2001 From: Michele Esposito Date: Wed, 14 Aug 2024 11:53:55 +0200 Subject: [PATCH 44/49] Revert "remove eslint-disable" --- .../tests/NetworkController.test.ts | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/packages/network-controller/tests/NetworkController.test.ts b/packages/network-controller/tests/NetworkController.test.ts index a8b1c99079f..b9840da2f76 100644 --- a/packages/network-controller/tests/NetworkController.test.ts +++ b/packages/network-controller/tests/NetworkController.test.ts @@ -542,6 +542,8 @@ describe('NetworkController', () => { // TODO: Update these names const infuraNativeTokenName = NetworksTicker[infuraNetworkType]; + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions describe(`when the selected network client represents the Infura network "${infuraNetworkType}"`, () => { it('sets the globally selected provider to the one from the corresponding network client', async () => { const infuraProjectId = 'some-infura-project-id'; @@ -721,7 +723,11 @@ describe('NetworkController', () => { const infuraNetworkNickname = NetworkNickname[infuraNetworkType]; const infuraNativeTokenName = NetworksTicker[infuraNetworkType]; + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions describe(`when the selectedNetworkClientId is changed to represent the Infura network "${infuraNetworkType}"`, () => { + // False negative - this is a string. + // 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 now pointed to ${infuraNetworkNickname} afterward`, async () => { const infuraProjectId = 'some-infura-project-id'; @@ -1254,6 +1260,8 @@ describe('NetworkController', () => { for (const infuraNetworkType of Object.values(InfuraNetworkType)) { const infuraChainId = ChainId[infuraNetworkType]; + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions describe(`when the selected network client represents the Infura network "${infuraNetworkType}"`, () => { 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 () => { @@ -1892,6 +1900,8 @@ describe('NetworkController', () => { describe('setProviderType', () => { for (const infuraNetworkType of Object.values(InfuraNetworkType)) { + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions describe(`given the Infura network "${infuraNetworkType}"`, () => { refreshNetworkTests({ expectedNetworkClientConfiguration: @@ -1902,6 +1912,8 @@ describe('NetworkController', () => { }); }); + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions it(`sets selectedNetworkClientId in state to "${infuraNetworkType}"`, async () => { await withController(async ({ controller }) => { mockCreateNetworkClient().mockReturnValue(buildFakeClient()); @@ -2038,6 +2050,8 @@ describe('NetworkController', () => { for (const infuraNetworkType of Object.values(InfuraNetworkType)) { const infuraChainId = ChainId[infuraNetworkType]; + // False negative - 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 "${infuraNetworkType}"`, () => { refreshNetworkTests({ expectedNetworkClientConfiguration: @@ -2062,6 +2076,8 @@ describe('NetworkController', () => { }, }); + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions it(`sets selectedNetworkClientId in state to "${infuraNetworkType}"`, async () => { await withController({}, async ({ controller }) => { mockCreateNetworkClient().mockReturnValue(buildFakeClient()); @@ -2554,6 +2570,8 @@ describe('NetworkController', () => { describe('resetConnection', () => { for (const infuraNetworkType of Object.values(InfuraNetworkType)) { + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions describe(`when the selected network client represents the Infura network "${infuraNetworkType}"`, () => { refreshNetworkTests({ expectedNetworkClientConfiguration: @@ -2675,6 +2693,8 @@ describe('NetworkController', () => { for (const infuraNetworkType of Object.values(InfuraNetworkType)) { const infuraChainId = ChainId[infuraNetworkType]; + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions describe(`given the ID of the Infura-supported chain "${infuraNetworkType}" that a network configuration is filed under`, () => { it('returns the network configuration', async () => { const registeredNetworkConfiguration = @@ -2791,6 +2811,8 @@ describe('NetworkController', () => { for (const infuraNetworkType of Object.values(InfuraNetworkType)) { const infuraChainId = ChainId[infuraNetworkType]; + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions describe(`given the ID of a network client that corresponds to an RPC endpoint for the Infura network "${infuraNetworkType}" in a network configuration`, () => { it('returns the network configuration', async () => { const registeredNetworkConfiguration = @@ -3048,6 +3070,8 @@ describe('NetworkController', () => { const infuraNetworkNickname = NetworkNickname[infuraNetworkType]; const infuraChainId = ChainId[infuraNetworkType]; + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions it(`throws if rpcEndpoints contains an Infura RPC endpoint which is already present in the network configuration for the Infura-supported chain ${infuraChainId}`, async () => { const infuraRpcEndpoint = buildInfuraRpcEndpoint(infuraNetworkType); @@ -3408,6 +3432,8 @@ describe('NetworkController', () => { name: infuraNetworkNickname, networkClientId: infuraNetworkType, type: RpcEndpointType.Infura as const, + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions url: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}` as const, }, ], @@ -3436,6 +3462,8 @@ describe('NetworkController', () => { name: infuraNetworkNickname, networkClientId: infuraNetworkType, type: RpcEndpointType.Infura as const, + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions url: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`, }, ], @@ -3481,6 +3509,8 @@ describe('NetworkController', () => { name: infuraNetworkNickname, networkClientId: infuraNetworkType, type: RpcEndpointType.Infura as const, + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions url: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}` as const, }, ], @@ -3498,6 +3528,8 @@ describe('NetworkController', () => { name: infuraNetworkNickname, networkClientId: infuraNetworkType, type: RpcEndpointType.Infura as const, + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions url: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`, }, ], @@ -3537,6 +3569,8 @@ describe('NetworkController', () => { name: infuraNetworkNickname, networkClientId: infuraNetworkType, type: RpcEndpointType.Infura as const, + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions url: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}` as const, }, ], @@ -3554,6 +3588,8 @@ describe('NetworkController', () => { name: infuraNetworkNickname, networkClientId: infuraNetworkType, type: RpcEndpointType.Infura as const, + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions url: `https://${infuraNetworkType}.infura.io/v3/{infuraProjectId}`, }, ], @@ -4181,6 +4217,8 @@ describe('NetworkController', () => { const infuraNetworkNickname = NetworkNickname[infuraNetworkType]; const infuraChainId = ChainId[infuraNetworkType]; + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions it(`throws if an Infura RPC endpoint is being added which is already present in the network configuration for the Infura-supported chain ${infuraChainId}`, async () => { const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ chainId: '0x1337', @@ -4493,6 +4531,8 @@ describe('NetworkController', () => { const infuraChainId = ChainId[infuraNetworkType]; const infuraNativeTokenName = NetworksTicker[infuraNetworkType]; + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions describe(`if the existing chain ID is the Infura-supported chain ${infuraChainId} and is not being changed`, () => { describe('when a new Infura RPC endpoint is being added', () => { it('creates and registers a new network client for the RPC endpoint', async () => { @@ -8250,7 +8290,11 @@ describe('NetworkController', () => { const anotherInfuraNetworkNickname = NetworkNickname[anotherInfuraNetworkType]; + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions describe(`if the chain ID is being changed from a non-Infura-supported chain to the Infura-supported chain ${infuraChainId}`, () => { + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions it(`throws if a network configuration for the Infura network "${infuraNetworkNickname}" is already registered under the new chain ID`, async () => { const networkConfigurationToUpdate = buildCustomNetworkConfiguration({ chainId: '0x1337' }); @@ -8868,6 +8912,8 @@ describe('NetworkController', () => { }); }); + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions describe(`if the chain ID is being changed from the Infura-supported chain ${infuraChainId} to a non-Infura-supported chain`, () => { it('throws if a network configuration for a custom network is already registered under the new chain ID', async () => { const networkConfigurationToUpdate = @@ -8903,6 +8949,8 @@ describe('NetworkController', () => { chainId: '0x1337', }), ).rejects.toThrow( + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions `Cannot move network from chain ${infuraChainId} to 0x1337 as another network for that chain already exists ('Some Network')`, ); }, @@ -9532,7 +9580,11 @@ describe('NetworkController', () => { }); }); + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions describe(`if the chain ID is being changed from the Infura-supported chain ${infuraChainId} to a different Infura-supported chain ${anotherInfuraChainId}`, () => { + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions it(`throws if a network configuration for the Infura network "${infuraNetworkNickname}" is already registered under the new chain ID`, async () => { const networkConfigurationToUpdate = buildInfuraNetworkConfiguration(infuraNetworkType); @@ -11353,6 +11405,8 @@ describe('NetworkController', () => { describe('rollbackToPreviousProvider', () => { describe('when called not following any network switches', () => { for (const infuraNetworkType of Object.values(InfuraNetworkType)) { + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions describe(`when the selected network client represents the Infura network "${infuraNetworkType}"`, () => { refreshNetworkTests({ expectedNetworkClientConfiguration: @@ -11401,6 +11455,8 @@ describe('NetworkController', () => { const infuraChainId = ChainId[infuraNetworkType]; const infuraNativeTokenName = NetworksTicker[infuraNetworkType]; + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions describe(`when called following a switch away from the Infura network "${infuraNetworkType}"`, () => { it('emits networkWillChange with state payload', async () => { await withController( @@ -11630,6 +11686,8 @@ describe('NetworkController', () => { ); }); + // False negative - this is a string. + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions it(`initializes a provider pointed to the "${infuraNetworkType}" Infura network`, async () => { const infuraProjectId = 'some-infura-project-id'; From 451515b0b25878e9c577c3990bf1e36cdc9a0041 Mon Sep 17 00:00:00 2001 From: Michele Esposito Date: Wed, 14 Aug 2024 13:46:45 +0200 Subject: [PATCH 45/49] fix: lint --- packages/ens-controller/src/EnsController.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ens-controller/src/EnsController.test.ts b/packages/ens-controller/src/EnsController.test.ts index 3c9a2e0bb86..00f384ddfe5 100644 --- a/packages/ens-controller/src/EnsController.test.ts +++ b/packages/ens-controller/src/EnsController.test.ts @@ -6,8 +6,8 @@ import { InfuraNetworkType, } from '@metamask/controller-utils'; import { - NetworkController, - NetworkState, + type NetworkController, + type NetworkState, getDefaultNetworkControllerState, } from '@metamask/network-controller'; From 1a9335f863a2e7a003092fa82d6aec6785ce9ee0 Mon Sep 17 00:00:00 2001 From: Brian Bergeron Date: Thu, 22 Aug 2024 06:44:21 -0700 Subject: [PATCH 46/49] update selected network controller --- .../src/SelectedNetworkController.ts | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/selected-network-controller/src/SelectedNetworkController.ts b/packages/selected-network-controller/src/SelectedNetworkController.ts index 5fd617e6745..3cb8f99ed03 100644 --- a/packages/selected-network-controller/src/SelectedNetworkController.ts +++ b/packages/selected-network-controller/src/SelectedNetworkController.ts @@ -191,14 +191,25 @@ export class SelectedNetworkController extends BaseController< this.messagingSystem.subscribe( 'NetworkController:stateChange', - ({ selectedNetworkClientId }, patches) => { + ( + { selectedNetworkClientId, networkConfigurationsByChainId }, + patches, + ) => { patches.forEach(({ op, path }) => { - // if a network is removed, update the networkClientId for all domains that were using it to the selected network - if (op === 'remove' && path[0] === 'networkConfigurations') { - const removedNetworkClientId = path[1] as NetworkClientId; + // if a network is updated or removed, update domains that were referencing a networkClientId that is now deleted + if ( + (op === 'replace' || op === 'remove') && + path[0] === 'networkConfigurationsByChainId' + ) { + const allNetworkClientIds = Object.values( + networkConfigurationsByChainId, + ).flatMap((network) => + network.rpcEndpoints.map((endpoint) => endpoint.networkClientId), + ); + Object.entries(this.state.domains).forEach( ([domain, networkClientIdForDomain]) => { - if (networkClientIdForDomain === removedNetworkClientId) { + if (!allNetworkClientIds.includes(networkClientIdForDomain)) { this.setNetworkClientIdForDomain( domain, selectedNetworkClientId, From b59ee0c80be54b353033f5fbfa156ebb8fec2849 Mon Sep 17 00:00:00 2001 From: Brian Bergeron Date: Tue, 27 Aug 2024 12:19:49 -0700 Subject: [PATCH 47/49] Fix https://github.com/MetaMask/core/pull/4286#discussion_r1732085702 --- packages/network-controller/src/NetworkController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index 4eeb6b79666..eb42b544c2b 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -2401,7 +2401,7 @@ export class NetworkController extends BaseController< this.#networkConfigurationsByNetworkClientId = buildNetworkConfigurationsByNetworkClientId( - this.state.networkConfigurationsByChainId, + state.networkConfigurationsByChainId, ); } From 02e7ff99c640127d9b08c80f0951f1141c16b3c6 Mon Sep 17 00:00:00 2001 From: Brian Bergeron Date: Thu, 29 Aug 2024 15:22:58 -0700 Subject: [PATCH 48/49] fix for `Cannot perform 'get' on a proxy that has been revoked` --- .../src/NetworkController.ts | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index eb42b544c2b..0216ef405b2 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -1558,6 +1558,11 @@ export class NetworkController extends BaseController< }); }); + this.#networkConfigurationsByNetworkClientId = + buildNetworkConfigurationsByNetworkClientId( + this.state.networkConfigurationsByChainId, + ); + this.messagingSystem.publish( `${controllerName}:networkAdded`, newNetworkConfiguration, @@ -1838,6 +1843,11 @@ export class NetworkController extends BaseController< }); } + this.#networkConfigurationsByNetworkClientId = + buildNetworkConfigurationsByNetworkClientId( + this.state.networkConfigurationsByChainId, + ); + this.#unregisterNetworkClientsAsNeeded({ networkClientOperations, autoManagedNetworkClientRegistry, @@ -1896,6 +1906,11 @@ export class NetworkController extends BaseController< existingNetworkConfiguration, }); }); + + this.#networkConfigurationsByNetworkClientId = + buildNetworkConfigurationsByNetworkClientId( + this.state.networkConfigurationsByChainId, + ); } /** @@ -2398,11 +2413,6 @@ export class NetworkController extends BaseController< state.networkConfigurationsByChainId[args.networkFields.chainId] = args.networkConfigurationToPersist; } - - this.#networkConfigurationsByNetworkClientId = - buildNetworkConfigurationsByNetworkClientId( - state.networkConfigurationsByChainId, - ); } /** From 21bb4d4394d0f1e3daa3d633df0b02d82f6b916a Mon Sep 17 00:00:00 2001 From: Michele Esposito <34438276+mikesposito@users.noreply.github.com> Date: Mon, 2 Sep 2024 12:25:06 +0200 Subject: [PATCH 49/49] fix: `SelectedNetworkController` network stateChange listener (#4638) ## Explanation Currently, `SelectedNetworkController` subscribes to the `NetworkController:stateChange` event to make sure that network client ids associated with managed domains stay valid. Though, we are currently checking on the specific immer patch applied to the NetworkController state: this means that based on how the state draft was manipulated on the origin controller, we may or may not capture the change and react to it. To fix it, now `SelectedNetworkController` will look at the actual content of the updated state, instead of relying on immer patches. To reduce the amount of event instances received by the controller, a selector is applied to the subscription, filtering out irrelevant instances. ## References ## Changelog ### `@metamask/network-controller` - **ADDED**: Added `getNetworkConfigurations`, `getAvailableNetworkClientIds` and `selectAvailableNetworkClientIds` selectors - These new selectors can be applied to messenger event subscriptions ### `@metamask/selected-network-controller` No consumer-faced changes ## Checklist - [ ] I've updated the test suite for new or updated code as appropriate - [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [ ] I've highlighted breaking changes using the "BREAKING" category above as appropriate --- packages/network-controller/CHANGELOG.md | 2 + packages/network-controller/jest.config.js | 8 +- packages/network-controller/package.json | 1 + .../src/NetworkController.ts | 42 +++++++++-- packages/network-controller/src/index.ts | 1 + .../tests/NetworkController.test.ts | 75 ++++++++++++++++++- .../src/SelectedNetworkController.ts | 43 ++++------- .../tests/SelectedNetworkController.test.ts | 52 +++++++------ yarn.lock | 8 ++ 9 files changed, 165 insertions(+), 67 deletions(-) diff --git a/packages/network-controller/CHANGELOG.md b/packages/network-controller/CHANGELOG.md index d59a30f58a4..cfbcd87e9be 100644 --- a/packages/network-controller/CHANGELOG.md +++ b/packages/network-controller/CHANGELOG.md @@ -20,6 +20,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `removeNetwork`, which replaces `removeNetworkConfiguration` and can be used to remove existing network clients for a chain ([#4268](https://github.com/MetaMask/core/pull/4286)) - Add `getDefaultNetworkControllerState` function, which replaces `defaultState` and matches patterns in other controllers ([#4268](https://github.com/MetaMask/core/pull/4286)) - Add `RpcEndpointType`, `AddNetworkFields`, and `UpdateNetworkFields` types ([#4268](https://github.com/MetaMask/core/pull/4286)) +- Add `getNetworkConfigurations`, `getAvailableNetworkClientIds` and `selectAvailableNetworkClientIds` selectors ([#4268](https://github.com/MetaMask/core/pull/4286)) + - These new selectors can be applied to messenger event subscriptions ### Changed diff --git a/packages/network-controller/jest.config.js b/packages/network-controller/jest.config.js index 49230ba055d..621e01529f5 100644 --- a/packages/network-controller/jest.config.js +++ b/packages/network-controller/jest.config.js @@ -17,10 +17,10 @@ module.exports = merge(baseConfig, { // An object that configures minimum threshold enforcement for coverage results coverageThreshold: { global: { - branches: 76.57, - functions: 94.93, - lines: 91.78, - statements: 91.36, + branches: 88.47, + functions: 97.43, + lines: 94.47, + statements: 94.29, }, }, diff --git a/packages/network-controller/package.json b/packages/network-controller/package.json index 3e62a719d76..ecf434eeb9f 100644 --- a/packages/network-controller/package.json +++ b/packages/network-controller/package.json @@ -56,6 +56,7 @@ "async-mutex": "^0.5.0", "immer": "^9.0.6", "loglevel": "^1.8.1", + "reselect": "^5.1.1", "uri-js": "^4.4.1", "uuid": "^8.3.2" }, diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index 0216ef405b2..6b42d86d273 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -23,6 +23,7 @@ import { isStrictHexString, hasProperty, isPlainObject } from '@metamask/utils'; import { strict as assert } from 'assert'; import type { Draft } from 'immer'; import type { Logger } from 'loglevel'; +import { createSelector } from 'reselect'; import * as URI from 'uri-js'; import { inspect } from 'util'; import { v4 as uuidV4 } from 'uuid'; @@ -555,6 +556,39 @@ export function getDefaultNetworkControllerState(): NetworkState { }; } +/** + * Get a list of all network configurations. + * + * @param state - NetworkController state + * @returns A list of all available network configurations + */ +export function getNetworkConfigurations( + state: NetworkState, +): NetworkConfiguration[] { + return Object.values(state.networkConfigurationsByChainId); +} + +/** + * Get a list of all available client IDs from a list of + * network configurations + * @param networkConfigurations - The array of network configurations + * @returns A list of all available client IDs + */ +export function getAvailableNetworkClientIds( + networkConfigurations: NetworkConfiguration[], +): string[] { + return networkConfigurations.flatMap((networkConfiguration) => + networkConfiguration.rpcEndpoints.map( + (rpcEndpoint) => rpcEndpoint.networkClientId, + ), + ); +} + +export const selectAvailableNetworkClientIds = createSelector( + [getNetworkConfigurations], + getAvailableNetworkClientIds, +); + /** * The collection of auto-managed network clients that map to Infura networks. */ @@ -706,13 +740,7 @@ function validateNetworkControllerState(state: NetworkState) { const networkConfigurationEntries = Object.entries( state.networkConfigurationsByChainId, ); - const networkClientIds = Object.values( - state.networkConfigurationsByChainId, - ).flatMap((networkConfiguration) => - networkConfiguration.rpcEndpoints.map( - (rpcEndpoint) => rpcEndpoint.networkClientId, - ), - ); + const networkClientIds = selectAvailableNetworkClientIds(state); if (networkConfigurationEntries.length === 0) { throw new Error( diff --git a/packages/network-controller/src/index.ts b/packages/network-controller/src/index.ts index b5c2bf83fa6..e05622fc65c 100644 --- a/packages/network-controller/src/index.ts +++ b/packages/network-controller/src/index.ts @@ -33,6 +33,7 @@ export type { } from './NetworkController'; export { getDefaultNetworkControllerState, + selectAvailableNetworkClientIds, knownKeysOf, NetworkController, RpcEndpointType, diff --git a/packages/network-controller/tests/NetworkController.test.ts b/packages/network-controller/tests/NetworkController.test.ts index b9840da2f76..8ada6adaa05 100644 --- a/packages/network-controller/tests/NetworkController.test.ts +++ b/packages/network-controller/tests/NetworkController.test.ts @@ -29,13 +29,21 @@ import type { AutoManagedBuiltInNetworkClientRegistry, AutoManagedCustomNetworkClientRegistry, NetworkClientId, + NetworkConfiguration, NetworkControllerActions, NetworkControllerEvents, NetworkControllerOptions, NetworkControllerStateChangeEvent, NetworkState, } from '../src/NetworkController'; -import { NetworkController, RpcEndpointType } from '../src/NetworkController'; +import { + getAvailableNetworkClientIds, + getDefaultNetworkControllerState, + getNetworkConfigurations, + NetworkController, + RpcEndpointType, + selectAvailableNetworkClientIds, +} from '../src/NetworkController'; import type { NetworkClientConfiguration, Provider } from '../src/types'; import { NetworkClientType } from '../src/types'; import { @@ -12760,6 +12768,71 @@ describe('NetworkController', () => { }); }); +describe('getNetworkConfigurations', () => { + it('returns network configurations available in the state', () => { + const state = getDefaultNetworkControllerState(); + + expect(getNetworkConfigurations(state)).toStrictEqual( + Object.values(state.networkConfigurationsByChainId), + ); + }); +}); + +describe('getAvailableNetworkClientIds', () => { + it('returns network client ids available in the state', () => { + const networkConfigurations = [ + { + rpcEndpoints: [ + { + networkClientId: 'foo', + }, + ], + }, + { + rpcEndpoints: [ + { + networkClientId: 'bar', + }, + ], + }, + ] as NetworkConfiguration[]; + + expect(getAvailableNetworkClientIds(networkConfigurations)).toStrictEqual([ + 'foo', + 'bar', + ]); + }); +}); + +describe('selectAvailableNetworkClientIds', () => { + it('selects all network client ids available in the state', () => { + const state = { + ...getDefaultNetworkControllerState(), + networkConfigurationsByChainId: { + '0x12': { + rpcEndpoints: [ + { + networkClientId: 'foo', + }, + ], + } as NetworkConfiguration, + '0x34': { + rpcEndpoints: [ + { + networkClientId: 'bar', + }, + ], + } as NetworkConfiguration, + }, + }; + + expect(selectAvailableNetworkClientIds(state)).toStrictEqual([ + 'foo', + 'bar', + ]); + }); +}); + /** * Creates a mocked version of `createNetworkClient` where multiple mock * invocations can be specified. A default implementation is provided so that if diff --git a/packages/selected-network-controller/src/SelectedNetworkController.ts b/packages/selected-network-controller/src/SelectedNetworkController.ts index 3cb8f99ed03..5c10acf4074 100644 --- a/packages/selected-network-controller/src/SelectedNetworkController.ts +++ b/packages/selected-network-controller/src/SelectedNetworkController.ts @@ -9,6 +9,7 @@ import type { NetworkControllerStateChangeEvent, ProviderProxy, } from '@metamask/network-controller'; +import { selectAvailableNetworkClientIds } from '@metamask/network-controller'; import type { PermissionControllerStateChange, GetSubjects as PermissionControllerGetSubjectsAction, @@ -191,35 +192,21 @@ export class SelectedNetworkController extends BaseController< this.messagingSystem.subscribe( 'NetworkController:stateChange', - ( - { selectedNetworkClientId, networkConfigurationsByChainId }, - patches, - ) => { - patches.forEach(({ op, path }) => { - // if a network is updated or removed, update domains that were referencing a networkClientId that is now deleted - if ( - (op === 'replace' || op === 'remove') && - path[0] === 'networkConfigurationsByChainId' - ) { - const allNetworkClientIds = Object.values( - networkConfigurationsByChainId, - ).flatMap((network) => - network.rpcEndpoints.map((endpoint) => endpoint.networkClientId), - ); - - Object.entries(this.state.domains).forEach( - ([domain, networkClientIdForDomain]) => { - if (!allNetworkClientIds.includes(networkClientIdForDomain)) { - this.setNetworkClientIdForDomain( - domain, - selectedNetworkClientId, - ); - } - }, - ); - } - }); + (availableNetworkClientIds) => { + // if a network is updated or removed, update the networkClientId for all domains + // that were using it to the selected network client id + const { selectedNetworkClientId } = this.messagingSystem.call( + 'NetworkController:getState', + ); + Object.entries(this.state.domains).forEach( + ([domain, networkClientIdForDomain]) => { + if (!availableNetworkClientIds.includes(networkClientIdForDomain)) { + this.setNetworkClientIdForDomain(domain, selectedNetworkClientId); + } + }, + ); }, + selectAvailableNetworkClientIds, ); onPreferencesStateChange(({ useRequestQueue }) => { diff --git a/packages/selected-network-controller/tests/SelectedNetworkController.test.ts b/packages/selected-network-controller/tests/SelectedNetworkController.test.ts index 90d92c5fb73..9dd3fb2d1a2 100644 --- a/packages/selected-network-controller/tests/SelectedNetworkController.test.ts +++ b/packages/selected-network-controller/tests/SelectedNetworkController.test.ts @@ -1,7 +1,9 @@ import { ControllerMessenger } from '@metamask/base-controller'; -import type { - ProviderProxy, - BlockTrackerProxy, +import { + type ProviderProxy, + type BlockTrackerProxy, + type NetworkState, + getDefaultNetworkControllerState, } from '@metamask/network-controller'; import { createEventEmitterProxy } from '@metamask/swappable-obj-proxy'; @@ -293,31 +295,29 @@ describe('SelectedNetworkController', () => { describe('NetworkController:stateChange', () => { describe('when a networkClient is deleted from the network controller state', () => { it('does not update state when useRequestQueuePreference is false', () => { - const { controller, messenger } = setup({ + const { controller, messenger, mockNetworkControllerGetState } = setup({ state: { domains: {}, }, }); + const mockNetworkControllerStateUpdate: NetworkState = { + ...getDefaultNetworkControllerState(), + selectedNetworkClientId: 'goerli', + }; + mockNetworkControllerGetState.mockReturnValueOnce( + mockNetworkControllerStateUpdate, + ); messenger.publish( 'NetworkController:stateChange', - { - selectedNetworkClientId: 'goerli', - networkConfigurationsByChainId: {}, - networksMetadata: {}, - }, - [ - { - op: 'remove', - path: ['networkConfigurations', 'test-network-client-id'], - }, - ], + mockNetworkControllerStateUpdate, + [], ); expect(controller.state.domains).toStrictEqual({}); }); it('updates the networkClientId for domains which were previously set to the deleted networkClientId when useRequestQueuePreference is true', () => { - const { controller, messenger } = setup({ + const { controller, messenger, mockNetworkControllerGetState } = setup({ state: { domains: { metamask: 'goerli', @@ -327,20 +327,18 @@ describe('SelectedNetworkController', () => { }, useRequestQueuePreference: true, }); + const mockNetworkControllerStateUpdate: NetworkState = { + ...getDefaultNetworkControllerState(), + selectedNetworkClientId: 'goerli', + }; + mockNetworkControllerGetState.mockReturnValueOnce( + mockNetworkControllerStateUpdate, + ); messenger.publish( 'NetworkController:stateChange', - { - selectedNetworkClientId: 'goerli', - networkConfigurationsByChainId: {}, - networksMetadata: {}, - }, - [ - { - op: 'remove', - path: ['networkConfigurations', 'test-network-client-id'], - }, - ], + mockNetworkControllerStateUpdate, + [], ); expect(controller.state.domains['example.com']).toBe('goerli'); expect(controller.state.domains['test.com']).toBe('goerli'); diff --git a/yarn.lock b/yarn.lock index 557d11a74d4..c2876acf503 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3198,6 +3198,7 @@ __metadata: lodash: "npm:^4.17.21" loglevel: "npm:^1.8.1" nock: "npm:^13.3.1" + reselect: "npm:^5.1.1" sinon: "npm:^9.2.4" ts-jest: "npm:^27.1.4" typedoc: "npm:^0.24.8" @@ -11388,6 +11389,13 @@ __metadata: languageName: node linkType: hard +"reselect@npm:^5.1.1": + version: 5.1.1 + resolution: "reselect@npm:5.1.1" + checksum: 10/1fdae11a39ed9c8d85a24df19517c8372ee24fefea9cce3fae9eaad8e9cefbba5a3d4940c6fe31296b6addf76e035588c55798f7e6e147e1b7c0855f119e7fa5 + languageName: node + linkType: hard + "resolve-cwd@npm:^3.0.0": version: 3.0.0 resolution: "resolve-cwd@npm:3.0.0"