From 5b5e5d222fba8ac66d6ef9f3e65df269546adb75 Mon Sep 17 00:00:00 2001 From: Dan Finlay <542863+danfinlay@users.noreply.github.com> Date: Mon, 15 May 2023 02:39:48 -0700 Subject: [PATCH 1/8] Skip injection on snaps iframe (#19096) * skip injection on snaps iframe * escape periods from regex block list These values are used as regex inputs, so plain periods are wild characters, and mean these can be interpreted as more domains than intended. * remove period escaping It's already escaped below. Whoops! Bad AI advice! --- shared/modules/provider-injection.js | 1 + 1 file changed, 1 insertion(+) diff --git a/shared/modules/provider-injection.js b/shared/modules/provider-injection.js index c4d74182347c..1fff0885433e 100644 --- a/shared/modules/provider-injection.js +++ b/shared/modules/provider-injection.js @@ -65,6 +65,7 @@ function documentElementCheck() { */ function blockedDomainCheck() { const blockedDomains = [ + 'execution.metamask.io', 'uscourts.gov', 'dropbox.com', 'webbyawards.com', From f95320d03ea2eedd1c250a1c934a0ecce66112fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Oliv=C3=A9?= Date: Mon, 15 May 2023 13:38:54 +0200 Subject: [PATCH 2/8] [MMI] Started adding styles imports with code fences (#17943) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Started adding styles imports with code fences * FIxed prettier issues * removed fences since they dont do anything * Update ui/components/ui/ui-components.scss Co-authored-by: Frederik Bolding --------- Co-authored-by: Antonio Regadas Co-authored-by: António Regadas Co-authored-by: Frederik Bolding --- ui/components/app/app-components.scss | 4 ++-- ui/components/app/app-header/index.scss | 2 -- .../custody-labels/{custody-labels.scss => index.scss} | 0 ...ractive-replacement-token-notification.scss => index.scss} | 0 ui/components/ui/ui-components.scss | 3 ++- ui/css/index.scss | 2 -- ui/pages/pages.scss | 4 ++-- 7 files changed, 6 insertions(+), 9 deletions(-) rename ui/components/institutional/custody-labels/{custody-labels.scss => index.scss} (100%) rename ui/components/institutional/interactive-replacement-token-notification/{interactive-replacement-token-notification.scss => index.scss} (100%) diff --git a/ui/components/app/app-components.scss b/ui/components/app/app-components.scss index 1a4b9eac349d..0ec09c41f719 100644 --- a/ui/components/app/app-components.scss +++ b/ui/components/app/app-components.scss @@ -105,8 +105,8 @@ @import 'network-account-balance-header/index'; @import 'approve-content-card/index'; @import 'transaction-alerts/transaction-alerts'; -///: BEGIN:ONLY_INCLUDE_IN(build-mmi) +@import '../institutional/compliance-details/index'; +@import '../institutional/interactive-replacement-token-notification/index'; @import '../institutional/confirm-remove-jwt-modal/index'; @import '../institutional/custody-confirm-link-modal/index'; @import '../institutional/transaction-failed-modal/index'; -///: END:ONLY_INCLUDE_IN diff --git a/ui/components/app/app-header/index.scss b/ui/components/app/app-header/index.scss index 4bcb08f634e8..a6838e33fb6b 100644 --- a/ui/components/app/app-header/index.scss +++ b/ui/components/app/app-header/index.scss @@ -34,7 +34,6 @@ } } - ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) &__custody-logo { &--icon { height: 16px; @@ -46,7 +45,6 @@ } } } - ///: END:ONLY_INCLUDE_IN &__contents { display: flex; diff --git a/ui/components/institutional/custody-labels/custody-labels.scss b/ui/components/institutional/custody-labels/index.scss similarity index 100% rename from ui/components/institutional/custody-labels/custody-labels.scss rename to ui/components/institutional/custody-labels/index.scss diff --git a/ui/components/institutional/interactive-replacement-token-notification/interactive-replacement-token-notification.scss b/ui/components/institutional/interactive-replacement-token-notification/index.scss similarity index 100% rename from ui/components/institutional/interactive-replacement-token-notification/interactive-replacement-token-notification.scss rename to ui/components/institutional/interactive-replacement-token-notification/index.scss diff --git a/ui/components/ui/ui-components.scss b/ui/components/ui/ui-components.scss index 6f012fb2a1dd..7ac0d578b272 100644 --- a/ui/components/ui/ui-components.scss +++ b/ui/components/ui/ui-components.scss @@ -60,4 +60,5 @@ @import 'deprecated-test-networks/index.scss'; @import 'nft-info/index.scss'; @import 'nft-collection-image/index'; - +@import '../institutional/custody-labels/index'; +@import '../institutional/note-to-trader/index'; diff --git a/ui/css/index.scss b/ui/css/index.scss index 1033eec1e164..2e5aa982c648 100644 --- a/ui/css/index.scss +++ b/ui/css/index.scss @@ -11,9 +11,7 @@ @import '../components/component-library/component-library-components.scss'; @import '../components/app/app-components'; @import '../components/ui/ui-components'; -///: BEGIN:ONLY_INCLUDE_IN(build-mmi) @import '../components/institutional/institutional-components'; -///: END:ONLY_INCLUDE_IN @import '../components/multichain/multichain-components.scss'; @import '../pages/pages'; @import './errors.scss'; diff --git a/ui/pages/pages.scss b/ui/pages/pages.scss index 26b50cbfbe03..41be4f4585a5 100644 --- a/ui/pages/pages.scss +++ b/ui/pages/pages.scss @@ -12,14 +12,14 @@ @import 'connected-accounts/index'; @import 'connected-sites/index'; @import 'create-account/index'; -///: BEGIN:ONLY_INCLUDE_IN(build-mmi) @import "institutional/connect-custody/index"; @import "institutional/custody/index"; @import "institutional/institutional-entity-done-page/index"; @import "institutional/compliance-feature-page/index"; @import "institutional/confirm-add-custodian-token/index"; @import "institutional/interactive-replacement-token-page/index"; -///: END:ONLY_INCLUDE_IN +@import 'institutional/interactive-replacement-token-page/index'; +@import 'institutional/confirm-add-custodian-token/index'; @import 'error/index'; @import 'send/gas-display/index'; @import 'home/index'; From e5fdb72acc03ce1932b7ad51c788f141a884acd9 Mon Sep 17 00:00:00 2001 From: David Walsh Date: Mon, 15 May 2023 10:15:39 -0500 Subject: [PATCH 3/8] UX: Multichain: Improve networks selector (#19083) * UX: Multichain: Improve networks selector * Update ui/selectors/selectors.js Co-authored-by: legobeat <109787230+legobeat@users.noreply.github.com> * Update ui/selectors/selectors.js Co-authored-by: legobeat <109787230+legobeat@users.noreply.github.com> * Update ui/selectors/selectors.js Co-authored-by: legobeat <109787230+legobeat@users.noreply.github.com> * Improvements --------- Co-authored-by: legobeat <109787230+legobeat@users.noreply.github.com> --- ui/selectors/selectors.js | 99 +++++++++++++++++++-------------------- 1 file changed, 47 insertions(+), 52 deletions(-) diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index 85cb57be3a56..c3549d0997ff 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -1170,61 +1170,56 @@ export function getAllEnabledNetworks(state) { export function getAllNetworks(state) { const networkConfigurations = getNetworkConfigurations(state) || {}; - const localhostFilter = (network) => network.chainId === CHAIN_IDS.LOCALHOST; - - const networks = []; - // Mainnet always first - networks.push({ - chainId: CHAIN_IDS.MAINNET, - nickname: MAINNET_DISPLAY_NAME, - rpcUrl: CHAIN_ID_TO_RPC_URL_MAP[CHAIN_IDS.MAINNET], - rpcPrefs: { - imageUrl: ETH_TOKEN_IMAGE_URL, + + const networks = [ + // Mainnet always first + { + chainId: CHAIN_IDS.MAINNET, + nickname: MAINNET_DISPLAY_NAME, + rpcUrl: CHAIN_ID_TO_RPC_URL_MAP[CHAIN_IDS.MAINNET], + rpcPrefs: { + imageUrl: ETH_TOKEN_IMAGE_URL, + }, + providerType: NETWORK_TYPES.MAINNET, + ticker: CURRENCY_SYMBOLS.ETH, }, - providerType: NETWORK_TYPES.MAINNET, - ticker: CURRENCY_SYMBOLS.ETH, - }); - // Custom networks added - networks.push( - ...Object.entries(networkConfigurations) - .filter( - ([, network]) => - !localhostFilter(network) && - network.chainId !== CHAIN_IDS.MAINNET && + // Custom networks added by the user + ...Object.values(networkConfigurations).filter( + ({ chainId }) => + ![ + CHAIN_IDS.LOCALHOST, + CHAIN_IDS.MAINNET, // Linea gets added as a custom network configuration so // we must ignore it here to display in test networks - network.chainId !== CHAIN_IDS.LINEA_TESTNET, - ) - .map(([, network]) => network), - ); - // Test networks - networks.push( - ...[ - { - chainId: CHAIN_IDS.GOERLI, - nickname: GOERLI_DISPLAY_NAME, - rpcUrl: CHAIN_ID_TO_RPC_URL_MAP[CHAIN_IDS.GOERLI], - providerType: NETWORK_TYPES.GOERLI, - ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.GOERLI], - }, - { - chainId: CHAIN_IDS.SEPOLIA, - nickname: SEPOLIA_DISPLAY_NAME, - rpcUrl: CHAIN_ID_TO_RPC_URL_MAP[CHAIN_IDS.SEPOLIA], - providerType: NETWORK_TYPES.SEPOLIA, - ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.SEPOLIA], - }, - { - chainId: CHAIN_IDS.LINEA_TESTNET, - nickname: LINEA_TESTNET_DISPLAY_NAME, - rpcUrl: CHAIN_ID_TO_RPC_URL_MAP[CHAIN_IDS.LINEA_TESTNET], - ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.LINEA_TESTNET], - }, - ], // Localhosts - ...Object.entries(networkConfigurations) - .filter(([, network]) => localhostFilter(network)) - .map(([, network]) => network), - ); + CHAIN_IDS.LINEA_TESTNET, + ].includes(chainId), + ), + // Test networks + { + chainId: CHAIN_IDS.GOERLI, + nickname: GOERLI_DISPLAY_NAME, + rpcUrl: CHAIN_ID_TO_RPC_URL_MAP[CHAIN_IDS.GOERLI], + providerType: NETWORK_TYPES.GOERLI, + ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.GOERLI], + }, + { + chainId: CHAIN_IDS.SEPOLIA, + nickname: SEPOLIA_DISPLAY_NAME, + rpcUrl: CHAIN_ID_TO_RPC_URL_MAP[CHAIN_IDS.SEPOLIA], + providerType: NETWORK_TYPES.SEPOLIA, + ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.SEPOLIA], + }, + { + chainId: CHAIN_IDS.LINEA_TESTNET, + nickname: LINEA_TESTNET_DISPLAY_NAME, + rpcUrl: CHAIN_ID_TO_RPC_URL_MAP[CHAIN_IDS.LINEA_TESTNET], + ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.LINEA_TESTNET], + }, + // Localhosts + ...Object.values(networkConfigurations).filter( + ({ chainId }) => chainId === CHAIN_IDS.LOCALHOST, + ), + ]; return networks; } From e29faca3a6a39d1c92577b36ae670989c658795f Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Mon, 15 May 2023 13:34:34 -0230 Subject: [PATCH 4/8] Refactor `lookupNetwork` unit tests (#19070) The `lookupNetwork` unit tests have been updated to expand test coverage and match the unit tests for the core network controller. A helper function `lookupNetworkTests` has been copied from core. It covers most of the behavior of the function. Vidation tests and functional tests not covered in core have been retained, but any tests that are now redundant have been deleted. Relates to #1197 --- .../network/network-controller.test.ts | 10224 ++++++++-------- 1 file changed, 4854 insertions(+), 5370 deletions(-) diff --git a/app/scripts/controllers/network/network-controller.test.ts b/app/scripts/controllers/network/network-controller.test.ts index 2099e427e564..7ceb32d2a41d 100644 --- a/app/scripts/controllers/network/network-controller.test.ts +++ b/app/scripts/controllers/network/network-controller.test.ts @@ -15,6 +15,7 @@ import { NetworkControllerEventType, NetworkControllerOptions, NetworkControllerState, + ProviderConfiguration, } from './network-controller'; import { createNetworkClient, @@ -86,19 +87,19 @@ const DEFAULT_INFURA_PROJECT_ID = 'fake-infura-project-id'; const INFURA_NETWORKS = [ { networkType: NETWORK_TYPES.MAINNET, - chainId: '0x1', + chainId: '0x1' as const, ticker: 'ETH', blockExplorerUrl: 'https://etherscan.io', }, { networkType: NETWORK_TYPES.GOERLI, - chainId: '0x5', + chainId: '0x5' as const, ticker: 'GoerliETH', blockExplorerUrl: 'https://goerli.etherscan.io', }, { networkType: NETWORK_TYPES.SEPOLIA, - chainId: '0xaa36a7', + chainId: '0xaa36a7' as const, ticker: 'SepoliaETH', blockExplorerUrl: 'https://sepolia.etherscan.io', }, @@ -1252,8 +1253,8 @@ describe('NetworkController', () => { INFURA_NETWORKS.forEach(({ networkType }) => { describe(`when the type in the provider configuration is "${networkType}"`, () => { - describe('if the request for eth_getBlockByNumber responds successfully', () => { - it('stores the fact that the network is available', async () => { + 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: { @@ -1263,27 +1264,90 @@ describe('NetworkController', () => { // the network selected, it just needs to exist chainId: '0x9999999', }, + networkConfigurations: { + testNetworkConfigurationId: { + id: 'testNetworkConfigurationId', + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', + ticker: 'ABC', + }, + }, }, + infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'net_version', + }, + response: { + result: '1', + }, }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingLookupNetwork({ + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + }, + { + request: { + method: 'net_version', + }, + response: { + result: '1', + }, + }, + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + beforeCompleting: async () => { + await controller.setActiveNetwork( + 'testNetworkConfigurationId', + ); + }, + }, + ]), + buildFakeProvider([ + { + request: { + method: 'net_version', + }, + error: GENERIC_JSON_RPC_ERROR, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + network: networkType, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://mock-rpc-url', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + + await waitForStateChanges({ controller, + propertyPath: ['networkStatus'], operation: async () => { await controller.initializeProvider(); }, }); expect(controller.store.getState().networkStatus).toBe( - 'unknown', + 'available', ); await waitForStateChanges({ @@ -1293,15 +1357,14 @@ describe('NetworkController', () => { await controller.lookupNetwork(); }, }); - expect(controller.store.getState().networkStatus).toBe( - 'available', + 'unknown', ); }, ); }); - it('stores the ID of the network', async () => { + it('stores the ID of the second network, not the first', async () => { await withController( { state: { @@ -1311,28 +1374,74 @@ describe('NetworkController', () => { // the network selected, it just needs to exist chainId: '0x9999999', }, + networkConfigurations: { + testNetworkConfigurationId: { + id: 'testNetworkConfigurationId', + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', + ticker: 'ABC', + }, + }, }, + infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'net_version', + }, + response: { + result: '111', + }, }, - response: { - result: '1', + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + beforeCompleting: async () => { + await controller.setActiveNetwork( + 'testNetworkConfigurationId', + ); + }, }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + ]), + buildFakeProvider([ + { + request: { + method: 'net_version', + }, + response: { + result: '222', + }, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + network: networkType, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://mock-rpc-url', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); await withoutCallingLookupNetwork({ controller, operation: async () => { await controller.initializeProvider(); }, }); - expect(controller.store.getState().networkId).toBeNull(); await waitForStateChanges({ controller, @@ -1342,12 +1451,12 @@ describe('NetworkController', () => { }, }); - expect(controller.store.getState().networkId).toBe('1'); + expect(controller.store.getState().networkId).toBe('222'); }, ); }); - it('stores the fact that the network supports EIP-1559 when baseFeePerGas is in the block header', async () => { + it('stores the EIP-1559 support of the second network, not the first', async () => { await withController( { state: { @@ -1357,24 +1466,62 @@ describe('NetworkController', () => { // the network selected, it just needs to exist chainId: '0x9999999', }, - networkDetails: { - EIPS: {}, + networkConfigurations: { + testNetworkConfigurationId: { + id: 'testNetworkConfigurationId', + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', + ticker: 'ABC', + }, }, }, + infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'eth_getBlockByNumber', + }, + response: { + result: POST_1559_BLOCK, + }, + beforeCompleting: async () => { + await controller.setActiveNetwork( + 'testNetworkConfigurationId', + ); + }, }, - response: { - result: POST_1559_BLOCK, + ]), + buildFakeProvider([ + { + request: { + method: 'eth_getBlockByNumber', + }, + response: { + result: PRE_1559_BLOCK, + }, }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + network: networkType, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://mock-rpc-url', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); await withoutCallingLookupNetwork({ controller, operation: async () => { @@ -1391,168 +1538,228 @@ describe('NetworkController', () => { }); expect( - controller.store.getState().networkDetails.EIPS[1559], - ).toBeTruthy(); + controller.store.getState().networkDetails, + ).toStrictEqual({ + EIPS: { + 1559: false, + }, + }); }, ); }); - it('stores the fact that the network does not support EIP-1559 when baseFeePerGas is not in the block header', async () => { + it('emits infuraIsBlocked, not infuraIsUnblocked, if the second network is blocked, even if the first one is not', async () => { + const anotherNetwork = INFURA_NETWORKS.find( + (network) => network.networkType !== networkType, + ); + /* eslint-disable-next-line jest/no-if */ + if (!anotherNetwork) { + throw new Error( + "Could not find another network to use. You've probably commented out all INFURA_NETWORKS but one. Please uncomment another one.", + ); + } + await withController( { state: { providerConfig: { type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist + // NOTE: This doesn't need to match the logical chain ID + // of the network selected, it just needs to exist chainId: '0x9999999', }, - networkDetails: { - EIPS: {}, - }, }, + infuraProjectId: 'some-infura-project-id', }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: PRE_1559_BLOCK, + async ({ controller, messenger }) => { + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + beforeCompleting: async () => { + await controller.setProviderType( + anotherNetwork.networkType, + ); + }, }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect( - controller.store.getState().networkDetails.EIPS[1559], - ).toBe(false); - }, - ); - }); - - it('emits infuraIsUnblocked', async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID - // of the network selected, it just needs to exist - chainId: '0x9999999', - }, - }, - }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, + ]), + buildFakeProvider([ + { + request: { + method: 'eth_getBlockByNumber', + }, + error: BLOCKED_INFURA_JSON_RPC_ERROR, }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + network: networkType, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + }) + .mockReturnValueOnce(fakeNetworkClients[0]) + .calledWith({ + network: anotherNetwork.networkType, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + }) + .mockReturnValueOnce(fakeNetworkClients[1]); await withoutCallingLookupNetwork({ controller, operation: async () => { await controller.initializeProvider(); }, }); - - const infuraIsUnblocked = await waitForPublishedEvents({ + const promiseForNoInfuraIsUnblockedEvents = + waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + count: 0, + }); + const promiseForInfuraIsBlocked = waitForPublishedEvents({ messenger, - eventType: NetworkControllerEventType.InfuraIsUnblocked, - operation: async () => { - await controller.lookupNetwork(); - }, + eventType: NetworkControllerEventType.InfuraIsBlocked, }); - expect(infuraIsUnblocked).toBeTruthy(); + await controller.lookupNetwork(); + + expect(await promiseForNoInfuraIsUnblockedEvents).toBeTruthy(); + expect(await promiseForInfuraIsBlocked).toBeTruthy(); }, ); }); }); - describe('if the request for eth_blockNumber responds with a "countryBlocked" error', () => { - it('stores the fact that the network is blocked', async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID - // of the network selected, it just needs to exist - chainId: '0x9999999', - }, + lookupNetworkTests({ + expectedProviderConfig: buildProviderConfig({ type: networkType }), + initialState: { + providerConfig: buildProviderConfig({ type: networkType }), + }, + operation: async (controller) => { + await controller.lookupNetwork(); + }, + }); + }); + }); + + describe('when the type in the provider configuration is "rpc"', () => { + describe('if the network was switched after the net_version request started but before it completed', () => { + it('stores the network status of the second network, not the first', async () => { + await withController( + { + state: { + providerConfig: { + type: 'rpc', + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', }, }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ + infuraProjectId: 'some-infura-project-id', + }, + async ({ controller }) => { + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'net_version', + }, + response: SUCCESSFUL_NET_VERSION_RESPONSE, + }, { request: { method: 'eth_getBlockByNumber', }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); + { + request: { + method: 'net_version', + }, + response: SUCCESSFUL_NET_VERSION_RESPONSE, + beforeCompleting: async () => { + await controller.setProviderType('goerli'); + }, }, - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.lookupNetwork(); + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, }, - }); + ]), + buildFakeProvider([ + { + request: { + method: 'eth_getBlockByNumber', + }, + error: GENERIC_JSON_RPC_ERROR, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://mock-rpc-url', + type: NetworkClientType.Custom, + }) + .mockReturnValueOnce(fakeNetworkClients[0]) + .calledWith({ + network: NETWORK_TYPES.GOERLI, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + }) + .mockReturnValueOnce(fakeNetworkClients[1]); + await waitForStateChanges({ + controller, + propertyPath: ['networkStatus'], + operation: async () => { + await controller.initializeProvider(); + }, + }); + expect(controller.store.getState().networkStatus).toBe( + 'available', + ); - expect(controller.store.getState().networkStatus).toBe( - 'blocked', - ); - }, - ); - }); + await waitForStateChanges({ + controller, + propertyPath: ['networkStatus'], + operation: async () => { + await controller.lookupNetwork(); + }, + }); + expect(controller.store.getState().networkStatus).toBe('unknown'); + }, + ); + }); - it('clears the ID of the network from state', async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x9999999', - }, + it('stores the ID of the second network, not the first', async () => { + await withController( + { + state: { + providerConfig: { + type: 'rpc', + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', + ticker: 'RPC', }, }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ + infuraProjectId: 'some-infura-project-id', + }, + async ({ controller }) => { + const fakeProviders = [ + buildFakeProvider([ { request: { method: 'net_version', @@ -1572,345 +1779,218 @@ describe('NetworkController', () => { method: 'net_version', }, response: { - result: '2', + result: '1', + }, + beforeCompleting: async () => { + await controller.setProviderType('goerli'); }, }, { request: { method: 'eth_getBlockByNumber', }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await waitForStateChanges({ - controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.initializeProvider(); + ]), + buildFakeProvider([ + { + request: { + method: 'net_version', + }, + response: { + result: '2', + }, }, - }); - expect(controller.store.getState().networkId).toBe('1'); - - await waitForStateChanges({ - controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - expect(controller.store.getState().networkId).toBeNull(); - }, - ); - }); - - it('clears whether the network supports EIP-1559 from state along with any other network details', async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x9999999', - }, - networkDetails: { - EIPS: { - 1559: true, - }, - other: 'details', - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - expect( - controller.store.getState().networkDetails, - ).toStrictEqual({ - EIPS: { - 1559: undefined, - }, - }); - }, - ); - }); - - it('emits infuraIsBlocked', async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID - // of the network selected, it just needs to exist - chainId: '0x9999999', + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://mock-rpc-url', + type: NetworkClientType.Custom, + }) + .mockReturnValueOnce(fakeNetworkClients[0]) + .calledWith({ + network: NETWORK_TYPES.GOERLI, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + }) + .mockReturnValueOnce(fakeNetworkClients[1]); + await waitForStateChanges({ + controller, + propertyPath: ['networkId'], + operation: async () => { + await controller.initializeProvider(); }, - }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); - - const infuraIsBlocked = await waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.InfuraIsBlocked, - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(infuraIsBlocked).toBeTruthy(); - }, - ); - }); + }); + expect(controller.store.getState().networkId).toBe('1'); - it('does not emit infuraIsUnblocked', async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID - // of the network selected, it just needs to exist - chainId: '0x9999999', - }, + await waitForStateChanges({ + controller, + propertyPath: ['networkId'], + operation: async () => { + await controller.lookupNetwork(); }, - }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); - - const promiseForNoInfuraIsUnblockedEvents = - waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.InfuraIsUnblocked, - count: 0, - operation: async () => { - await controller.lookupNetwork(); - }, - }); + }); - expect(await promiseForNoInfuraIsUnblockedEvents).toBeTruthy(); - }, - ); - }); + expect(controller.store.getState().networkId).toBe('2'); + }, + ); }); - describe('if the request for eth_getBlockByNumber responds with a generic error', () => { - it('stores the network status as unavailable if the error does not translate to an internal RPC error', async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x9999999', - }, + it('stores the EIP-1559 support of the second network, not the first', async () => { + await withController( + { + state: { + providerConfig: { + type: 'rpc', + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', + ticker: 'RPC', + }, + networkDetails: { + EIPS: {}, + other: 'details', }, }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ + infuraProjectId: 'some-infura-project-id', + }, + async ({ controller }) => { + const fakeProviders = [ + buildFakeProvider([ { request: { method: 'net_version', }, - response: { - result: '1', - }, + response: SUCCESSFUL_NET_VERSION_RESPONSE, }, { request: { method: 'eth_getBlockByNumber', }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - { - request: { - method: 'net_version', - }, response: { - result: '2', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', + result: POST_1559_BLOCK, }, - error: ethErrors.rpc.methodNotFound(), - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.initializeProvider(); - }, - }); - expect(controller.store.getState().networkStatus).toBe( - 'available', - ); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - expect(controller.store.getState().networkStatus).toBe( - 'unavailable', - ); - }, - ); - }); - - it('stores the network status as unknown if the error translates to an internal RPC error', async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x9999999', }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ { request: { method: 'net_version', }, - response: { - result: '1', + response: SUCCESSFUL_NET_VERSION_RESPONSE, + beforeCompleting: async () => { + await controller.setProviderType('goerli'); }, }, { request: { method: 'eth_getBlockByNumber', }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + response: { + result: POST_1559_BLOCK, + }, }, + ]), + buildFakeProvider([ { request: { method: 'net_version', }, - response: { - result: '2', - }, + response: SUCCESSFUL_NET_VERSION_RESPONSE, }, { request: { method: 'eth_getBlockByNumber', }, - error: GENERIC_JSON_RPC_ERROR, + response: { + result: PRE_1559_BLOCK, + }, }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://mock-rpc-url', + type: NetworkClientType.Custom, + }) + .mockReturnValueOnce(fakeNetworkClients[0]) + .calledWith({ + network: NETWORK_TYPES.GOERLI, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + }) + .mockReturnValueOnce(fakeNetworkClients[1]); + await waitForStateChanges({ + controller, + propertyPath: ['networkDetails'], + operation: async () => { + await controller.initializeProvider(); + }, + }); + expect(controller.store.getState().networkDetails).toStrictEqual({ + EIPS: { + 1559: true, + }, + other: 'details', + }); - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.initializeProvider(); - }, - }); - expect(controller.store.getState().networkStatus).toBe( - 'available', - ); + await waitForStateChanges({ + controller, + propertyPath: ['networkDetails'], + // setProviderType clears networkDetails first, and then updates + // it to what we expect it to be + count: 2, + operation: async () => { + await controller.lookupNetwork(); + }, + }); - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - expect(controller.store.getState().networkStatus).toBe( - 'unknown', - ); - }, - ); - }); + expect(controller.store.getState().networkDetails).toStrictEqual({ + EIPS: { + 1559: false, + }, + }); + }, + ); + }); - it('clears the ID of the network from state', async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x9999999', - }, + it('emits infuraIsBlocked, not infuraIsUnblocked, if the second network is blocked, even if the first one is not', async () => { + await withController( + { + state: { + providerConfig: { + type: 'rpc', + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', + ticker: 'RPC', }, }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ + infuraProjectId: 'some-infura-project-id', + }, + async ({ controller, messenger }) => { + const fakeProviders = [ + buildFakeProvider([ { request: { method: 'net_version', }, - response: { - result: '1', + response: SUCCESSFUL_NET_VERSION_RESPONSE, + beforeCompleting: async () => { + await controller.setProviderType('goerli'); }, }, { @@ -1919,626 +1999,386 @@ describe('NetworkController', () => { }, response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, }, + ]), + buildFakeProvider([ { request: { method: 'net_version', }, - response: { - result: '2', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - error: GENERIC_JSON_RPC_ERROR, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await waitForStateChanges({ - controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.initializeProvider(); - }, - }); - expect(controller.store.getState().networkId).toBe('1'); - - await waitForStateChanges({ - controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - expect(controller.store.getState().networkId).toBeNull(); - }, - ); - }); - - it('clears whether the network supports EIP-1559 from state along with any other network details', async () => { - const intentionalErrorMessage = - 'intentional error from eth_getBlockByNumber'; - - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x9999999', - }, - networkDetails: { - EIPS: { - 1559: true, - }, - other: 'details', + response: SUCCESSFUL_NET_VERSION_RESPONSE, }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ { request: { method: 'eth_getBlockByNumber', }, - error: GENERIC_JSON_RPC_ERROR, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - operation: async () => { - try { - await controller.lookupNetwork(); - } catch (error) { - if (error !== intentionalErrorMessage) { - console.error(error); - } - } - }, - }); - expect( - controller.store.getState().networkDetails, - ).toStrictEqual({ - EIPS: { - 1559: undefined, - }, - }); - }, - ); - }); - - it('does not emit infuraIsBlocked', async () => { - await withController(async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', + error: BLOCKED_INFURA_JSON_RPC_ERROR, }, - error: GENERIC_JSON_RPC_ERROR, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://mock-rpc-url', + type: NetworkClientType.Custom, + }) + .mockReturnValueOnce(fakeNetworkClients[0]) + .calledWith({ + network: NETWORK_TYPES.GOERLI, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + }) + .mockReturnValueOnce(fakeNetworkClients[1]); await withoutCallingLookupNetwork({ controller, operation: async () => { await controller.initializeProvider(); }, }); - - const promiseForNoInfuraIsBlockedEvents = waitForPublishedEvents({ + const promiseForNoInfuraIsUnblockedEvents = + waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + count: 0, + }); + const promiseForInfuraIsBlocked = waitForPublishedEvents({ messenger, eventType: NetworkControllerEventType.InfuraIsBlocked, - count: 0, - operation: async () => { - await controller.lookupNetwork(); - }, }); - expect(await promiseForNoInfuraIsBlockedEvents).toBeTruthy(); - }); - }); + await controller.lookupNetwork(); - it('does not emit infuraIsUnblocked', async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x9999999', - }, + expect(await promiseForNoInfuraIsUnblockedEvents).toBeTruthy(); + expect(await promiseForInfuraIsBlocked).toBeTruthy(); + }, + ); + }); + }); + + describe('if the network was switched after the eth_getBlockByNumber request started but before it completed', () => { + it('stores the network status of the second network, not the first', async () => { + await withController( + { + state: { + providerConfig: { + type: 'rpc', + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', + ticker: 'RPC', + }, + networkDetails: { + EIPS: {}, }, }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider([ + infuraProjectId: 'some-infura-project-id', + }, + async ({ controller }) => { + const fakeProviders = [ + buildFakeProvider([ { request: { - method: 'eth_getBlockByNumber', + method: 'net_version', }, - error: GENERIC_JSON_RPC_ERROR, + response: SUCCESSFUL_NET_VERSION_RESPONSE, }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, }, - }); - - const promiseForNoInfuraIsUnblockedEvents = - waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.InfuraIsUnblocked, - count: 0, - operation: async () => { - await controller.lookupNetwork(); + { + request: { + method: 'net_version', }, - }); - - expect(await promiseForNoInfuraIsUnblockedEvents).toBeTruthy(); - }, - ); - }); - }); - - describe('if the network was switched after the eth_getBlockByNumber request started but before it completed', () => { - it('stores the network status of the second network, not the first', async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x9999999', + response: SUCCESSFUL_NET_VERSION_RESPONSE, }, - networkConfigurations: { - testNetworkConfigurationId: { - id: 'testNetworkConfigurationId', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - ticker: 'ABC', + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + beforeCompleting: async () => { + await controller.setProviderType('goerli'); + }, + }, + ]), + buildFakeProvider([ + { + request: { + method: 'eth_getBlockByNumber', }, + error: GENERIC_JSON_RPC_ERROR, }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://mock-rpc-url', + type: NetworkClientType.Custom, + }) + .mockReturnValueOnce(fakeNetworkClients[0]) + .calledWith({ + network: NETWORK_TYPES.GOERLI, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + }) + .mockReturnValueOnce(fakeNetworkClients[1]); + await waitForStateChanges({ + controller, + propertyPath: ['networkStatus'], + operation: async () => { + await controller.initializeProvider(); + }, + }); + expect(controller.store.getState().networkStatus).toBe( + 'available', + ); + + await waitForStateChanges({ + controller, + propertyPath: ['networkStatus'], + operation: async () => { + await controller.lookupNetwork(); + }, + }); + expect(controller.store.getState().networkStatus).toBe('unknown'); + }, + ); + }); + + it('stores the network ID of the second network, not the first', async () => { + await withController( + { + state: { + providerConfig: { + type: 'rpc', + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', + ticker: 'RPC', }, - infuraProjectId: 'some-infura-project-id', }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, + infuraProjectId: 'some-infura-project-id', + }, + async ({ controller }) => { + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'net_version', }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + response: { + result: '1', }, - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, + }, + { + request: { + method: 'eth_getBlockByNumber', }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - beforeCompleting: async () => { - await controller.setActiveNetwork( - 'testNetworkConfigurationId', - ); - }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + }, + { + request: { + method: 'net_version', }, - ]), - buildFakeProvider([ - { - request: { - method: 'net_version', - }, - error: GENERIC_JSON_RPC_ERROR, + response: { + result: '1', }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - chainId: '0x1337', - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.initializeProvider(); }, - }); - expect(controller.store.getState().networkStatus).toBe( - 'available', - ); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.lookupNetwork(); + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + beforeCompleting: async () => { + await controller.setProviderType('goerli'); + }, }, - }); - expect(controller.store.getState().networkStatus).toBe( - 'unknown', - ); - }, - ); - }); - - it('stores the ID of the second network, not the first', async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x9999999', + ]), + buildFakeProvider([ + { + request: { + method: 'net_version', + }, + response: { + result: '2', + }, }, - networkConfigurations: { - testNetworkConfigurationId: { - id: 'testNetworkConfigurationId', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - ticker: 'ABC', + { + request: { + method: 'eth_getBlockByNumber', }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: { - result: '111', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - beforeCompleting: async () => { - await controller.setActiveNetwork( - 'testNetworkConfigurationId', - ); - }, - }, - ]), - buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: { - result: '222', - }, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - chainId: '0x1337', - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://mock-rpc-url', + type: NetworkClientType.Custom, + }) + .mockReturnValueOnce(fakeNetworkClients[0]) + .calledWith({ + network: NETWORK_TYPES.GOERLI, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + }) + .mockReturnValueOnce(fakeNetworkClients[1]); + await waitForStateChanges({ + controller, + propertyPath: ['networkId'], + operation: async () => { + await controller.initializeProvider(); + }, + }); + expect(controller.store.getState().networkId).toBe('1'); - await waitForStateChanges({ - controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); + await waitForStateChanges({ + controller, + propertyPath: ['networkId'], + operation: async () => { + await controller.lookupNetwork(); + }, + }); + expect(controller.store.getState().networkId).toBe('2'); + }, + ); + }); - expect(controller.store.getState().networkId).toBe('222'); + it('stores the EIP-1559 support of the second network, not the first', async () => { + await withController( + { + state: { + providerConfig: { + type: 'rpc', + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', + ticker: 'RPC', + }, + networkDetails: { + EIPS: {}, + other: 'details', + }, }, - ); - }); - - it('stores the EIP-1559 support of the second network, not the first', async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x9999999', + infuraProjectId: 'some-infura-project-id', + }, + async ({ controller }) => { + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'net_version', + }, + response: SUCCESSFUL_NET_VERSION_RESPONSE, }, - networkConfigurations: { - testNetworkConfigurationId: { - id: 'testNetworkConfigurationId', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - ticker: 'ABC', + { + request: { + method: 'eth_getBlockByNumber', + }, + response: { + result: POST_1559_BLOCK, }, }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - beforeCompleting: async () => { - await controller.setActiveNetwork( - 'testNetworkConfigurationId', - ); - }, + { + request: { + method: 'net_version', }, - ]), - buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: PRE_1559_BLOCK, - }, + response: SUCCESSFUL_NET_VERSION_RESPONSE, + beforeCompleting: async () => { + await controller.setProviderType('goerli'); }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - chainId: '0x1337', - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); }, - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - operation: async () => { - await controller.lookupNetwork(); + { + request: { + method: 'eth_getBlockByNumber', + }, + response: { + result: POST_1559_BLOCK, + }, }, - }); - - expect( - controller.store.getState().networkDetails, - ).toStrictEqual({ - EIPS: { - 1559: false, + ]), + buildFakeProvider([ + { + request: { + method: 'net_version', + }, + response: SUCCESSFUL_NET_VERSION_RESPONSE, }, - }); - }, - ); - }); - - it('emits infuraIsBlocked, not infuraIsUnblocked, if the second network is blocked, even if the first one is not', async () => { - const anotherNetwork = INFURA_NETWORKS.find( - (network) => network.networkType !== networkType, - ); - /* eslint-disable-next-line jest/no-if */ - if (!anotherNetwork) { - throw new Error( - "Could not find another network to use. You've probably commented out all INFURA_NETWORKS but one. Please uncomment another one.", - ); - } - - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID - // of the network selected, it just needs to exist - chainId: '0x9999999', - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller, messenger }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - beforeCompleting: async () => { - await controller.setProviderType( - anotherNetwork.networkType, - ); - }, + { + request: { + method: 'eth_getBlockByNumber', }, - ]), - buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, + response: { + result: PRE_1559_BLOCK, }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValueOnce(fakeNetworkClients[0]) - .calledWith({ - network: anotherNetwork.networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValueOnce(fakeNetworkClients[1]); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); }, - }); - const promiseForNoInfuraIsUnblockedEvents = - waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.InfuraIsUnblocked, - count: 0, - }); - const promiseForInfuraIsBlocked = waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.InfuraIsBlocked, - }); - - await controller.lookupNetwork(); - - expect(await promiseForNoInfuraIsUnblockedEvents).toBeTruthy(); - expect(await promiseForInfuraIsBlocked).toBeTruthy(); - }, - ); - }); - }); - }); - }); - - describe('when the type in the provider configuration is "rpc"', () => { - describe('if both net_version and eth_getBlockByNumber respond successfully', () => { - it('stores the fact the network is available', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ chainId: '0x1337', - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingLookupNetwork({ + rpcUrl: 'https://mock-rpc-url', + type: NetworkClientType.Custom, + }) + .mockReturnValueOnce(fakeNetworkClients[0]) + .calledWith({ + network: NETWORK_TYPES.GOERLI, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + }) + .mockReturnValueOnce(fakeNetworkClients[1]); + await waitForStateChanges({ controller, + propertyPath: ['networkDetails'], operation: async () => { await controller.initializeProvider(); }, }); - expect(controller.store.getState().networkStatus).toBe('unknown'); + expect(controller.store.getState().networkDetails).toStrictEqual({ + EIPS: { + 1559: true, + }, + other: 'details', + }); await waitForStateChanges({ controller, - propertyPath: ['networkStatus'], + propertyPath: ['networkDetails'], + // setProviderType clears networkDetails first, and then updates + // it to what we expect it to be + count: 2, operation: async () => { await controller.lookupNetwork(); }, }); - expect(controller.store.getState().networkStatus).toBe( - 'available', - ); + expect(controller.store.getState().networkDetails).toStrictEqual({ + EIPS: { + 1559: false, + }, + }); }, ); }); - it('stores the ID of the network', async () => { + it('emits infuraIsBlocked, not infuraIsUnblocked, if the second network is blocked, even if the first one is not', async () => { await withController( { state: { @@ -2546,836 +2386,730 @@ describe('NetworkController', () => { type: 'rpc', rpcUrl: 'https://mock-rpc-url', chainId: '0x1337', + ticker: 'RPC', }, }, + infuraProjectId: 'some-infura-project-id', }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', + async ({ controller, messenger }) => { + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'net_version', + }, + response: SUCCESSFUL_NET_VERSION_RESPONSE, + beforeCompleting: async () => { + await controller.setProviderType('goerli'); + }, }, - response: { - result: '42', + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, }, - }, - { - request: { - method: 'eth_getBlockByNumber', + ]), + buildFakeProvider([ + { + request: { + method: 'net_version', + }, + response: SUCCESSFUL_NET_VERSION_RESPONSE, }, - response: { - result: POST_1559_BLOCK, + { + request: { + method: 'eth_getBlockByNumber', + }, + error: BLOCKED_INFURA_JSON_RPC_ERROR, }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + chainId: '0x1337', + rpcUrl: 'https://mock-rpc-url', + type: NetworkClientType.Custom, + }) + .mockReturnValueOnce(fakeNetworkClients[0]) + .calledWith({ + network: NETWORK_TYPES.GOERLI, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + }) + .mockReturnValueOnce(fakeNetworkClients[1]); await withoutCallingLookupNetwork({ controller, operation: async () => { await controller.initializeProvider(); }, }); - expect(controller.store.getState().networkId).toBe(null); - - await waitForStateChanges({ - controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.lookupNetwork(); - }, + const promiseForNoInfuraIsUnblockedEvents = + waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + count: 0, + }); + const promiseForInfuraIsBlocked = waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsBlocked, }); - expect(controller.store.getState().networkId).toBe('42'); + await controller.lookupNetwork(); + + expect(await promiseForNoInfuraIsUnblockedEvents).toBeTruthy(); + expect(await promiseForInfuraIsBlocked).toBeTruthy(); }, ); }); + }); - it('stores the fact that the network supports EIP-1559 when baseFeePerGas is in the block header', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, + lookupNetworkTests({ + expectedProviderConfig: buildProviderConfig({ + type: NETWORK_TYPES.RPC, + }), + initialState: { + providerConfig: buildProviderConfig({ type: NETWORK_TYPES.RPC }), + }, + operation: async (controller) => { + await controller.lookupNetwork(); + }, + }); + }); + }); + + describe('setActiveNetwork', () => { + it('throws if the given networkConfigurationId does not match one in networkConfigurations', async () => { + await withController( + { + state: { + networkConfigurations: { + testNetworkConfigurationId: { + id: 'testNetworkConfigurationId', + rpcUrl: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', }, }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); + }, + }, + async ({ controller }) => { + const fakeProvider = buildFakeProvider(); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - expect( - controller.store.getState().networkDetails.EIPS[1559], - ).toBeTruthy(); - }, + await expect(() => + controller.setActiveNetwork('invalid-network-configuration-id'), + ).rejects.toThrow( + new Error( + 'networkConfigurationId invalid-network-configuration-id does not match a configured networkConfiguration', + ), ); - }); + }, + ); + }); - it('stores the fact that the network does not support EIP-1559 when baseFeePerGas is not in the block header', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, + it('overwrites the provider configuration given a networkConfigurationId that matches a configured networkConfiguration', async () => { + await withController( + { + state: { + providerConfig: { + type: 'rpc', + rpcUrl: 'https://mock-rpc-url-1', + chainId: '0x111', + ticker: 'TEST1', + nickname: 'test network 1', + rpcPrefs: { + blockExplorerUrl: 'https://test-block-explorer-1.com', }, }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect( - controller.store.getState().networkDetails.EIPS[1559], - ).toBe(false); - }, - ); - }); - - it('emits infuraIsUnblocked', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', + rpcUrl: 'https://mock-rpc-url-2', + chainId: '0x222', + ticker: 'TEST2', + nickname: 'test network 2', + rpcPrefs: { + blockExplorerUrl: 'https://test-block-explorer-2.com', }, }, }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); + }, + }, + async ({ controller }) => { + const fakeProvider = buildFakeProvider(); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient() + .calledWith({ + rpcUrl: 'https://mock-rpc-url-2', + chainId: '0x222', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClient); - const infuraIsUnblocked = await waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.InfuraIsUnblocked, - operation: async () => { - await controller.lookupNetwork(); - }, - }); + await controller.setActiveNetwork('testNetworkConfiguration'); - expect(infuraIsUnblocked).toBeTruthy(); + expect(controller.store.getState().providerConfig).toStrictEqual({ + id: 'testNetworkConfiguration', + type: 'rpc', + rpcUrl: 'https://mock-rpc-url-2', + chainId: '0x222', + ticker: 'TEST2', + nickname: 'test network 2', + rpcPrefs: { + blockExplorerUrl: 'https://test-block-explorer-2.com', }, - ); - }); - }); + }); + }, + ); + }); - describe('if the request for eth_getBlockByNumber responds successfully, but the request for net_version responds with a generic error', () => { - it('stores the network status as available if the error does not translate to an internal RPC error', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, + it('emits networkWillChange before making any changes to the network status', async () => { + await withController( + { + state: { + providerConfig: { + type: 'rpc', + rpcUrl: 'https://mock-rpc-url-1', + chainId: '0x111', + ticker: 'TEST1', + }, + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', + rpcUrl: 'https://mock-rpc-url-2', + chainId: '0x222', + ticker: 'TEST2', }, }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - { - request: { - method: 'net_version', - }, - error: ethErrors.rpc.methodNotFound(), + }, + }, + async ({ controller, messenger }) => { + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'net_version', }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + response: SUCCESSFUL_NET_VERSION_RESPONSE, + }, + ]), + buildFakeProvider([ + { + request: { + method: 'net_version', }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.initializeProvider(); - }, - }); - expect(controller.store.getState().networkStatus).toBe( - 'available', - ); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); + error: GENERIC_JSON_RPC_ERROR, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + rpcUrl: 'https://mock-rpc-url-1', + chainId: '0x111', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + rpcUrl: 'https://mock-rpc-url-2', + chainId: '0x222', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await waitForLookupNetworkToComplete({ + controller, + operation: async () => { + await controller.initializeProvider(); + }, + }); + const initialNetworkStatus = + controller.store.getState().networkStatus; + expect(initialNetworkStatus).toBe('available'); + const networkWillChange = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.NetworkWillChange, + operation: () => { + // Intentionally not awaited because we're checking state + // partway through the operation + controller.setActiveNetwork('testNetworkConfiguration'); + }, + beforeResolving: () => { expect(controller.store.getState().networkStatus).toBe( - 'unavailable', + initialNetworkStatus, ); }, - ); - }); + }); + expect(networkWillChange).toBeTruthy(); + }, + ); + }); - it('stores the network status as unknown if the error translates to an internal RPC error', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, + it('resets the network status to "unknown" before emitting networkDidChange', async () => { + await withController( + { + state: { + providerConfig: { + type: 'rpc', + rpcUrl: 'https://mock-rpc-url-1', + chainId: '0x111', + ticker: 'TEST1', + }, + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', + rpcUrl: 'https://mock-rpc-url-2', + chainId: '0x222', + ticker: 'TEST2', }, }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - { - request: { - method: 'net_version', - }, - error: GENERIC_JSON_RPC_ERROR, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.initializeProvider(); + }, + }, + async ({ controller }) => { + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'net_version', }, - }); - expect(controller.store.getState().networkStatus).toBe( - 'available', - ); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.lookupNetwork(); + response: SUCCESSFUL_NET_VERSION_RESPONSE, + }, + ]), + buildFakeProvider([ + { + request: { + method: 'net_version', }, - }); + response: SUCCESSFUL_NET_VERSION_RESPONSE, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + rpcUrl: 'https://mock-rpc-url-1', + chainId: '0x111', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + rpcUrl: 'https://mock-rpc-url-2', + chainId: '0x222', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); + expect(controller.store.getState().networkStatus).toBe('available'); + await waitForStateChanges({ + controller, + propertyPath: ['networkStatus'], + // We only care about the first state change, because it happens + // before networkDidChange + count: 1, + operation: () => { + // Intentionally not awaited because we're checking state + // partway through the operation. + controller.setActiveNetwork('testNetworkConfiguration'); + }, + beforeResolving: () => { expect(controller.store.getState().networkStatus).toBe('unknown'); }, - ); - }); + }); + }, + ); + }); - it('clears the ID of the network from state', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, + it('clears EIP-1559 support for the network from state before emitting networkDidChange', async () => { + await withController( + { + state: { + providerConfig: { + type: 'rpc', + rpcUrl: 'https://mock-rpc-url-1', + chainId: '0x111', + ticker: 'TEST1', + }, + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', + rpcUrl: 'https://mock-rpc-url-2', + chainId: '0x222', + ticker: 'TEST2', }, }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: { - result: '42', - }, + networkDetails: { + EIPS: {}, + other: 'details', + }, + }, + }, + async ({ controller }) => { + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'eth_getBlockByNumber', }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + response: { + result: POST_1559_BLOCK, }, - { - request: { - method: 'net_version', - }, - error: GENERIC_JSON_RPC_ERROR, + }, + ]), + buildFakeProvider([ + { + request: { + method: 'eth_getBlockByNumber', }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.initializeProvider(); - }, - }); - expect(controller.store.getState().networkId).toBe('42'); - - await waitForStateChanges({ - controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(controller.store.getState().networkId).toBeNull(); - }, - ); - }); - - it('clears whether the network supports EIP-1559 from state along with any other network details', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, - networkDetails: { - EIPS: { - 1559: true, - }, - other: 'details', + response: { + result: PRE_1559_BLOCK, }, }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + rpcUrl: 'https://mock-rpc-url-1', + chainId: '0x111', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + rpcUrl: 'https://mock-rpc-url-2', + chainId: '0x222', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); + expect(controller.store.getState().networkDetails).toStrictEqual({ + EIPS: { + 1559: true, }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - { - request: { - method: 'net_version', - }, - error: GENERIC_JSON_RPC_ERROR, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.initializeProvider(); - }, - }); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: false, - }, - other: 'details', - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); + other: 'details', + }); + await waitForStateChanges({ + controller, + propertyPath: ['networkDetails'], + // We only care about the first state change, because it happens + // before networkDidChange + count: 1, + operation: () => { + // Intentionally not awaited because we're checking state + // partway through the operation + controller.setActiveNetwork('testNetworkConfiguration'); + }, + beforeResolving: () => { expect(controller.store.getState().networkDetails).toStrictEqual({ EIPS: { 1559: undefined, }, }); }, - ); - }); + }); + }, + ); + }); - it('does not emit infuraIsBlocked', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, + it('initializes a provider pointed to the given RPC URL', async () => { + await withController( + { + state: { + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', + ticker: 'TEST', }, }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', - }, - error: GENERIC_JSON_RPC_ERROR, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); - - const promiseForNoInfuraIsBlockedEvents = waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.InfuraIsBlocked, - count: 0, - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(await promiseForNoInfuraIsBlockedEvents).toBeTruthy(); - }, - ); - }); - - it('emits infuraIsUnblocked', async () => { - await withController( + }, + }, + async ({ controller }) => { + const fakeProvider = buildFakeProvider([ { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, + request: { + method: 'test', + }, + response: { + result: 'test response', }, }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', - }, - error: GENERIC_JSON_RPC_ERROR, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); + ]); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient() + .calledWith({ + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClient); - const infuraIsUnblocked = await waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.InfuraIsUnblocked, - operation: async () => { - await controller.lookupNetwork(); - }, - }); + await controller.setActiveNetwork('testNetworkConfiguration'); - expect(infuraIsUnblocked).toBeTruthy(); - }, + const { provider } = controller.getProviderAndBlockTracker(); + assert(provider, 'Provider is somehow unset'); + const promisifiedSendAsync = promisify(provider.sendAsync).bind( + provider, ); - }); - }); + const response = await promisifiedSendAsync({ + id: '1', + jsonrpc: '2.0', + method: 'test', + }); + expect(response.result).toBe('test response'); + }, + ); + }); - describe('if the request for net_version responds successfully, but the request for eth_getBlockByNumber responds with a generic error', () => { - it('stores the fact that the network is unavailable', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, + it('replaces the provider object underlying the provider proxy without creating a new instance of the proxy itself', async () => { + await withController( + { + state: { + providerConfig: { + type: 'rpc', + rpcUrl: 'https://mock-rpc-url-1', + chainId: '0x111', + ticker: 'TEST1', + }, + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', + rpcUrl: 'https://mock-rpc-url-2', + chainId: '0x222', + ticker: 'TEST2', }, }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - error: GENERIC_JSON_RPC_ERROR, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.initializeProvider(); - }, - }); - expect(controller.store.getState().networkStatus).toBe( - 'available', - ); + }, + }, + async ({ controller }) => { + const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + rpcUrl: 'https://mock-rpc-url-1', + chainId: '0x111', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + rpcUrl: 'https://mock-rpc-url-2', + chainId: '0x222', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); + const { provider: providerBefore } = + controller.getProviderAndBlockTracker(); - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); + await controller.setActiveNetwork('testNetworkConfiguration'); - expect(controller.store.getState().networkStatus).toBe('unknown'); - }, - ); - }); + const { provider: providerAfter } = + controller.getProviderAndBlockTracker(); + expect(providerBefore).toBe(providerAfter); + }, + ); + }); - it('clears the ID of the network from state', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, + it('emits networkDidChange', async () => { + await withController( + { + state: { + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', + ticker: 'TEST', }, }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: { - result: '42', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - { - request: { - method: 'net_version', - }, - response: { - result: '42', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - error: GENERIC_JSON_RPC_ERROR, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await waitForStateChanges({ - controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.initializeProvider(); - }, - }); - expect(controller.store.getState().networkId).toBe('42'); + }, + }, + async ({ controller, messenger }) => { + const fakeProvider = buildFakeProvider(); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient() + .calledWith({ + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClient); - await waitForStateChanges({ - controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); + const networkDidChange = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.NetworkDidChange, + operation: async () => { + await controller.setActiveNetwork('testNetworkConfiguration'); + }, + }); + + expect(networkDidChange).toBeTruthy(); + }, + ); + }); + + it('emits infuraIsUnblocked', async () => { + await withController( + { + state: { + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', + ticker: 'TEST', + }, + }, + }, + }, + async ({ controller, messenger }) => { + const fakeProvider = buildFakeProvider(); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient() + .calledWith({ + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClient); - expect(controller.store.getState().networkId).toBeNull(); + const infuraIsUnblocked = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + operation: async () => { + await controller.setActiveNetwork('testNetworkConfiguration'); }, - ); - }); + }); - it('clears whether the network supports EIP-1559 from state along with any other network details', async () => { - await withController( + expect(infuraIsUnblocked).toBeTruthy(); + }, + ); + }); + + it('determines the status of the network, storing it in state', async () => { + await withController( + { + state: { + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', + ticker: 'TEST', + }, + }, + }, + }, + async ({ controller }) => { + const fakeProvider = buildFakeProvider([ { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, - networkDetails: { - EIPS: {}, - other: 'details', - }, + request: { + method: 'net_version', }, + response: SUCCESSFUL_NET_VERSION_RESPONSE, }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - error: GENERIC_JSON_RPC_ERROR, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - operation: async () => { - await controller.initializeProvider(); - }, - }); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: true, - }, - other: 'details', - }); + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + }, + ]); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient() + .calledWith({ + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClient); - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); + await controller.setActiveNetwork('testNetworkConfiguration'); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: undefined, - }, - }); - }, - ); - }); + expect(controller.store.getState().networkStatus).toBe('available'); + }, + ); + }); - it('does not emit infuraIsBlocked', async () => { - await withController( + it('determines whether the network supports EIP-1559, storing it in state', async () => { + await withController( + { + state: { + networkDetails: { + EIPS: {}, + other: 'details', + }, + networkConfigurations: { + testNetworkConfigurationId: { + id: 'testNetworkConfigurationId', + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', + ticker: 'TEST', + }, + }, + }, + }, + async ({ controller }) => { + const fakeProvider = buildFakeProvider([ { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, + request: { + method: 'eth_getBlockByNumber', + }, + response: { + result: POST_1559_BLOCK, }, }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - error: GENERIC_JSON_RPC_ERROR, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); + ]); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient() + .calledWith({ + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClient); - const promiseForNoInfuraIsBlockedEvents = waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.InfuraIsBlocked, - count: 0, - operation: async () => { - await controller.lookupNetwork(); - }, - }); + await controller.setActiveNetwork('testNetworkConfigurationId'); - expect(await promiseForNoInfuraIsBlockedEvents).toBeTruthy(); + expect(controller.store.getState().networkDetails).toStrictEqual({ + EIPS: { + 1559: true, }, - ); - }); + }); + }, + ); + }); + }); - it('emits infuraIsUnblocked', async () => { + describe('setProviderType', () => { + for (const { + networkType, + chainId, + ticker, + blockExplorerUrl, + } of INFURA_NETWORKS) { + describe(`given a type of "${networkType}"`, () => { + it(`overwrites the provider configuration using type: "${networkType}", chainId: "${chainId}", ticker "${ticker}", and blockExplorerUrl "${blockExplorerUrl}", clearing rpcUrl and nickname`, async () => { await withController( { state: { @@ -3383,49 +3117,54 @@ describe('NetworkController', () => { type: 'rpc', rpcUrl: 'https://mock-rpc-url', chainId: '0x1337', + nickname: 'test-chain', + ticker: 'TEST', + rpcPrefs: { + blockExplorerUrl: 'https://test-block-explorer.com', + }, }, }, }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - error: GENERIC_JSON_RPC_ERROR, - }, - ]); + async ({ controller }) => { + const fakeProvider = buildFakeProvider(); const fakeNetworkClient = buildFakeClient(fakeProvider); mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); - const infuraIsUnblocked = await waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.InfuraIsUnblocked, - operation: async () => { - await controller.lookupNetwork(); - }, - }); + controller.setProviderType(networkType); - expect(infuraIsUnblocked).toBeTruthy(); + expect(controller.store.getState().providerConfig).toStrictEqual({ + type: networkType, + rpcUrl: undefined, + chainId, + ticker, + nickname: undefined, + rpcPrefs: { blockExplorerUrl }, + }); }, ); }); - }); - describe('if the network was switched after the net_version request started but before it completed', () => { - it('stores the network status of the second network, not the first', async () => { + it('emits networkWillChange', async () => { + await withController(async ({ controller, messenger }) => { + const fakeProvider = buildFakeProvider(); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + + const networkWillChange = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.NetworkWillChange, + operation: () => { + // Intentionally not awaited because we're capturing an event + // emitted partway through the operation + controller.setProviderType(networkType); + }, + }); + + expect(networkWillChange).toBeTruthy(); + }); + }); + + it('resets the network status to "unknown" before emitting networkDidChange', async () => { await withController( { state: { @@ -3433,6 +3172,7 @@ describe('NetworkController', () => { type: 'rpc', rpcUrl: 'https://mock-rpc-url', chainId: '0x1337', + ticker: 'TEST', }, }, infuraProjectId: 'some-infura-project-id', @@ -3446,36 +3186,8 @@ describe('NetworkController', () => { }, response: SUCCESSFUL_NET_VERSION_RESPONSE, }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - beforeCompleting: async () => { - await controller.setProviderType('goerli'); - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]), - buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - error: GENERIC_JSON_RPC_ERROR, - }, ]), + buildFakeProvider(), ]; const fakeNetworkClients = [ buildFakeClient(fakeProviders[0]), @@ -3483,24 +3195,18 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ - chainId: '0x1337', rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', type: NetworkClientType.Custom, }) - .mockReturnValueOnce(fakeNetworkClients[0]) + .mockReturnValue(fakeNetworkClients[0]) .calledWith({ - network: NETWORK_TYPES.GOERLI, + network: networkType, infuraProjectId: 'some-infura-project-id', type: NetworkClientType.Infura, }) - .mockReturnValueOnce(fakeNetworkClients[1]); - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.initializeProvider(); - }, - }); + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); expect(controller.store.getState().networkStatus).toBe( 'available', ); @@ -3508,16 +3214,25 @@ describe('NetworkController', () => { await waitForStateChanges({ controller, propertyPath: ['networkStatus'], - operation: async () => { - await controller.lookupNetwork(); + // We only care about the first state change, because it + // happens before networkDidChange + count: 1, + operation: () => { + // Intentionally not awaited because we're checking the state + // partway through the operation + controller.setProviderType(networkType); + }, + beforeResolving: () => { + expect(controller.store.getState().networkStatus).toBe( + 'unknown', + ); }, }); - expect(controller.store.getState().networkStatus).toBe('unknown'); }, ); }); - it('stores the ID of the second network, not the first', async () => { + it('clears EIP-1559 support for the network from state before emitting networkDidChange', async () => { await withController( { state: { @@ -3525,7 +3240,11 @@ describe('NetworkController', () => { type: 'rpc', rpcUrl: 'https://mock-rpc-url', chainId: '0x1337', - ticker: 'RPC', + ticker: 'TEST', + }, + networkDetails: { + EIPS: {}, + other: 'details', }, }, infuraProjectId: 'some-infura-project-id', @@ -3533,52 +3252,23 @@ describe('NetworkController', () => { async ({ controller }) => { const fakeProviders = [ buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - }, { request: { method: 'eth_getBlockByNumber', }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - { - request: { - method: 'net_version', - }, response: { - result: '1', - }, - beforeCompleting: async () => { - await controller.setProviderType('goerli'); - }, - }, - { - request: { - method: 'eth_getBlockByNumber', + result: POST_1559_BLOCK, }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, }, ]), buildFakeProvider([ { request: { - method: 'net_version', + method: 'eth_getBlockByNumber', }, response: { - result: '2', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', + result: PRE_1559_BLOCK, }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, }, ]), ]; @@ -3588,40 +3278,91 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ - chainId: '0x1337', rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', type: NetworkClientType.Custom, }) - .mockReturnValueOnce(fakeNetworkClients[0]) + .mockReturnValue(fakeNetworkClients[0]) .calledWith({ - network: NETWORK_TYPES.GOERLI, + network: networkType, infuraProjectId: 'some-infura-project-id', type: NetworkClientType.Infura, }) - .mockReturnValueOnce(fakeNetworkClients[1]); - await waitForStateChanges({ - controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.initializeProvider(); + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); + expect(controller.store.getState().networkDetails).toStrictEqual({ + EIPS: { + 1559: true, }, + other: 'details', }); - expect(controller.store.getState().networkId).toBe('1'); await waitForStateChanges({ controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.lookupNetwork(); + propertyPath: ['networkDetails'], + // We only care about the first state change, because it + // happens before networkDidChange + count: 1, + operation: () => { + // Intentionally not awaited because we're checking the state + // partway through the operation + controller.setProviderType(networkType); + }, + beforeResolving: () => { + expect( + controller.store.getState().networkDetails, + ).toStrictEqual({ + EIPS: { + 1559: undefined, + }, + }); }, }); + }, + ); + }); - expect(controller.store.getState().networkId).toBe('2'); + it(`initializes a provider pointed to the "${networkType}" Infura network`, async () => { + await withController( + { infuraProjectId: 'some-infura-project-id' }, + async ({ controller }) => { + const fakeProvider = buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response', + }, + }, + ]); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient() + .calledWith({ + network: networkType, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + }) + .mockReturnValue(fakeNetworkClient); + + await controller.setProviderType(networkType); + + const { provider } = controller.getProviderAndBlockTracker(); + assert(provider, 'Provider is somehow unset'); + const promisifiedSendAsync = promisify(provider.sendAsync).bind( + provider, + ); + const response = await promisifiedSendAsync({ + id: '1', + jsonrpc: '2.0', + method: 'test', + }); + expect(response.result).toBe('test response'); }, ); }); - it('stores the EIP-1559 support of the second network, not the first', async () => { + it('replaces the provider object underlying the provider proxy without creating a new instance of the proxy itself', async () => { await withController( { state: { @@ -3629,289 +3370,236 @@ describe('NetworkController', () => { type: 'rpc', rpcUrl: 'https://mock-rpc-url', chainId: '0x1337', - ticker: 'RPC', - }, - networkDetails: { - EIPS: {}, - other: 'details', + ticker: 'TEST', }, }, infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - beforeCompleting: async () => { - await controller.setProviderType('goerli'); - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ]), - buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - ]), - ]; + const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; const fakeNetworkClients = [ buildFakeClient(fakeProviders[0]), buildFakeClient(fakeProviders[1]), ]; mockCreateNetworkClient() .calledWith({ - chainId: '0x1337', rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', type: NetworkClientType.Custom, }) - .mockReturnValueOnce(fakeNetworkClients[0]) + .mockReturnValue(fakeNetworkClients[0]) .calledWith({ - network: NETWORK_TYPES.GOERLI, + network: networkType, infuraProjectId: 'some-infura-project-id', type: NetworkClientType.Infura, }) - .mockReturnValueOnce(fakeNetworkClients[1]); - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - operation: async () => { - await controller.initializeProvider(); - }, - }); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: true, - }, - other: 'details', - }); + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); + const { provider: providerBefore } = + controller.getProviderAndBlockTracker(); - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - // setProviderType clears networkDetails first, and then updates - // it to what we expect it to be - count: 2, - operation: async () => { - await controller.lookupNetwork(); - }, - }); + await controller.setProviderType(networkType); + + const { provider: providerAfter } = + controller.getProviderAndBlockTracker(); + expect(providerBefore).toBe(providerAfter); + }, + ); + }); + + it('emits networkDidChange', async () => { + await withController(async ({ controller, messenger }) => { + const fakeProvider = buildFakeProvider(); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + + const networkDidChange = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.NetworkDidChange, + operation: async () => { + await controller.setProviderType(networkType); + }, + }); + + expect(networkDidChange).toBeTruthy(); + }); + }); + + it('emits infuraIsBlocked or infuraIsUnblocked, depending on whether Infura is blocking requests', async () => { + await withController(async ({ controller, messenger }) => { + const fakeProvider = buildFakeProvider([ + { + request: { + method: 'eth_getBlockByNumber', + }, + error: BLOCKED_INFURA_JSON_RPC_ERROR, + }, + ]); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + const promiseForNoInfuraIsUnblockedEvents = waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + count: 0, + }); + const promiseForInfuraIsBlocked = waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsBlocked, + }); + + await controller.setProviderType(networkType); + + expect(await promiseForNoInfuraIsUnblockedEvents).toBeTruthy(); + expect(await promiseForInfuraIsBlocked).toBeTruthy(); + }); + }); + + it('determines the status of the network, storing it in state', async () => { + await withController(async ({ controller }) => { + const fakeProvider = buildFakeProvider([ + { + request: { + method: 'net_version', + }, + response: SUCCESSFUL_NET_VERSION_RESPONSE, + }, + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + }, + ]); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + + await controller.setProviderType(networkType); + + expect(controller.store.getState().networkStatus).toBe('available'); + }); + }); + + it('determines whether the network supports EIP-1559, storing it in state', async () => { + await withController( + { + state: { + networkDetails: { + EIPS: {}, + other: 'details', + }, + }, + }, + async ({ controller }) => { + const fakeProvider = buildFakeProvider([ + { + request: { + method: 'eth_getBlockByNumber', + }, + response: { + result: POST_1559_BLOCK, + }, + }, + ]); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + + await controller.setProviderType(networkType); expect(controller.store.getState().networkDetails).toStrictEqual({ EIPS: { - 1559: false, + 1559: true, }, }); }, ); }); + }); + } - it('emits infuraIsBlocked, not infuraIsUnblocked, if the second network is blocked, even if the first one is not', async () => { + describe('given a type of "rpc"', () => { + it('throws', async () => { + await withController(async ({ controller }) => { + await expect(() => controller.setProviderType('rpc')).rejects.toThrow( + new Error( + 'NetworkController - cannot call "setProviderType" with type "rpc". Use "setActiveNetwork"', + ), + ); + }); + }); + }); + + describe('given an invalid Infura network name', () => { + it('throws', async () => { + await withController(async ({ controller }) => { + await expect(() => + controller.setProviderType('sadlflaksdj'), + ).rejects.toThrow( + new Error('Unknown Infura provider type "sadlflaksdj".'), + ); + }); + }); + }); + }); + + describe('resetConnection', () => { + for (const { networkType } of INFURA_NETWORKS) { + describe(`when the type in the provider configuration is "${networkType}"`, () => { + it('emits networkWillChange', async () => { await withController( { state: { providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - ticker: 'RPC', + type: networkType, + // NOTE: This doesn't need to match the logical chain ID of + // the network selected, it just needs to exist + chainId: '0x9999999', }, }, - infuraProjectId: 'some-infura-project-id', }, async ({ controller, messenger }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - beforeCompleting: async () => { - await controller.setProviderType('goerli'); - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]), - buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - chainId: '0x1337', - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValueOnce(fakeNetworkClients[0]) - .calledWith({ - network: NETWORK_TYPES.GOERLI, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValueOnce(fakeNetworkClients[1]); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); - const promiseForNoInfuraIsUnblockedEvents = - waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.InfuraIsUnblocked, - count: 0, - }); - const promiseForInfuraIsBlocked = waitForPublishedEvents({ + const fakeProvider = buildFakeProvider(); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + + const networkWillChange = await waitForPublishedEvents({ messenger, - eventType: NetworkControllerEventType.InfuraIsBlocked, + eventType: NetworkControllerEventType.NetworkWillChange, + operation: () => { + // Intentionally not awaited because we want to capture an + // event emitted partway throught this operation + controller.resetConnection(); + }, }); - await controller.lookupNetwork(); - - expect(await promiseForNoInfuraIsUnblockedEvents).toBeTruthy(); - expect(await promiseForInfuraIsBlocked).toBeTruthy(); + expect(networkWillChange).toBeTruthy(); }, ); }); - }); - 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 () => { + it('resets the network status to "unknown" before emitting networkDidChange', async () => { await withController( { state: { providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - ticker: 'RPC', - }, - networkDetails: { - EIPS: {}, + type: networkType, + // NOTE: This doesn't need to match the logical chain ID of + // the network selected, it just needs to exist + chainId: '0x9999999', }, }, - infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, + const fakeProvider = buildFakeProvider([ + { + request: { + method: 'net_version', }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - beforeCompleting: async () => { - await controller.setProviderType('goerli'); - }, - }, - ]), - buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - error: GENERIC_JSON_RPC_ERROR, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - chainId: '0x1337', - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValueOnce(fakeNetworkClients[0]) - .calledWith({ - network: NETWORK_TYPES.GOERLI, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValueOnce(fakeNetworkClients[1]); - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.initializeProvider(); + response: SUCCESSFUL_NET_VERSION_RESPONSE, }, - }); + ]); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + await controller.initializeProvider(); expect(controller.store.getState().networkStatus).toBe( 'available', ); @@ -3919,308 +3607,200 @@ describe('NetworkController', () => { await waitForStateChanges({ controller, propertyPath: ['networkStatus'], - operation: async () => { - await controller.lookupNetwork(); + // We only care about the first state change, because it + // happens before networkDidChange + count: 1, + operation: () => { + // Intentionally not awaited because we want to capture a + // state change made partway through the operation + controller.resetConnection(); }, }); + expect(controller.store.getState().networkStatus).toBe('unknown'); }, ); }); - it('stores the network ID of the second network, not the first', async () => { + it('clears EIP-1559 support for the network from state before emitting networkDidChange', async () => { await withController( { state: { providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - ticker: 'RPC', + type: networkType, + // NOTE: This doesn't need to match the logical chain ID of + // the network selected, it just needs to exist + chainId: '0x9999999', }, }, - infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - beforeCompleting: async () => { - await controller.setProviderType('goerli'); - }, - }, - ]), - buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: { - result: '2', - }, + const fakeProvider = buildFakeProvider([ + { + request: { + method: 'eth_getBlockByNumber', }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + response: { + result: POST_1559_BLOCK, }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - chainId: '0x1337', - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValueOnce(fakeNetworkClients[0]) - .calledWith({ - network: NETWORK_TYPES.GOERLI, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValueOnce(fakeNetworkClients[1]); - await waitForStateChanges({ - controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.initializeProvider(); + }, + ]); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + await controller.initializeProvider(); + expect(controller.store.getState().networkDetails).toStrictEqual({ + EIPS: { + 1559: true, }, }); - expect(controller.store.getState().networkId).toBe('1'); await waitForStateChanges({ controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.lookupNetwork(); + propertyPath: ['networkDetails'], + // 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 + // partway through the operation + controller.resetConnection(); + }, + }); + + expect(controller.store.getState().networkDetails).toStrictEqual({ + EIPS: { + 1559: undefined, }, }); - expect(controller.store.getState().networkId).toBe('2'); }, ); }); - it('stores the EIP-1559 support of the second network, not the first', async () => { + it(`initializes a new provider object pointed to the current Infura network (type: "${networkType}")`, async () => { await withController( { state: { providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - ticker: 'RPC', - }, - networkDetails: { - EIPS: {}, - other: 'details', + type: networkType, + // NOTE: This doesn't need to match the logical chain ID of + // the network selected, it just needs to exist + chainId: '0x9999999', }, }, - infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - beforeCompleting: async () => { - await controller.setProviderType('goerli'); - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ]), - buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, + const fakeProvider = buildFakeProvider([ + { + request: { + method: 'test', }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: PRE_1559_BLOCK, - }, + response: { + result: 'test response', }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - chainId: '0x1337', - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValueOnce(fakeNetworkClients[0]) - .calledWith({ - network: NETWORK_TYPES.GOERLI, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValueOnce(fakeNetworkClients[1]); - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - operation: async () => { - await controller.initializeProvider(); }, + ]); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + + await controller.resetConnection(); + + const { provider } = controller.getProviderAndBlockTracker(); + assert(provider, 'Provider is somehow unset'); + const promisifiedSendAsync = promisify(provider.sendAsync).bind( + provider, + ); + const response = await promisifiedSendAsync({ + id: '1', + jsonrpc: '2.0', + method: 'test', }); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: true, + expect(response.result).toBe('test response'); + }, + ); + }); + + it('replaces the provider object underlying the provider proxy without creating a new instance of the proxy itself', async () => { + await withController( + { + state: { + providerConfig: { + type: networkType, + // NOTE: This doesn't need to match the logical chain ID of + // the network selected, it just needs to exist + chainId: '0x9999999', }, - other: 'details', - }); + }, + }, + async ({ controller }) => { + const fakeProvider = buildFakeProvider(); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + await controller.initializeProvider(); + const { provider: providerBefore } = + controller.getProviderAndBlockTracker(); - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - // setProviderType clears networkDetails first, and then updates - // it to what we expect it to be - count: 2, - operation: async () => { - await controller.lookupNetwork(); + await controller.resetConnection(); + + const { provider: providerAfter } = + controller.getProviderAndBlockTracker(); + expect(providerBefore).toBe(providerAfter); + }, + ); + }); + + it('emits networkDidChange', async () => { + await withController( + { + state: { + providerConfig: { + type: networkType, + // NOTE: This doesn't need to match the logical chain ID of + // the network selected, it just needs to exist + chainId: '0x9999999', }, - }); + }, + }, + async ({ controller, messenger }) => { + const fakeProvider = buildFakeProvider(); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: false, + const networkDidChange = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.NetworkDidChange, + operation: async () => { + await controller.resetConnection(); }, }); + + expect(networkDidChange).toBeTruthy(); }, ); }); - it('emits infuraIsBlocked, not infuraIsUnblocked, if the second network is blocked, even if the first one is not', async () => { + it('emits infuraIsBlocked or infuraIsUnblocked, depending on whether Infura is blocking requests', async () => { await withController( { state: { providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - ticker: 'RPC', + type: networkType, + // NOTE: This doesn't need to match the logical chain ID of + // the network selected, it just needs to exist + chainId: '0x9999999', }, }, - infuraProjectId: 'some-infura-project-id', }, async ({ controller, messenger }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - beforeCompleting: async () => { - await controller.setProviderType('goerli'); - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]), - buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, + const fakeProvider = buildFakeProvider([ + { + request: { + method: 'eth_getBlockByNumber', }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - chainId: '0x1337', - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValueOnce(fakeNetworkClients[0]) - .calledWith({ - network: NETWORK_TYPES.GOERLI, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValueOnce(fakeNetworkClients[1]); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); + error: BLOCKED_INFURA_JSON_RPC_ERROR, }, - }); + ]); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); const promiseForNoInfuraIsUnblockedEvents = waitForPublishedEvents({ messenger, @@ -4232,724 +3812,546 @@ describe('NetworkController', () => { eventType: NetworkControllerEventType.InfuraIsBlocked, }); - await controller.lookupNetwork(); + await controller.resetConnection(); expect(await promiseForNoInfuraIsUnblockedEvents).toBeTruthy(); expect(await promiseForInfuraIsBlocked).toBeTruthy(); }, ); }); - }); - }); - }); - describe('setActiveNetwork', () => { - it('throws if the given networkConfigurationId does not match one in networkConfigurations', async () => { - await withController( - { - state: { - networkConfigurations: { - testNetworkConfigurationId: { - id: 'testNetworkConfigurationId', - rpcUrl: 'https://mock-rpc-url', - chainId: '0xtest', - ticker: 'TEST', + it('checks the status of the network again, updating state appropriately', async () => { + await withController( + { + state: { + providerConfig: { + type: networkType, + // NOTE: This doesn't need to match the logical chain ID of + // the network selected, it just needs to exist + chainId: '0x9999999', + }, }, }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + async ({ controller }) => { + const fakeProvider = buildFakeProvider([ + { + request: { + method: 'net_version', + }, + response: SUCCESSFUL_NET_VERSION_RESPONSE, + }, + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + }, + ]); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await expect(() => - controller.setActiveNetwork('invalid-network-configuration-id'), - ).rejects.toThrow( - new Error( - 'networkConfigurationId invalid-network-configuration-id does not match a configured networkConfiguration', - ), - ); - }, - ); - }); + await controller.resetConnection(); - it('overwrites the provider configuration given a networkConfigurationId that matches a configured networkConfiguration', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url-1', - chainId: '0x111', - ticker: 'TEST1', - nickname: 'test network 1', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer-1.com', - }, + expect(controller.store.getState().networkStatus).toBe( + 'available', + ); }, - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url-2', - chainId: '0x222', - ticker: 'TEST2', - nickname: 'test network 2', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer-2.com', + ); + }); + + it('checks whether the network supports EIP-1559 again, updating state appropriately', async () => { + await withController( + { + state: { + providerConfig: { + type: networkType, + // NOTE: This doesn't need to match the logical chain ID of + // the network selected, it just needs to exist + chainId: '0x9999999', + }, + networkDetails: { + EIPS: {}, + other: 'details', }, }, }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url-2', - chainId: '0x222', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClient); + async ({ controller }) => { + const fakeProvider = buildFakeProvider([ + { + request: { + method: 'eth_getBlockByNumber', + }, + response: { + result: POST_1559_BLOCK, + }, + }, + ]); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.setActiveNetwork('testNetworkConfiguration'); + await controller.resetConnection(); - expect(controller.store.getState().providerConfig).toStrictEqual({ - id: 'testNetworkConfiguration', - type: 'rpc', - rpcUrl: 'https://mock-rpc-url-2', - chainId: '0x222', - ticker: 'TEST2', - nickname: 'test network 2', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer-2.com', + expect(controller.store.getState().networkDetails).toStrictEqual({ + EIPS: { + 1559: true, + }, + }); }, - }); - }, - ); - }); + ); + }); + }); + } - it('emits networkWillChange before making any changes to the network status', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url-1', - chainId: '0x111', - ticker: 'TEST1', + describe(`when the type in the provider configuration is "rpc"`, () => { + it('emits networkWillChange', async () => { + await withController( + { + state: { + providerConfig: { + type: 'rpc', + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', + }, }, - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url-2', - chainId: '0x222', - ticker: 'TEST2', + }, + async ({ controller, messenger }) => { + const fakeProvider = buildFakeProvider(); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + + const networkWillChange = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.NetworkWillChange, + operation: () => { + // Intentionally not awaited because we're capturing an event + // emitted partway through the operation + controller.resetConnection(); + }, + }); + + expect(networkWillChange).toBeTruthy(); + }, + ); + }); + + it('resets the network status to "unknown" before emitting networkDidChange', async () => { + await withController( + { + state: { + providerConfig: { + type: 'rpc', + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', }, }, }, - }, - async ({ controller, messenger }) => { - const fakeProviders = [ - buildFakeProvider([ + async ({ controller }) => { + const fakeProvider = buildFakeProvider([ { request: { method: 'net_version', }, response: SUCCESSFUL_NET_VERSION_RESPONSE, }, - ]), - buildFakeProvider([ - { - request: { - method: 'net_version', - }, - error: GENERIC_JSON_RPC_ERROR, + ]); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + await controller.initializeProvider(); + expect(controller.store.getState().networkStatus).toBe('available'); + + await waitForStateChanges({ + controller, + propertyPath: ['networkStatus'], + // 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 + // partway through the operation + controller.resetConnection(); }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url-1', - chainId: '0x111', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - rpcUrl: 'https://mock-rpc-url-2', - chainId: '0x222', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await waitForLookupNetworkToComplete({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); - const initialNetworkStatus = - controller.store.getState().networkStatus; - expect(initialNetworkStatus).toBe('available'); + }); - const networkWillChange = await waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.NetworkWillChange, - operation: () => { - // Intentionally not awaited because we're checking state - // partway through the operation - controller.setActiveNetwork('testNetworkConfiguration'); - }, - beforeResolving: () => { - expect(controller.store.getState().networkStatus).toBe( - initialNetworkStatus, - ); - }, - }); - expect(networkWillChange).toBeTruthy(); - }, - ); - }); + expect(controller.store.getState().networkStatus).toBe('unknown'); + }, + ); + }); - it('resets the network status to "unknown" before emitting networkDidChange', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url-1', - chainId: '0x111', - ticker: 'TEST1', - }, - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url-2', - chainId: '0x222', - ticker: 'TEST2', + it('clears EIP-1559 support for the network from state before emitting networkDidChange', async () => { + await withController( + { + state: { + providerConfig: { + type: 'rpc', + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', + }, + networkDetails: { + EIPS: {}, + other: 'details', }, }, }, - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ + async ({ controller }) => { + const fakeProvider = buildFakeProvider([ { request: { - method: 'net_version', + method: 'eth_getBlockByNumber', }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - ]), - buildFakeProvider([ - { - request: { - method: 'net_version', + response: { + result: POST_1559_BLOCK, }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url-1', - chainId: '0x111', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - rpcUrl: 'https://mock-rpc-url-2', - chainId: '0x222', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - expect(controller.store.getState().networkStatus).toBe('available'); + ]); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + await controller.initializeProvider(); + expect(controller.store.getState().networkDetails).toStrictEqual({ + EIPS: { + 1559: true, + }, + other: 'details', + }); - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - // We only care about the first state change, because it happens - // before networkDidChange - count: 1, - operation: () => { - // Intentionally not awaited because we're checking state - // partway through the operation. - controller.setActiveNetwork('testNetworkConfiguration'); - }, - beforeResolving: () => { - expect(controller.store.getState().networkStatus).toBe('unknown'); - }, - }); - }, - ); - }); + await waitForStateChanges({ + controller, + propertyPath: ['networkDetails'], + // 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 + // partway through the operation + controller.resetConnection(); + }, + }); + expect(controller.store.getState().networkDetails).toStrictEqual({ + EIPS: { + 1559: undefined, + }, + }); + }, + ); + }); - it('clears EIP-1559 support for the network from state before emitting networkDidChange', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url-1', - chainId: '0x111', - ticker: 'TEST1', - }, - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url-2', - chainId: '0x222', - ticker: 'TEST2', + it('initializes a new provider object pointed to the same RPC URL as the current network', async () => { + await withController( + { + state: { + providerConfig: { + type: 'rpc', + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', }, }, - networkDetails: { - EIPS: {}, - other: 'details', - }, }, - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ + async ({ controller }) => { + const fakeProvider = buildFakeProvider([ { request: { - method: 'eth_getBlockByNumber', + method: 'test', }, response: { - result: POST_1559_BLOCK, + result: 'test response', }, }, - ]), - buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: PRE_1559_BLOCK, - }, + ]); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + + await controller.resetConnection(); + + const { provider } = controller.getProviderAndBlockTracker(); + assert(provider, 'Provider is somehow unset'); + const promisifiedSendAsync = promisify(provider.sendAsync).bind( + provider, + ); + const response = await promisifiedSendAsync({ + id: '1', + jsonrpc: '2.0', + method: 'test', + }); + expect(response.result).toBe('test response'); + }, + ); + }); + + it('replaces the provider object underlying the provider proxy without creating a new instance of the proxy itself', async () => { + await withController( + { + state: { + providerConfig: { + type: 'rpc', + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url-1', - chainId: '0x111', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - rpcUrl: 'https://mock-rpc-url-2', - chainId: '0x222', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: true, }, - other: 'details', - }); + }, + async ({ controller }) => { + const fakeProvider = buildFakeProvider(); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + await controller.initializeProvider(); + const { provider: providerBefore } = + controller.getProviderAndBlockTracker(); - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - // We only care about the first state change, because it happens - // before networkDidChange - count: 1, - operation: () => { - // Intentionally not awaited because we're checking state - // partway through the operation - controller.setActiveNetwork('testNetworkConfiguration'); - }, - beforeResolving: () => { - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: undefined, - }, - }); - }, - }); - }, - ); - }); + await controller.resetConnection(); - it('initializes a provider pointed to the given RPC URL', async () => { - await withController( - { - state: { - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', + const { provider: providerAfter } = + controller.getProviderAndBlockTracker(); + expect(providerBefore).toBe(providerAfter); + }, + ); + }); + + it('emits networkDidChange', async () => { + await withController( + { + state: { + providerConfig: { + type: 'rpc', rpcUrl: 'https://mock-rpc-url', chainId: '0x1337', - ticker: 'TEST', }, }, }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'test', - }, - response: { - result: 'test response', + async ({ controller, messenger }) => { + const fakeProvider = buildFakeProvider(); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + + const networkDidChange = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.NetworkDidChange, + operation: async () => { + await controller.resetConnection(); }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClient); + }); - await controller.setActiveNetwork('testNetworkConfiguration'); + expect(networkDidChange).toBeTruthy(); + }, + ); + }); - const { provider } = controller.getProviderAndBlockTracker(); - assert(provider, 'Provider is somehow unset'); - const promisifiedSendAsync = promisify(provider.sendAsync).bind( - provider, - ); - const response = await promisifiedSendAsync({ - id: '1', - jsonrpc: '2.0', - method: 'test', - }); - expect(response.result).toBe('test response'); - }, - ); - }); - - it('replaces the provider object underlying the provider proxy without creating a new instance of the proxy itself', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url-1', - chainId: '0x111', - ticker: 'TEST1', - }, - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url-2', - chainId: '0x222', - ticker: 'TEST2', - }, - }, - }, - }, - async ({ controller }) => { - const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url-1', - chainId: '0x111', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - rpcUrl: 'https://mock-rpc-url-2', - chainId: '0x222', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - const { provider: providerBefore } = - controller.getProviderAndBlockTracker(); - - await controller.setActiveNetwork('testNetworkConfiguration'); - - const { provider: providerAfter } = - controller.getProviderAndBlockTracker(); - expect(providerBefore).toBe(providerAfter); - }, - ); - }); - - it('emits networkDidChange', async () => { - await withController( - { - state: { - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', + it('emits infuraIsUnblocked', async () => { + await withController( + { + state: { + providerConfig: { + type: 'rpc', rpcUrl: 'https://mock-rpc-url', chainId: '0x1337', - ticker: 'TEST', }, }, }, - }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClient); + async ({ controller, messenger }) => { + const fakeProvider = buildFakeProvider(); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - const networkDidChange = await waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.NetworkDidChange, - operation: async () => { - await controller.setActiveNetwork('testNetworkConfiguration'); - }, - }); + const infuraIsUnblocked = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + operation: async () => { + await controller.resetConnection(); + }, + }); - expect(networkDidChange).toBeTruthy(); - }, - ); - }); + expect(infuraIsUnblocked).toBeTruthy(); + }, + ); + }); - it('emits infuraIsUnblocked', async () => { - await withController( - { - state: { - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', + it('checks the status of the network again, updating state appropriately', async () => { + await withController( + { + state: { + providerConfig: { + type: 'rpc', rpcUrl: 'https://mock-rpc-url', chainId: '0x1337', - ticker: 'TEST', }, }, }, - }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClient); + async ({ controller }) => { + const fakeProvider = buildFakeProvider([ + { + request: { + method: 'net_version', + }, + response: SUCCESSFUL_NET_VERSION_RESPONSE, + }, + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + }, + ]); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - const infuraIsUnblocked = await waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.InfuraIsUnblocked, - operation: async () => { - await controller.setActiveNetwork('testNetworkConfiguration'); - }, - }); + await controller.resetConnection(); - expect(infuraIsUnblocked).toBeTruthy(); - }, - ); - }); + expect(controller.store.getState().networkStatus).toBe('available'); + }, + ); + }); - it('determines the status of the network, storing it in state', async () => { - await withController( - { - state: { - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', + it('ensures that EIP-1559 support for the current network is up to date', async () => { + await withController( + { + state: { + providerConfig: { + type: 'rpc', rpcUrl: 'https://mock-rpc-url', chainId: '0x1337', - ticker: 'TEST', }, - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', + networkDetails: { + EIPS: {}, + other: 'details', }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, }, - { - request: { - method: 'eth_getBlockByNumber', + }, + async ({ controller }) => { + const fakeProvider = buildFakeProvider([ + { + request: { + method: 'eth_getBlockByNumber', + }, + response: { + result: POST_1559_BLOCK, + }, }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClient); - - await controller.setActiveNetwork('testNetworkConfiguration'); + ]); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - expect(controller.store.getState().networkStatus).toBe('available'); - }, - ); - }); + await controller.resetConnection(); - it('determines whether the network supports EIP-1559, storing it in state', async () => { - await withController( - { - state: { - networkDetails: { - EIPS: {}, - other: 'details', - }, - networkConfigurations: { - testNetworkConfigurationId: { - id: 'testNetworkConfigurationId', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - ticker: 'TEST', + expect(controller.store.getState().networkDetails).toStrictEqual({ + EIPS: { + 1559: true, }, - }, + }); }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClient); - - await controller.setActiveNetwork('testNetworkConfigurationId'); - - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: true, - }, - }); - }, - ); + ); + }); }); }); - describe('setProviderType', () => { - for (const { - networkType, - chainId, - ticker, - blockExplorerUrl, - } of INFURA_NETWORKS) { - describe(`given a type of "${networkType}"`, () => { - it(`overwrites the provider configuration using type: "${networkType}", chainId: "${chainId}", ticker "${ticker}", and blockExplorerUrl "${blockExplorerUrl}", clearing rpcUrl and nickname`, async () => { + describe('rollbackToPreviousProvider', () => { + for (const { networkType } of INFURA_NETWORKS) { + describe(`if the previous provider configuration had a type of "${networkType}"`, () => { + it('overwrites the the current provider configuration with the previous provider configuration', async () => { await withController( { state: { providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - nickname: 'test-chain', - ticker: 'TEST', + type: networkType, + // NOTE: This doesn't need to match the logical chain ID + // of the network selected, it just needs to exist + chainId: '0x111', + // NOTE: This doesn't need to match the logical chain ID + // of the network selected, it just needs to exist + rpcUrl: 'https://mock-rpc-url-1', + ticker: 'TEST1', + nickname: 'test network 1', rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer.com', + blockExplorerUrl: 'https://test-block-explorer-1.com', + }, + }, + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', + rpcUrl: 'https://mock-rpc-url-2', + chainId: '0x222', + ticker: 'TEST2', + nickname: 'test network 2', + rpcPrefs: { + blockExplorerUrl: 'https://test-block-explorer-2.com', + }, }, }, }, + infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + rpcUrl: 'https://mock-rpc-url-2', + chainId: '0x222', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + network: networkType, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.setActiveNetwork('testNetworkConfiguration'); + expect(controller.store.getState().providerConfig).toStrictEqual({ + type: 'rpc', + id: 'testNetworkConfiguration', + rpcUrl: 'https://mock-rpc-url-2', + chainId: '0x222', + ticker: 'TEST2', + nickname: 'test network 2', + rpcPrefs: { + blockExplorerUrl: 'https://test-block-explorer-2.com', + }, + }); - controller.setProviderType(networkType); + await waitForLookupNetworkToComplete({ + controller, + operation: async () => { + await controller.rollbackToPreviousProvider(); + }, + }); expect(controller.store.getState().providerConfig).toStrictEqual({ type: networkType, - rpcUrl: undefined, - chainId, - ticker, - nickname: undefined, - rpcPrefs: { blockExplorerUrl }, + chainId: '0x111', + rpcUrl: 'https://mock-rpc-url-1', + ticker: 'TEST1', + nickname: 'test network 1', + rpcPrefs: { + blockExplorerUrl: 'https://test-block-explorer-1.com', + }, }); }, ); }); it('emits networkWillChange', async () => { - await withController(async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - const networkWillChange = await waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.NetworkWillChange, - operation: () => { - // Intentionally not awaited because we're capturing an event - // emitted partway through the operation - controller.setProviderType(networkType); - }, - }); - - expect(networkWillChange).toBeTruthy(); - }); - }); - - it('resets the network status to "unknown" before emitting networkDidChange', async () => { await withController( { state: { providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - ticker: 'TEST', + type: networkType, + // NOTE: This doesn't need to match the logical chain ID of + // the network selected, it just needs to exist + chainId: '0x111', + }, + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', + rpcUrl: 'https://mock-rpc-url', + chainId: '0x222', + ticker: 'TEST', + }, }, }, infuraProjectId: 'some-infura-project-id', }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - ]), - buildFakeProvider(), - ]; + async ({ controller, messenger }) => { + const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; const fakeNetworkClients = [ buildFakeClient(fakeProviders[0]), buildFakeClient(fakeProviders[1]), @@ -4957,7 +4359,7 @@ describe('NetworkController', () => { mockCreateNetworkClient() .calledWith({ rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', + chainId: '0x222', type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[0]) @@ -4967,45 +4369,43 @@ describe('NetworkController', () => { type: NetworkClientType.Infura, }) .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - expect(controller.store.getState().networkStatus).toBe( - 'available', - ); + await controller.setActiveNetwork('testNetworkConfiguration'); - await waitForStateChanges({ + await waitForLookupNetworkToComplete({ controller, - propertyPath: ['networkStatus'], - // We only care about the first state change, because it - // happens before networkDidChange - count: 1, - operation: () => { - // Intentionally not awaited because we're checking the state - // partway through the operation - controller.setProviderType(networkType); - }, - beforeResolving: () => { - expect(controller.store.getState().networkStatus).toBe( - 'unknown', - ); + operation: async () => { + const networkWillChange = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.NetworkWillChange, + operation: async () => { + await controller.rollbackToPreviousProvider(); + }, + }); + + expect(networkWillChange).toBeTruthy(); }, }); }, ); }); - it('clears EIP-1559 support for the network from state before emitting networkDidChange', async () => { + it('resets the network status to "unknown" before emitting networkDidChange', async () => { await withController( { state: { providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - ticker: 'TEST', + type: networkType, + // NOTE: This doesn't need to match the logical chain ID of + // the network selected, it just needs to exist + chainId: '0x111', }, - networkDetails: { - EIPS: {}, - other: 'details', + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', + rpcUrl: 'https://mock-rpc-url', + chainId: '0x222', + ticker: 'TEST', + }, }, }, infuraProjectId: 'some-infura-project-id', @@ -5015,23 +4415,18 @@ describe('NetworkController', () => { buildFakeProvider([ { request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, + method: 'net_version', }, + response: SUCCESSFUL_NET_VERSION_RESPONSE, }, - ]), - buildFakeProvider([ { request: { method: 'eth_getBlockByNumber', }, - response: { - result: PRE_1559_BLOCK, - }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, }, ]), + buildFakeProvider(), ]; const fakeNetworkClients = [ buildFakeClient(fakeProviders[0]), @@ -5040,7 +4435,7 @@ describe('NetworkController', () => { mockCreateNetworkClient() .calledWith({ rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', + chainId: '0x222', type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[0]) @@ -5050,31 +4445,29 @@ describe('NetworkController', () => { type: NetworkClientType.Infura, }) .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: true, - }, - other: 'details', - }); + await controller.setActiveNetwork('testNetworkConfiguration'); + expect(controller.store.getState().networkStatus).toBe( + 'available', + ); - await waitForStateChanges({ + await waitForLookupNetworkToComplete({ controller, - propertyPath: ['networkDetails'], - // We only care about the first state change, because it - // happens before networkDidChange - count: 1, - operation: () => { - // Intentionally not awaited because we're checking the state - // partway through the operation - controller.setProviderType(networkType); - }, - beforeResolving: () => { - expect( - controller.store.getState().networkDetails, - ).toStrictEqual({ - EIPS: { - 1559: undefined, + operation: async () => { + await waitForStateChanges({ + controller, + propertyPath: ['networkStatus'], + // 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 + controller.rollbackToPreviousProvider(); + }, + beforeResolving: () => { + expect(controller.store.getState().networkStatus).toBe( + 'unknown', + ); }, }); }, @@ -5083,30 +4476,159 @@ describe('NetworkController', () => { ); }); - it(`initializes a provider pointed to the "${networkType}" Infura network`, async () => { + it('clears EIP-1559 support for the network from state before emitting networkDidChange', async () => { await withController( - { infuraProjectId: 'some-infura-project-id' }, + { + state: { + providerConfig: { + type: networkType, + // NOTE: This doesn't need to match the logical chain ID of + // the network selected, it just needs to exist + chainId: '0x111', + }, + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', + rpcUrl: 'https://mock-rpc-url', + chainId: '0x222', + ticker: 'TEST', + }, + }, + networkDetails: { + EIPS: {}, + other: 'details', + }, + }, + infuraProjectId: 'some-infura-project-id', + }, async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'test', + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'eth_getBlockByNumber', + }, + response: { + result: POST_1559_BLOCK, + }, }, - response: { - result: 'test response', + ]), + buildFakeProvider(), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + rpcUrl: 'https://mock-rpc-url', + chainId: '0x222', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + network: networkType, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.setActiveNetwork('testNetworkConfiguration'); + expect(controller.store.getState().networkDetails).toStrictEqual({ + EIPS: { + 1559: true, + }, + }); + + await waitForLookupNetworkToComplete({ + controller, + operation: async () => { + await waitForStateChanges({ + controller, + propertyPath: ['networkDetails'], + // 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 + controller.rollbackToPreviousProvider(); + }, + beforeResolving: () => { + expect( + controller.store.getState().networkDetails, + ).toStrictEqual({ + EIPS: { + 1559: undefined, + }, + }); + }, + }); + }, + }); + }, + ); + }); + + it(`initializes a provider pointed to the "${networkType}" Infura network`, async () => { + await withController( + { + state: { + providerConfig: { + type: networkType, + // NOTE: This doesn't need to match the logical chain ID of + // the network selected, it just needs to exist + chainId: '0x111', + }, + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', + rpcUrl: 'https://mock-rpc-url', + chainId: '0x222', + ticker: 'TEST', }, }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); + }, + infuraProjectId: 'some-infura-project-id', + }, + async ({ controller }) => { + const fakeProviders = [ + buildFakeProvider(), + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response', + }, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; mockCreateNetworkClient() + .calledWith({ + rpcUrl: 'https://mock-rpc-url', + chainId: '0x222', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[0]) .calledWith({ network: networkType, infuraProjectId: 'some-infura-project-id', type: NetworkClientType.Infura, }) - .mockReturnValue(fakeNetworkClient); + .mockReturnValue(fakeNetworkClients[1]); + await controller.setActiveNetwork('testNetworkConfiguration'); - await controller.setProviderType(networkType); + await waitForLookupNetworkToComplete({ + controller, + operation: async () => { + await controller.rollbackToPreviousProvider(); + }, + }); const { provider } = controller.getProviderAndBlockTracker(); assert(provider, 'Provider is somehow unset'); @@ -5128,10 +4650,18 @@ describe('NetworkController', () => { { state: { providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - ticker: 'TEST', + type: networkType, + // NOTE: This doesn't need to match the logical chain ID of + // the network selected, it just needs to exist + chainId: '0x111', + }, + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', + rpcUrl: 'https://mock-rpc-url', + chainId: '0x222', + ticker: 'TEST', + }, }, }, infuraProjectId: 'some-infura-project-id', @@ -5145,7 +4675,7 @@ describe('NetworkController', () => { mockCreateNetworkClient() .calledWith({ rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', + chainId: '0x222', type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[0]) @@ -5155,11 +4685,16 @@ describe('NetworkController', () => { type: NetworkClientType.Infura, }) .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); + await controller.setActiveNetwork('testNetworkConfiguration'); const { provider: providerBefore } = controller.getProviderAndBlockTracker(); - await controller.setProviderType(networkType); + await waitForLookupNetworkToComplete({ + controller, + operation: async () => { + await controller.rollbackToPreviousProvider(); + }, + }); const { provider: providerAfter } = controller.getProviderAndBlockTracker(); @@ -5169,143 +4704,67 @@ describe('NetworkController', () => { }); it('emits networkDidChange', async () => { - await withController(async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - const networkDidChange = await waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.NetworkDidChange, - operation: async () => { - await controller.setProviderType(networkType); - }, - }); - - expect(networkDidChange).toBeTruthy(); - }); - }); - - it('emits infuraIsBlocked or infuraIsUnblocked, depending on whether Infura is blocking requests', async () => { - await withController(async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - const promiseForNoInfuraIsUnblockedEvents = waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.InfuraIsUnblocked, - count: 0, - }); - const promiseForInfuraIsBlocked = waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.InfuraIsBlocked, - }); - - await controller.setProviderType(networkType); - - expect(await promiseForNoInfuraIsUnblockedEvents).toBeTruthy(); - expect(await promiseForInfuraIsBlocked).toBeTruthy(); - }); - }); - - it('determines the status of the network, storing it in state', async () => { - await withController(async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - await controller.setProviderType(networkType); - - expect(controller.store.getState().networkStatus).toBe('available'); - }); - }); - - it('determines whether the network supports EIP-1559, storing it in state', async () => { - await withController( - { - state: { - networkDetails: { - EIPS: {}, - other: 'details', + await withController( + { + state: { + providerConfig: { + type: networkType, + // NOTE: This doesn't need to match the logical chain ID of + // the network selected, it just needs to exist + chainId: '0x111', + }, + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', + rpcUrl: 'https://mock-rpc-url', + chainId: '0x222', + ticker: 'TEST', + }, }, }, + infuraProjectId: 'some-infura-project-id', }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + async ({ controller, messenger }) => { + const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + rpcUrl: 'https://mock-rpc-url', + chainId: '0x222', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + network: networkType, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + }) + .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType(networkType); + await controller.setActiveNetwork('testNetworkConfiguration'); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: true, + await waitForLookupNetworkToComplete({ + controller, + operation: async () => { + const networkDidChange = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.NetworkDidChange, + operation: async () => { + await controller.rollbackToPreviousProvider(); + }, + }); + + expect(networkDidChange).toBeTruthy(); }, }); }, ); }); - }); - } - - describe('given a type of "rpc"', () => { - it('throws', async () => { - await withController(async ({ controller }) => { - await expect(() => controller.setProviderType('rpc')).rejects.toThrow( - new Error( - 'NetworkController - cannot call "setProviderType" with type "rpc". Use "setActiveNetwork"', - ), - ); - }); - }); - }); - - describe('given an invalid Infura network name', () => { - it('throws', async () => { - await withController(async ({ controller }) => { - await expect(() => - controller.setProviderType('sadlflaksdj'), - ).rejects.toThrow( - new Error('Unknown Infura provider type "sadlflaksdj".'), - ); - }); - }); - }); - }); - describe('resetConnection', () => { - for (const { networkType } of INFURA_NETWORKS) { - describe(`when the type in the provider configuration is "${networkType}"`, () => { - it('emits networkWillChange', async () => { + it('emits infuraIsBlocked or infuraIsUnblocked, depending on whether Infura is blocking requests for the previous network', async () => { await withController( { state: { @@ -5313,31 +4772,74 @@ describe('NetworkController', () => { type: networkType, // NOTE: This doesn't need to match the logical chain ID of // the network selected, it just needs to exist - chainId: '0x9999999', + chainId: '0x111', + }, + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', + rpcUrl: 'https://mock-rpc-url', + chainId: '0x222', + ticker: 'TEST', + }, }, }, + infuraProjectId: 'some-infura-project-id', }, async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - const networkWillChange = await waitForPublishedEvents({ + const fakeProviders = [ + buildFakeProvider(), + buildFakeProvider([ + { + request: { + method: 'eth_getBlockByNumber', + }, + error: BLOCKED_INFURA_JSON_RPC_ERROR, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + rpcUrl: 'https://mock-rpc-url', + chainId: '0x222', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + network: networkType, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.setActiveNetwork('testNetworkConfiguration'); + const promiseForNoInfuraIsUnblockedEvents = + waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + count: 0, + }); + const promiseForInfuraIsBlocked = waitForPublishedEvents({ messenger, - eventType: NetworkControllerEventType.NetworkWillChange, - operation: () => { - // Intentionally not awaited because we want to capture an - // event emitted partway throught this operation - controller.resetConnection(); + eventType: NetworkControllerEventType.InfuraIsBlocked, + }); + + await waitForLookupNetworkToComplete({ + controller, + operation: async () => { + await controller.rollbackToPreviousProvider(); }, }); - expect(networkWillChange).toBeTruthy(); + expect(await promiseForNoInfuraIsUnblockedEvents).toBeTruthy(); + expect(await promiseForInfuraIsBlocked).toBeTruthy(); }, ); }); - it('resets the network status to "unknown" before emitting networkDidChange', async () => { + it('checks the status of the previous network again and updates state accordingly', async () => { await withController( { state: { @@ -5345,45 +4847,75 @@ describe('NetworkController', () => { type: networkType, // NOTE: This doesn't need to match the logical chain ID of // the network selected, it just needs to exist - chainId: '0x9999999', + chainId: '0x111', + }, + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', + rpcUrl: 'https://mock-rpc-url', + chainId: '0x222', + ticker: 'TEST', + }, }, }, + infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'net_version', + }, + error: ethErrors.rpc.methodNotFound(), }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.initializeProvider(); + ]), + buildFakeProvider([ + { + request: { + method: 'net_version', + }, + response: SUCCESSFUL_NET_VERSION_RESPONSE, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + rpcUrl: 'https://mock-rpc-url', + chainId: '0x222', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + network: networkType, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.setActiveNetwork('testNetworkConfiguration'); expect(controller.store.getState().networkStatus).toBe( - 'available', + 'unavailable', ); await waitForStateChanges({ controller, propertyPath: ['networkStatus'], - // We only care about the first state change, because it - // happens before networkDidChange - count: 1, - operation: () => { - // Intentionally not awaited because we want to capture a - // state change made partway through the operation - controller.resetConnection(); + operation: async () => { + await controller.rollbackToPreviousProvider(); }, }); - - expect(controller.store.getState().networkStatus).toBe('unknown'); + expect(controller.store.getState().networkStatus).toBe( + 'available', + ); }, ); }); - it('clears EIP-1559 support for the network from state before emitting networkDidChange', async () => { + it('checks whether the previous network supports EIP-1559 again and updates state accordingly', async () => { await withController( { state: { @@ -5391,210 +4923,226 @@ describe('NetworkController', () => { type: networkType, // NOTE: This doesn't need to match the logical chain ID of // the network selected, it just needs to exist - chainId: '0x9999999', + chainId: '0x111', + }, + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', + rpcUrl: 'https://mock-rpc-url', + chainId: '0x222', + ticker: 'TEST', + }, + }, + networkDetails: { + EIPS: {}, + other: 'details', }, }, + infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'eth_getBlockByNumber', + }, + response: { + result: PRE_1559_BLOCK, + }, }, - response: { - result: POST_1559_BLOCK, + ]), + buildFakeProvider([ + { + request: { + method: 'eth_getBlockByNumber', + }, + response: { + result: POST_1559_BLOCK, + }, }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.initializeProvider(); + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + rpcUrl: 'https://mock-rpc-url', + chainId: '0x222', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + network: networkType, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.setActiveNetwork('testNetworkConfiguration'); expect(controller.store.getState().networkDetails).toStrictEqual({ EIPS: { - 1559: true, + 1559: false, }, }); await waitForStateChanges({ controller, propertyPath: ['networkDetails'], - // 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 - // partway through the operation - controller.resetConnection(); + // rollbackToPreviousProvider clears networkDetails first, and + // then updates it to what we expect it to be + count: 2, + operation: async () => { + await controller.rollbackToPreviousProvider(); }, }); - expect(controller.store.getState().networkDetails).toStrictEqual({ EIPS: { - 1559: undefined, + 1559: true, }, }); }, ); }); + }); + } - it(`initializes a new provider object pointed to the current Infura network (type: "${networkType}")`, async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x9999999', + describe(`if the previous provider configuration had a type of "rpc"`, () => { + it('overwrites the the current provider configuration with the previous provider configuration', async () => { + await withController( + { + state: { + providerConfig: { + type: 'rpc', + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', + nickname: 'network', + ticker: 'TEST', + rpcPrefs: { + blockExplorerUrl: 'https://test-block-explorer.com', }, }, }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'test', - }, - response: { - result: 'test response', - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - await controller.resetConnection(); - - const { provider } = controller.getProviderAndBlockTracker(); - assert(provider, 'Provider is somehow unset'); - const promisifiedSendAsync = promisify(provider.sendAsync).bind( - provider, - ); - const response = await promisifiedSendAsync({ - id: '1', - jsonrpc: '2.0', - method: 'test', - }); - expect(response.result).toBe('test response'); - }, - ); - }); - - it('replaces the provider object underlying the provider proxy without creating a new instance of the proxy itself', async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x9999999', - }, + infuraProjectId: 'some-infura-project-id', + }, + async ({ controller }) => { + const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + network: NETWORK_TYPES.GOERLI, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.setProviderType('goerli'); + expect(controller.store.getState().providerConfig).toStrictEqual({ + type: 'goerli', + rpcUrl: undefined, + chainId: '0x5', + ticker: 'GoerliETH', + nickname: undefined, + rpcPrefs: { + blockExplorerUrl: 'https://goerli.etherscan.io', }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.initializeProvider(); - const { provider: providerBefore } = - controller.getProviderAndBlockTracker(); - - await controller.resetConnection(); + }); - const { provider: providerAfter } = - controller.getProviderAndBlockTracker(); - expect(providerBefore).toBe(providerAfter); - }, - ); - }); + await waitForLookupNetworkToComplete({ + controller, + operation: async () => { + await controller.rollbackToPreviousProvider(); + }, + }); + expect(controller.store.getState().providerConfig).toStrictEqual({ + type: 'rpc', + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', + nickname: 'network', + ticker: 'TEST', + rpcPrefs: { + blockExplorerUrl: 'https://test-block-explorer.com', + }, + }); + }, + ); + }); - it('emits networkDidChange', async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x9999999', - }, + it('emits networkWillChange', async () => { + await withController( + { + state: { + providerConfig: { + type: 'rpc', + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', }, }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - const networkDidChange = await waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.NetworkDidChange, - operation: async () => { - await controller.resetConnection(); - }, - }); - - expect(networkDidChange).toBeTruthy(); - }, - ); - }); + infuraProjectId: 'some-infura-project-id', + }, + async ({ controller, messenger }) => { + const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + network: NETWORK_TYPES.GOERLI, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.setProviderType('goerli'); - it('emits infuraIsBlocked or infuraIsUnblocked, depending on whether Infura is blocking requests', async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x9999999', - }, - }, - }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - const promiseForNoInfuraIsUnblockedEvents = - waitForPublishedEvents({ + await waitForLookupNetworkToComplete({ + controller, + operation: async () => { + const networkWillChange = await waitForPublishedEvents({ messenger, - eventType: NetworkControllerEventType.InfuraIsUnblocked, - count: 0, + eventType: NetworkControllerEventType.NetworkWillChange, + operation: async () => { + await controller.rollbackToPreviousProvider(); + }, }); - const promiseForInfuraIsBlocked = waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.InfuraIsBlocked, - }); - - await controller.resetConnection(); - expect(await promiseForNoInfuraIsUnblockedEvents).toBeTruthy(); - expect(await promiseForInfuraIsBlocked).toBeTruthy(); - }, - ); - }); + expect(networkWillChange).toBeTruthy(); + }, + }); + }, + ); + }); - it('checks the status of the network again, updating state appropriately', async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x9999999', - }, + it('resets the network state to "unknown" before emitting networkDidChange', async () => { + await withController( + { + state: { + providerConfig: { + type: 'rpc', + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', }, }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ + infuraProjectId: 'some-infura-project-id', + }, + async ({ controller }) => { + const fakeProviders = [ + buildFakeProvider([ { request: { method: 'net_version', @@ -5607,95 +5155,56 @@ describe('NetworkController', () => { }, response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - await controller.resetConnection(); - - expect(controller.store.getState().networkStatus).toBe( - 'available', - ); - }, - ); - }); - - it('checks whether the network supports EIP-1559 again, updating state appropriately', async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x9999999', - }, - networkDetails: { - EIPS: {}, - other: 'details', - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - await controller.resetConnection(); - - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: true, - }, - }); - }, - ); - }); - }); - } - - describe(`when the type in the provider configuration is "rpc"`, () => { - it('emits networkWillChange', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', + ]), + buildFakeProvider(), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + network: NETWORK_TYPES.GOERLI, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ rpcUrl: 'https://mock-rpc-url', chainId: '0x1337', - }, - }, - }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.setProviderType('goerli'); + expect(controller.store.getState().networkStatus).toBe('available'); - const networkWillChange = await waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.NetworkWillChange, - operation: () => { - // Intentionally not awaited because we're capturing an event - // emitted partway through the operation - controller.resetConnection(); + await waitForLookupNetworkToComplete({ + controller, + operation: async () => { + await waitForStateChanges({ + controller, + propertyPath: ['networkStatus'], + // 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 + controller.rollbackToPreviousProvider(); + }, + beforeResolving: () => { + expect(controller.store.getState().networkStatus).toBe( + 'unknown', + ); + }, + }); }, }); - - expect(networkWillChange).toBeTruthy(); }, ); }); - it('resets the network status to "unknown" before emitting networkDidChange', async () => { + it('clears EIP-1559 support for the network from state before emitting networkDidChange', async () => { await withController( { state: { @@ -5705,97 +5214,77 @@ describe('NetworkController', () => { chainId: '0x1337', }, }, + infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'eth_getBlockByNumber', + }, + response: { + result: POST_1559_BLOCK, + }, }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.initializeProvider(); - expect(controller.store.getState().networkStatus).toBe('available'); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - // 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 - // partway through the operation - controller.resetConnection(); - }, - }); - - expect(controller.store.getState().networkStatus).toBe('unknown'); - }, - ); - }); - - it('clears EIP-1559 support for the network from state before emitting networkDidChange', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', + ]), + buildFakeProvider(), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + network: NETWORK_TYPES.GOERLI, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ rpcUrl: 'https://mock-rpc-url', chainId: '0x1337', - }, - networkDetails: { - EIPS: {}, - other: 'details', - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.initializeProvider(); + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.setProviderType('goerli'); expect(controller.store.getState().networkDetails).toStrictEqual({ EIPS: { 1559: true, }, - other: 'details', }); - await waitForStateChanges({ + await waitForLookupNetworkToComplete({ controller, - propertyPath: ['networkDetails'], - // 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 - // partway through the operation - controller.resetConnection(); - }, - }); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: undefined, + operation: async () => { + await waitForStateChanges({ + controller, + propertyPath: ['networkDetails'], + // 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 + controller.rollbackToPreviousProvider(); + }, + beforeResolving: () => { + expect( + controller.store.getState().networkDetails, + ).toStrictEqual({ + EIPS: { + 1559: undefined, + }, + }); + }, + }); }, }); }, ); }); - it('initializes a new provider object pointed to the same RPC URL as the current network', async () => { + it('initializes a provider pointed to the given RPC URL', async () => { await withController( { state: { @@ -5805,22 +5294,47 @@ describe('NetworkController', () => { chainId: '0x1337', }, }, + infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'test', - }, - response: { - result: 'test response', + const fakeProviders = [ + buildFakeProvider(), + buildFakeProvider([ + { + request: { + method: 'test', + }, + response: { + result: 'test response', + }, }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + network: NETWORK_TYPES.GOERLI, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.setProviderType('goerli'); - await controller.resetConnection(); + await waitForLookupNetworkToComplete({ + controller, + operation: async () => { + await controller.rollbackToPreviousProvider(); + }, + }); const { provider } = controller.getProviderAndBlockTracker(); assert(provider, 'Provider is somehow unset'); @@ -5847,16 +5361,37 @@ describe('NetworkController', () => { chainId: '0x1337', }, }, + infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.initializeProvider(); + const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + network: NETWORK_TYPES.GOERLI, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.setProviderType('goerli'); const { provider: providerBefore } = controller.getProviderAndBlockTracker(); - await controller.resetConnection(); + await waitForLookupNetworkToComplete({ + controller, + operation: async () => { + await controller.rollbackToPreviousProvider(); + }, + }); const { provider: providerAfter } = controller.getProviderAndBlockTracker(); @@ -5875,21 +5410,43 @@ describe('NetworkController', () => { chainId: '0x1337', }, }, + infuraProjectId: 'some-infura-project-id', }, async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + network: NETWORK_TYPES.GOERLI, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.setProviderType('goerli'); - const networkDidChange = await waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.NetworkDidChange, + await waitForLookupNetworkToComplete({ + controller, operation: async () => { - await controller.resetConnection(); + const networkDidChange = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.NetworkDidChange, + operation: async () => { + await controller.rollbackToPreviousProvider(); + }, + }); + + expect(networkDidChange).toBeTruthy(); }, }); - - expect(networkDidChange).toBeTruthy(); }, ); }); @@ -5904,26 +5461,48 @@ describe('NetworkController', () => { chainId: '0x1337', }, }, + infuraProjectId: 'some-infura-project-id', }, async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + network: NETWORK_TYPES.GOERLI, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.setProviderType('goerli'); - const infuraIsUnblocked = await waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.InfuraIsUnblocked, + await waitForLookupNetworkToComplete({ + controller, operation: async () => { - await controller.resetConnection(); + const infuraIsUnblocked = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + operation: async () => { + await controller.rollbackToPreviousProvider(); + }, + }); + + expect(infuraIsUnblocked).toBeTruthy(); }, }); - - expect(infuraIsUnblocked).toBeTruthy(); }, ); }); - it('checks the status of the network again, updating state appropriately', async () => { + it('checks the status of the previous network again and updates state accordingly', async () => { await withController( { state: { @@ -5933,33 +5512,62 @@ describe('NetworkController', () => { chainId: '0x1337', }, }, + infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'net_version', + }, + error: ethErrors.rpc.methodNotFound(), }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', + ]), + buildFakeProvider([ + { + request: { + method: 'net_version', + }, + response: SUCCESSFUL_NET_VERSION_RESPONSE, }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - await controller.resetConnection(); + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + network: NETWORK_TYPES.GOERLI, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.setProviderType('goerli'); + expect(controller.store.getState().networkStatus).toBe( + 'unavailable', + ); + await controller.rollbackToPreviousProvider(); expect(controller.store.getState().networkStatus).toBe('available'); }, ); }); - it('ensures that EIP-1559 support for the current network is up to date', async () => { + it('checks whether the previous network supports EIP-1559 again and updates state accordingly', async () => { await withController( { state: { @@ -5973,23 +5581,61 @@ describe('NetworkController', () => { other: 'details', }, }, + infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', + const fakeProviders = [ + buildFakeProvider([ + { + request: { + method: 'eth_getBlockByNumber', + }, + response: { + result: PRE_1559_BLOCK, + }, }, - response: { - result: POST_1559_BLOCK, + ]), + buildFakeProvider([ + { + request: { + method: 'eth_getBlockByNumber', + }, + response: { + result: POST_1559_BLOCK, + }, }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + network: NETWORK_TYPES.GOERLI, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.setProviderType('goerli'); + expect(controller.store.getState().networkDetails).toStrictEqual({ + EIPS: { + 1559: false, }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - await controller.resetConnection(); + }); + await waitForLookupNetworkToComplete({ + controller, + operation: async () => { + await controller.rollbackToPreviousProvider(); + }, + }); expect(controller.store.getState().networkDetails).toStrictEqual({ EIPS: { 1559: true, @@ -6001,1402 +5647,762 @@ describe('NetworkController', () => { }); }); - describe('rollbackToPreviousProvider', () => { - for (const { networkType } of INFURA_NETWORKS) { - describe(`if the previous provider configuration had a type of "${networkType}"`, () => { - it('overwrites the the current provider configuration with the previous provider configuration', async () => { - await withController( + describe('upsertNetworkConfiguration', () => { + it('throws if the given chain ID is not a 0x-prefixed hex number', async () => { + const invalidChainId = '1'; + await withController(async ({ controller }) => { + await expect(() => + controller.upsertNetworkConfiguration( { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID - // of the network selected, it just needs to exist - chainId: '0x111', - // NOTE: This doesn't need to match the logical chain ID - // of the network selected, it just needs to exist - rpcUrl: 'https://mock-rpc-url-1', - ticker: 'TEST1', - nickname: 'test network 1', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer-1.com', - }, - }, - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url-2', - chainId: '0x222', - ticker: 'TEST2', - nickname: 'test network 2', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer-2.com', - }, - }, - }, - }, - infuraProjectId: 'some-infura-project-id', + /* @ts-expect-error We are intentionally passing bad input. */ + chainId: invalidChainId, + nickname: 'RPC', + rpcPrefs: { blockExplorerUrl: 'test-block-explorer.com' }, + rpcUrl: 'rpc_url', + ticker: 'RPC', }, - async ({ controller }) => { - const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url-2', - chainId: '0x222', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); - expect(controller.store.getState().providerConfig).toStrictEqual({ - type: 'rpc', - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url-2', - chainId: '0x222', - ticker: 'TEST2', - nickname: 'test network 2', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer-2.com', - }, - }); - - await waitForLookupNetworkToComplete({ - controller, - operation: async () => { - await controller.rollbackToPreviousProvider(); - }, - }); - - expect(controller.store.getState().providerConfig).toStrictEqual({ - type: networkType, - chainId: '0x111', - rpcUrl: 'https://mock-rpc-url-1', - ticker: 'TEST1', - nickname: 'test network 1', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer-1.com', - }, - }); + { + referrer: 'https://test-dapp.com', + source: MetaMetricsNetworkEventSource.Dapp, }, - ); - }); + ), + ).rejects.toThrow( + new Error( + `Invalid chain ID "${invalidChainId}": invalid hex string.`, + ), + ); + }); + }); - it('emits networkWillChange', async () => { - await withController( + it('throws if the given chain ID is greater than the maximum allowed ID', async () => { + await withController(async ({ controller }) => { + await expect(() => + controller.upsertNetworkConfiguration( { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x111', - }, - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x222', - ticker: 'TEST', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller, messenger }) => { - const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: '0x222', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); - - await waitForLookupNetworkToComplete({ - controller, - operation: async () => { - const networkWillChange = await waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.NetworkWillChange, - operation: async () => { - await controller.rollbackToPreviousProvider(); - }, - }); - - expect(networkWillChange).toBeTruthy(); - }, - }); - }, - ); - }); - - it('resets the network status to "unknown" before emitting networkDidChange', async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x111', - }, - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x222', - ticker: 'TEST', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]), - buildFakeProvider(), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: '0x222', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); - expect(controller.store.getState().networkStatus).toBe( - 'available', - ); - - await waitForLookupNetworkToComplete({ - controller, - operation: async () => { - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - // 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 - controller.rollbackToPreviousProvider(); - }, - beforeResolving: () => { - expect(controller.store.getState().networkStatus).toBe( - 'unknown', - ); - }, - }); - }, - }); - }, - ); - }); - - it('clears EIP-1559 support for the network from state before emitting networkDidChange', async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x111', - }, - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x222', - ticker: 'TEST', - }, - }, - networkDetails: { - EIPS: {}, - other: 'details', - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ]), - buildFakeProvider(), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: '0x222', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: true, - }, - }); - - await waitForLookupNetworkToComplete({ - controller, - operation: async () => { - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - // 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 - controller.rollbackToPreviousProvider(); - }, - beforeResolving: () => { - expect( - controller.store.getState().networkDetails, - ).toStrictEqual({ - EIPS: { - 1559: undefined, - }, - }); - }, - }); - }, - }); + chainId: '0xFFFFFFFFFFFFFFFF', + nickname: 'RPC', + rpcPrefs: { blockExplorerUrl: 'test-block-explorer.com' }, + rpcUrl: 'rpc_url', + ticker: 'RPC', }, - ); - }); - - it(`initializes a provider pointed to the "${networkType}" Infura network`, async () => { - await withController( { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x111', - }, - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x222', - ticker: 'TEST', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider(), - buildFakeProvider([ - { - request: { - method: 'test', - }, - response: { - result: 'test response', - }, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: '0x222', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); - - await waitForLookupNetworkToComplete({ - controller, - operation: async () => { - await controller.rollbackToPreviousProvider(); - }, - }); - - const { provider } = controller.getProviderAndBlockTracker(); - assert(provider, 'Provider is somehow unset'); - const promisifiedSendAsync = promisify(provider.sendAsync).bind( - provider, - ); - const response = await promisifiedSendAsync({ - id: '1', - jsonrpc: '2.0', - method: 'test', - }); - expect(response.result).toBe('test response'); + referrer: 'https://test-dapp.com', + source: MetaMetricsNetworkEventSource.Dapp, }, - ); - }); + ), + ).rejects.toThrow( + new Error( + 'Invalid chain ID "0xFFFFFFFFFFFFFFFF": numerical value greater than max safe value.', + ), + ); + }); + }); - it('replaces the provider object underlying the provider proxy without creating a new instance of the proxy itself', async () => { - await withController( + it('throws if the no (or a falsy) rpcUrl is passed', async () => { + await withController(async ({ controller }) => { + await expect(() => + controller.upsertNetworkConfiguration( + /* @ts-expect-error We are intentionally passing bad input. */ { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x111', - }, - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x222', - ticker: 'TEST', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: '0x222', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); - const { provider: providerBefore } = - controller.getProviderAndBlockTracker(); - - await waitForLookupNetworkToComplete({ - controller, - operation: async () => { - await controller.rollbackToPreviousProvider(); - }, - }); - - const { provider: providerAfter } = - controller.getProviderAndBlockTracker(); - expect(providerBefore).toBe(providerAfter); - }, - ); - }); - - it('emits networkDidChange', async () => { - await withController( + chainId: '0x9999', + nickname: 'RPC', + rpcPrefs: { blockExplorerUrl: 'test-block-explorer.com' }, + ticker: 'RPC', + }, { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x111', - }, - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x222', - ticker: 'TEST', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', + referrer: 'https://test-dapp.com', + source: MetaMetricsNetworkEventSource.Dapp, }, - async ({ controller, messenger }) => { - const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: '0x222', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - - await controller.setActiveNetwork('testNetworkConfiguration'); - - await waitForLookupNetworkToComplete({ - controller, - operation: async () => { - const networkDidChange = await waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.NetworkDidChange, - operation: async () => { - await controller.rollbackToPreviousProvider(); - }, - }); + ), + ).rejects.toThrow( + new Error( + 'An rpcUrl is required to add or update network configuration', + ), + ); + }); + }); - expect(networkDidChange).toBeTruthy(); - }, - }); + it('throws if rpcUrl passed is not a valid Url', async () => { + await withController(async ({ controller }) => { + await expect(() => + controller.upsertNetworkConfiguration( + { + chainId: '0x9999', + nickname: 'RPC', + rpcPrefs: { blockExplorerUrl: 'test-block-explorer.com' }, + ticker: 'RPC', + rpcUrl: 'test', }, - ); - }); - - it('emits infuraIsBlocked or infuraIsUnblocked, depending on whether Infura is blocking requests for the previous network', async () => { - await withController( { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x111', - }, - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x222', - ticker: 'TEST', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', + referrer: 'https://test-dapp.com', + source: MetaMetricsNetworkEventSource.Dapp, }, - async ({ controller, messenger }) => { - const fakeProviders = [ - buildFakeProvider(), - buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: '0x222', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); - const promiseForNoInfuraIsUnblockedEvents = - waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.InfuraIsUnblocked, - count: 0, - }); - const promiseForInfuraIsBlocked = waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.InfuraIsBlocked, - }); - - await waitForLookupNetworkToComplete({ - controller, - operation: async () => { - await controller.rollbackToPreviousProvider(); - }, - }); + ), + ).rejects.toThrow(new Error('rpcUrl must be a valid URL')); + }); + }); - expect(await promiseForNoInfuraIsUnblockedEvents).toBeTruthy(); - expect(await promiseForInfuraIsBlocked).toBeTruthy(); + it('throws if the no (or a falsy) ticker is passed', async () => { + await withController(async ({ controller }) => { + await expect(() => + controller.upsertNetworkConfiguration( + /* @ts-expect-error We are intentionally passing bad input. */ + { + chainId: '0x5', + nickname: 'RPC', + rpcPrefs: { blockExplorerUrl: 'test-block-explorer.com' }, + rpcUrl: 'https://mock-rpc-url', }, - ); - }); - - it('checks the status of the previous network again and updates state accordingly', async () => { - await withController( { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x111', - }, - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x222', - ticker: 'TEST', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', + referrer: 'https://test-dapp.com', + source: MetaMetricsNetworkEventSource.Dapp, }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'net_version', - }, - error: ethErrors.rpc.methodNotFound(), - }, - ]), - buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: '0x222', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); - expect(controller.store.getState().networkStatus).toBe( - 'unavailable', - ); + ), + ).rejects.toThrow( + new Error( + 'A ticker is required to add or update networkConfiguration', + ), + ); + }); + }); + + it('throws if an options object is not passed as a second argument', async () => { + await withController(async ({ controller }) => { + await expect(() => + /* @ts-expect-error We are intentionally passing bad input. */ + controller.upsertNetworkConfiguration({ + chainId: '0x5', + nickname: 'RPC', + rpcPrefs: { blockExplorerUrl: 'test-block-explorer.com' }, + rpcUrl: 'https://mock-rpc-url', + }), + ).rejects.toThrow( + new Error( + "Cannot read properties of undefined (reading 'setActive')", + ), + ); + }); + }); + + it('should add the given network if all required properties are present but nither rpcPrefs nor nickname properties are passed', async () => { + uuidV4Mock.mockImplementationOnce(() => 'networkConfigurationId'); + await withController( + { + state: { + networkConfigurations: {}, + }, + }, + async ({ controller }) => { + const rpcUrlNetwork = { + chainId: '0x1' as const, + rpcUrl: 'https://test-rpc-url', + ticker: 'test_ticker', + }; - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.rollbackToPreviousProvider(); - }, - }); - expect(controller.store.getState().networkStatus).toBe( - 'available', - ); - }, - ); - }); + await controller.upsertNetworkConfiguration(rpcUrlNetwork, { + referrer: 'https://test-dapp.com', + source: MetaMetricsNetworkEventSource.Dapp, + }); - it('checks whether the previous network supports EIP-1559 again and updates state accordingly', async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x111', - }, - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x222', - ticker: 'TEST', - }, - }, - networkDetails: { - EIPS: {}, - other: 'details', - }, + expect( + Object.values(controller.store.getState().networkConfigurations), + ).toStrictEqual( + expect.arrayContaining([ + { + ...rpcUrlNetwork, + nickname: undefined, + rpcPrefs: undefined, + id: 'networkConfigurationId', }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - ]), - buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: '0x222', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: false, - }, - }); + ]), + ); + }, + ); + }); - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - // rollbackToPreviousProvider clears networkDetails first, and - // then updates it to what we expect it to be - count: 2, - operation: async () => { - await controller.rollbackToPreviousProvider(); - }, - }); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: true, - }, - }); - }, + it('adds new networkConfiguration to networkController store, but only adds valid properties (rpcUrl, chainId, ticker, nickname, rpcPrefs) and fills any missing properties from this list as undefined', async function () { + uuidV4Mock.mockImplementationOnce(() => 'networkConfigurationId'); + await withController( + { + state: { + networkConfigurations: {}, + }, + }, + async ({ controller }) => { + const rpcUrlNetwork = { + chainId: '0x1' as const, + rpcUrl: 'https://test-rpc-url', + ticker: 'test_ticker', + invalidKey: 'new-chain', + invalidKey2: {}, + }; + + await controller.upsertNetworkConfiguration(rpcUrlNetwork, { + referrer: 'https://test-dapp.com', + source: MetaMetricsNetworkEventSource.Dapp, + }); + + expect( + Object.values(controller.store.getState().networkConfigurations), + ).toStrictEqual( + expect.arrayContaining([ + { + chainId: '0x1', + rpcUrl: 'https://test-rpc-url', + ticker: 'test_ticker', + nickname: undefined, + rpcPrefs: undefined, + id: 'networkConfigurationId', + }, + ]), ); - }); - }); - } + }, + ); + }); - describe(`if the previous provider configuration had a type of "rpc"`, () => { - it('overwrites the the current provider configuration with the previous provider configuration', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - nickname: 'network', - ticker: 'TEST', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer.com', - }, + it('should add the given network configuration if its rpcURL does not match an existing configuration without changing or overwriting other configurations', async () => { + uuidV4Mock.mockImplementationOnce(() => 'networkConfigurationId2'); + await withController( + { + state: { + networkConfigurations: { + networkConfigurationId: { + rpcUrl: 'https://test-rpc-url', + ticker: 'ticker', + nickname: 'nickname', + rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, + chainId: '0x1', + id: 'networkConfigurationId', }, }, - infuraProjectId: 'some-infura-project-id', }, - async ({ controller }) => { - const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: NETWORK_TYPES.GOERLI, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); - expect(controller.store.getState().providerConfig).toStrictEqual({ - type: 'goerli', - rpcUrl: undefined, - chainId: '0x5', - ticker: 'GoerliETH', - nickname: undefined, - rpcPrefs: { - blockExplorerUrl: 'https://goerli.etherscan.io', - }, - }); + }, + async ({ controller }) => { + const rpcUrlNetwork = { + chainId: '0x1' as const, + nickname: 'RPC', + rpcPrefs: undefined, + rpcUrl: 'https://test-rpc-url-2', + ticker: 'RPC', + }; - await waitForLookupNetworkToComplete({ - controller, - operation: async () => { - await controller.rollbackToPreviousProvider(); - }, - }); - expect(controller.store.getState().providerConfig).toStrictEqual({ - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - nickname: 'network', - ticker: 'TEST', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer.com', + await controller.upsertNetworkConfiguration(rpcUrlNetwork, { + referrer: 'https://test-dapp.com', + source: MetaMetricsNetworkEventSource.Dapp, + }); + + expect( + Object.values(controller.store.getState().networkConfigurations), + ).toStrictEqual( + expect.arrayContaining([ + { + rpcUrl: 'https://test-rpc-url', + ticker: 'ticker', + nickname: 'nickname', + rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, + chainId: '0x1', + id: 'networkConfigurationId', }, - }); - }, - ); - }); + { ...rpcUrlNetwork, id: 'networkConfigurationId2' }, + ]), + ); + }, + ); + }); - it('emits networkWillChange', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', + it('should use the given configuration to update an existing network configuration that has a matching rpcUrl', async () => { + await withController( + { + state: { + networkConfigurations: { + networkConfigurationId: { + rpcUrl: 'https://test-rpc-url', + ticker: 'old_rpc_ticker', + nickname: 'old_rpc_chainName', + rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, + chainId: '0x1', + id: 'networkConfigurationId', }, }, - infuraProjectId: 'some-infura-project-id', }, - async ({ controller, messenger }) => { - const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: NETWORK_TYPES.GOERLI, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); + }, - await waitForLookupNetworkToComplete({ - controller, - operation: async () => { - const networkWillChange = await waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.NetworkWillChange, - operation: async () => { - await controller.rollbackToPreviousProvider(); - }, - }); + async ({ controller }) => { + const updatedConfiguration = { + rpcUrl: 'https://test-rpc-url', + ticker: 'new_rpc_ticker', + nickname: 'new_rpc_chainName', + rpcPrefs: { blockExplorerUrl: 'alternativetestchainscan.io' }, + chainId: '0x1' as const, + }; + await controller.upsertNetworkConfiguration(updatedConfiguration, { + referrer: 'https://test-dapp.com', + source: MetaMetricsNetworkEventSource.Dapp, + }); + expect( + Object.values(controller.store.getState().networkConfigurations), + ).toStrictEqual([ + { + rpcUrl: 'https://test-rpc-url', + nickname: 'new_rpc_chainName', + ticker: 'new_rpc_ticker', + rpcPrefs: { blockExplorerUrl: 'alternativetestchainscan.io' }, + chainId: '0x1', + id: 'networkConfigurationId', + }, + ]); + }, + ); + }); - expect(networkWillChange).toBeTruthy(); + it('should use the given configuration to update an existing network configuration that has a matching rpcUrl without changing or overwriting other networkConfigurations', async () => { + await withController( + { + state: { + networkConfigurations: { + networkConfigurationId: { + rpcUrl: 'https://test-rpc-url', + ticker: 'ticker', + nickname: 'nickname', + rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, + chainId: '0x1', + id: 'networkConfigurationId', }, - }); - }, - ); - }); - - it('resets the network state to "unknown" before emitting networkDidChange', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', + networkConfigurationId2: { + rpcUrl: 'https://test-rpc-url-2', + ticker: 'ticker-2', + nickname: 'nickname-2', + rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, + chainId: '0x9999', + id: 'networkConfigurationId2', }, }, - infuraProjectId: 'some-infura-project-id', }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]), - buildFakeProvider(), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: NETWORK_TYPES.GOERLI, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); - expect(controller.store.getState().networkStatus).toBe('available'); + }, + async ({ controller }) => { + await controller.upsertNetworkConfiguration( + { + rpcUrl: 'https://test-rpc-url', + ticker: 'new-ticker', + nickname: 'new-nickname', + rpcPrefs: { blockExplorerUrl: 'alternativetestchainscan.io' }, + chainId: '0x1', + }, + { + referrer: 'https://test-dapp.com', + source: MetaMetricsNetworkEventSource.Dapp, + }, + ); - await waitForLookupNetworkToComplete({ - controller, - operation: async () => { - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - // 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 - controller.rollbackToPreviousProvider(); - }, - beforeResolving: () => { - expect(controller.store.getState().networkStatus).toBe( - 'unknown', - ); - }, - }); - }, - }); - }, - ); - }); + expect( + Object.values(controller.store.getState().networkConfigurations), + ).toStrictEqual([ + { + rpcUrl: 'https://test-rpc-url', + ticker: 'new-ticker', + nickname: 'new-nickname', + rpcPrefs: { blockExplorerUrl: 'alternativetestchainscan.io' }, + chainId: '0x1', + id: 'networkConfigurationId', + }, + { + rpcUrl: 'https://test-rpc-url-2', + ticker: 'ticker-2', + nickname: 'nickname-2', + rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, + chainId: '0x9999', + id: 'networkConfigurationId2', + }, + ]); + }, + ); + }); - it('clears EIP-1559 support for the network from state before emitting networkDidChange', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', + it('should add the given network and not set it to active if the setActive option is not passed (or a falsy value is passed)', async () => { + uuidV4Mock.mockImplementationOnce(() => 'networkConfigurationId'); + const originalProviderConfig = { + type: NETWORK_TYPES.RPC, + rpcUrl: 'https://mock-rpc-url', + chainId: '0xtest' as const, + ticker: 'TEST', + }; + await withController( + { + state: { + providerConfig: originalProviderConfig, + networkConfigurations: { + testNetworkConfigurationId: { rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', + chainId: '0xtest', + ticker: 'TEST', + id: 'testNetworkConfigurationId', }, }, - infuraProjectId: 'some-infura-project-id', }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ]), - buildFakeProvider(), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: NETWORK_TYPES.GOERLI, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: true, - }, - }); + }, + async ({ controller }) => { + const rpcUrlNetwork = { + chainId: '0x1' as const, + rpcUrl: 'https://test-rpc-url', + ticker: 'test_ticker', + }; - await waitForLookupNetworkToComplete({ - controller, - operation: async () => { - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - // 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 - controller.rollbackToPreviousProvider(); - }, - beforeResolving: () => { - expect( - controller.store.getState().networkDetails, - ).toStrictEqual({ - EIPS: { - 1559: undefined, - }, - }); - }, - }); - }, - }); - }, - ); - }); + await controller.upsertNetworkConfiguration(rpcUrlNetwork, { + referrer: 'https://test-dapp.com', + source: MetaMetricsNetworkEventSource.Dapp, + }); - it('initializes a provider pointed to the given RPC URL', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', + expect(controller.store.getState().providerConfig).toStrictEqual( + originalProviderConfig, + ); + }, + ); + }); + + it('should add the given network and set it to active if the setActive option is passed as true', async () => { + uuidV4Mock.mockImplementationOnce(() => 'networkConfigurationId'); + await withController( + { + state: { + providerConfig: { + type: 'rpc', + rpcUrl: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + }, + networkConfigurations: { + testNetworkConfigurationId: { rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', + chainId: '0xtest', + ticker: 'TEST', + id: 'testNetworkConfigurationId', }, }, - infuraProjectId: 'some-infura-project-id', }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider(), - buildFakeProvider([ - { - request: { - method: 'test', - }, - response: { - result: 'test response', - }, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: NETWORK_TYPES.GOERLI, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); + }, + async ({ controller }) => { + const fakeProvider = buildFakeProvider(); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + const rpcUrlNetwork = { + chainId: '0x1' as const, + rpcUrl: 'https://test-rpc-url', + ticker: 'test_ticker', + }; - await waitForLookupNetworkToComplete({ - controller, - operation: async () => { - await controller.rollbackToPreviousProvider(); - }, - }); + await controller.upsertNetworkConfiguration(rpcUrlNetwork, { + setActive: true, + referrer: 'https://test-dapp.com', + source: MetaMetricsNetworkEventSource.Dapp, + }); - const { provider } = controller.getProviderAndBlockTracker(); - assert(provider, 'Provider is somehow unset'); - const promisifiedSendAsync = promisify(provider.sendAsync).bind( - provider, - ); - const response = await promisifiedSendAsync({ - id: '1', - jsonrpc: '2.0', - method: 'test', - }); - expect(response.result).toBe('test response'); + expect(controller.store.getState().providerConfig).toStrictEqual({ + ...rpcUrlNetwork, + nickname: undefined, + rpcPrefs: undefined, + type: 'rpc', + id: 'networkConfigurationId', + }); + }, + ); + }); + + it('adds new networkConfiguration to networkController store and calls to the metametrics event tracking with the correct values', async () => { + uuidV4Mock.mockImplementationOnce(() => 'networkConfigurationId'); + const trackEventSpy = jest.fn(); + await withController( + { + state: { + providerConfig: { + type: 'rpc', + rpcUrl: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + }, + networkConfigurations: { + testNetworkConfigurationId: { + rpcUrl: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + id: 'testNetworkConfigurationId', + }, + }, }, - ); - }); + trackMetaMetricsEvent: trackEventSpy, + }, + async ({ controller }) => { + const newNetworkConfiguration = { + rpcUrl: 'https://new-chain-rpc-url', + chainId: '0x9999' as const, + ticker: 'NEW', + nickname: 'new-chain', + rpcPrefs: { blockExplorerUrl: 'https://block-explorer' }, + }; - it('replaces the provider object underlying the provider proxy without creating a new instance of the proxy itself', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', + await controller.upsertNetworkConfiguration(newNetworkConfiguration, { + referrer: 'https://test-dapp.com', + source: MetaMetricsNetworkEventSource.Dapp, + }); + + expect( + Object.values(controller.store.getState().networkConfigurations), + ).toStrictEqual([ + { + rpcUrl: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + id: 'testNetworkConfigurationId', + }, + { + ...newNetworkConfiguration, + id: 'networkConfigurationId', + }, + ]); + expect(trackEventSpy).toHaveBeenCalledWith({ + event: 'Custom Network Added', + category: 'Network', + referrer: { + url: 'https://test-dapp.com', + }, + properties: { + chain_id: '0x9999', + symbol: 'NEW', + source: 'dapp', + }, + }); + }, + ); + }); + + it('throws if referrer and source arguments are not passed', async () => { + uuidV4Mock.mockImplementationOnce(() => 'networkConfigurationId'); + const trackEventSpy = jest.fn(); + await withController( + { + state: { + providerConfig: { + type: 'rpc', + rpcUrl: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + }, + networkConfigurations: { + testNetworkConfigurationId: { rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', + chainId: '0xtest', + ticker: 'TEST', + id: 'testNetworkConfigurationId', }, }, - infuraProjectId: 'some-infura-project-id', }, - async ({ controller }) => { - const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: NETWORK_TYPES.GOERLI, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); - const { provider: providerBefore } = - controller.getProviderAndBlockTracker(); - - await waitForLookupNetworkToComplete({ - controller, - operation: async () => { - await controller.rollbackToPreviousProvider(); - }, - }); + trackMetaMetricsEvent: trackEventSpy, + }, + async ({ controller }) => { + const newNetworkConfiguration = { + rpcUrl: 'https://new-chain-rpc-url', + chainId: '0x9999' as const, + ticker: 'NEW', + nickname: 'new-chain', + rpcPrefs: { blockExplorerUrl: 'https://block-explorer' }, + }; - const { provider: providerAfter } = - controller.getProviderAndBlockTracker(); - expect(providerBefore).toBe(providerAfter); - }, - ); - }); + await expect(() => + /* @ts-expect-error We are intentionally passing bad input. */ + controller.upsertNetworkConfiguration(newNetworkConfiguration, {}), + ).rejects.toThrow( + 'referrer and source are required arguments for adding or updating a network configuration', + ); + }, + ); + }); + }); - it('emits networkDidChange', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', + describe('removeNetworkConfigurations', () => { + it('removes a network configuration', async () => { + const networkConfigurationId = 'testNetworkConfigurationId'; + await withController( + { + state: { + networkConfigurations: { + [networkConfigurationId]: { + id: networkConfigurationId, + rpcUrl: 'https://test-rpc-url', + ticker: 'old_rpc_ticker', + nickname: 'old_rpc_chainName', + rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, + chainId: '0x1', }, }, - infuraProjectId: 'some-infura-project-id', }, - async ({ controller, messenger }) => { - const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: NETWORK_TYPES.GOERLI, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); + }, + async ({ controller }) => { + controller.removeNetworkConfiguration(networkConfigurationId); - await waitForLookupNetworkToComplete({ - controller, - operation: async () => { - const networkDidChange = await waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.NetworkDidChange, - operation: async () => { - await controller.rollbackToPreviousProvider(); - }, - }); + expect( + controller.store.getState().networkConfigurations, + ).toStrictEqual({}); + }, + ); + }); - expect(networkDidChange).toBeTruthy(); + it('throws if the networkConfigurationId it is passed does not correspond to a network configuration in state', async () => { + const testNetworkConfigurationId = 'testNetworkConfigurationId'; + const invalidNetworkConfigurationId = 'invalidNetworkConfigurationId'; + await withController( + { + state: { + networkConfigurations: { + [testNetworkConfigurationId]: { + rpcUrl: 'https://rpc-url.com', + ticker: 'old_rpc_ticker', + nickname: 'old_rpc_nickname', + rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, + chainId: '0x1', + id: testNetworkConfigurationId, }, - }); + }, }, - ); - }); + }, + async ({ controller }) => { + expect(() => + controller.removeNetworkConfiguration( + invalidNetworkConfigurationId, + ), + ).toThrow( + `networkConfigurationId ${invalidNetworkConfigurationId} does not match a configured networkConfiguration`, + ); + }, + ); + }); + }); +}); - it('emits infuraIsUnblocked', async () => { +/** + * Creates a mocked version of `createNetworkClient` where multiple mock + * invocations can be specified. A default implementation is provided so that if + * none of the actual invocations of the function match the mock invocations + * then an error will be thrown. + */ +function mockCreateNetworkClient() { + return when(createNetworkClientMock).mockImplementation((options) => { + const inspectedOptions = inspect(options, { depth: null, compact: true }); + const lines = [ + `No fake network client was specified for ${inspectedOptions}.`, + 'Make sure to mock this invocation of `createNetworkClient`.', + ]; + if ('infuraProjectId' in options) { + lines.push( + '(You might have forgotten to pass an `infuraProjectId` to `withController`.)', + ); + } + throw new Error(lines.join('\n')); + }); +} + +/** + * Test an operation that performs a `lookupNetwork` call with the given + * provider configuration. All effects of the `lookupNetwork` call should be + * covered by these tests. + * + * @param args - Arguments. + * @param args.expectedProviderConfig - The provider configuration that the + * operation is expected to set. + * @param args.initialState - The initial state of the network controller. + * @param args.operation - The operation to test. + */ +function lookupNetworkTests({ + expectedProviderConfig, + initialState, + operation, +}: { + expectedProviderConfig: ProviderConfiguration; + initialState?: Partial; + operation: (controller: NetworkController) => Promise; +}) { + describe('if the network ID and network details requests resolve successfully', () => { + describe('if the current network is different from the network in state', () => { + it('updates the network in state to match', async () => { await withController( { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, - }, - infuraProjectId: 'some-infura-project-id', + state: initialState, }, - async ({ controller, messenger }) => { - const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: NETWORK_TYPES.GOERLI, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); + async ({ controller }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { method: 'net_version' }, + response: { result: '12345' }, + }, + ], + stubLookupNetworkWhileSetting: true, + }); - await waitForLookupNetworkToComplete({ - controller, - operation: async () => { - const infuraIsUnblocked = await waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.InfuraIsUnblocked, - operation: async () => { - await controller.rollbackToPreviousProvider(); - }, - }); + await operation(controller); - expect(infuraIsUnblocked).toBeTruthy(); - }, - }); + expect(controller.store.getState().networkId).toBe('12345'); }, ); }); + }); - it('checks the status of the previous network again and updates state accordingly', async () => { + describe('if the version of the current network is the same as that in state', () => { + it('does not change network ID in state', async () => { await withController( { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, - }, - infuraProjectId: 'some-infura-project-id', + state: initialState, }, async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'net_version', - }, - error: ethErrors.rpc.methodNotFound(), - }, - ]), - buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, + await setFakeProvider(controller, { + stubs: [ { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + request: { method: 'net_version' }, + response: { result: '12345' }, }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: NETWORK_TYPES.GOERLI, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); - expect(controller.store.getState().networkStatus).toBe( - 'unavailable', - ); + ], + stubLookupNetworkWhileSetting: true, + }); - await controller.rollbackToPreviousProvider(); - expect(controller.store.getState().networkStatus).toBe('available'); + await operation(controller); + + await expect(controller.store.getState().networkId).toBe('12345'); }, ); }); + }); - it('checks whether the previous network supports EIP-1559 again and updates state accordingly', async () => { + describe('if the network details of the current network are different from the network details in state', () => { + it('updates the network in state to match', async () => { await withController( { state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, + ...initialState, networkDetails: { - EIPS: {}, - other: 'details', + EIPS: { + 1559: false, + }, }, }, - infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - ]), - buildFakeProvider([ + await setFakeProvider(controller, { + stubs: [ { request: { method: 'eth_getBlockByNumber', + params: ['latest', false], }, response: { - result: POST_1559_BLOCK, + result: { + baseFeePerGas: '0x1', + }, }, }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: NETWORK_TYPES.GOERLI, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: false, - }, + ], + stubLookupNetworkWhileSetting: true, }); - await waitForLookupNetworkToComplete({ - controller, - operation: async () => { - await controller.rollbackToPreviousProvider(); - }, - }); + await operation(controller); + expect(controller.store.getState().networkDetails).toStrictEqual({ EIPS: { 1559: true, @@ -7406,658 +6412,1054 @@ describe('NetworkController', () => { ); }); }); - }); - - describe('upsertNetworkConfiguration', () => { - it('throws if the given chain ID is not a 0x-prefixed hex number', async () => { - const invalidChainId = '1'; - await withController(async ({ controller }) => { - await expect(() => - controller.upsertNetworkConfiguration( - { - /* @ts-expect-error We are intentionally passing bad input. */ - chainId: invalidChainId, - nickname: 'RPC', - rpcPrefs: { blockExplorerUrl: 'test-block-explorer.com' }, - rpcUrl: 'rpc_url', - ticker: 'RPC', - }, - { - referrer: 'https://test-dapp.com', - source: MetaMetricsNetworkEventSource.Dapp, - }, - ), - ).rejects.toThrow( - new Error( - `Invalid chain ID "${invalidChainId}": invalid hex string.`, - ), - ); - }); - }); - - it('throws if the given chain ID is greater than the maximum allowed ID', async () => { - await withController(async ({ controller }) => { - await expect(() => - controller.upsertNetworkConfiguration( - { - chainId: '0xFFFFFFFFFFFFFFFF', - nickname: 'RPC', - rpcPrefs: { blockExplorerUrl: 'test-block-explorer.com' }, - rpcUrl: 'rpc_url', - ticker: 'RPC', - }, - { - referrer: 'https://test-dapp.com', - source: MetaMetricsNetworkEventSource.Dapp, - }, - ), - ).rejects.toThrow( - new Error( - 'Invalid chain ID "0xFFFFFFFFFFFFFFFF": numerical value greater than max safe value.', - ), - ); - }); - }); - - it('throws if the no (or a falsy) rpcUrl is passed', async () => { - await withController(async ({ controller }) => { - await expect(() => - controller.upsertNetworkConfiguration( - /* @ts-expect-error We are intentionally passing bad input. */ - { - chainId: '0x9999', - nickname: 'RPC', - rpcPrefs: { blockExplorerUrl: 'test-block-explorer.com' }, - ticker: 'RPC', - }, - { - referrer: 'https://test-dapp.com', - source: MetaMetricsNetworkEventSource.Dapp, - }, - ), - ).rejects.toThrow( - new Error( - 'An rpcUrl is required to add or update network configuration', - ), - ); - }); - }); - - it('throws if rpcUrl passed is not a valid Url', async () => { - await withController(async ({ controller }) => { - await expect(() => - controller.upsertNetworkConfiguration( - { - chainId: '0x9999', - nickname: 'RPC', - rpcPrefs: { blockExplorerUrl: 'test-block-explorer.com' }, - ticker: 'RPC', - rpcUrl: 'test', - }, - { - referrer: 'https://test-dapp.com', - source: MetaMetricsNetworkEventSource.Dapp, - }, - ), - ).rejects.toThrow(new Error('rpcUrl must be a valid URL')); - }); - }); - it('throws if the no (or a falsy) ticker is passed', async () => { - await withController(async ({ controller }) => { - await expect(() => - controller.upsertNetworkConfiguration( - /* @ts-expect-error We are intentionally passing bad input. */ - { - chainId: '0x5', - nickname: 'RPC', - rpcPrefs: { blockExplorerUrl: 'test-block-explorer.com' }, - rpcUrl: 'https://mock-rpc-url', - }, - { - referrer: 'https://test-dapp.com', - source: MetaMetricsNetworkEventSource.Dapp, + describe('if the network details of the current network are the same as the network details in state', () => { + it('does not change network details in state', async () => { + await withController( + { + state: { + ...initialState, + networkDetails: { + EIPS: { + 1559: true, + }, + }, }, - ), - ).rejects.toThrow( - new Error( - 'A ticker is required to add or update networkConfiguration', - ), - ); - }); - }); - - it('throws if an options object is not passed as a second argument', async () => { - await withController(async ({ controller }) => { - await expect(() => - /* @ts-expect-error We are intentionally passing bad input. */ - controller.upsertNetworkConfiguration({ - chainId: '0x5', - nickname: 'RPC', - rpcPrefs: { blockExplorerUrl: 'test-block-explorer.com' }, - rpcUrl: 'https://mock-rpc-url', - }), - ).rejects.toThrow( - new Error( - "Cannot read properties of undefined (reading 'setActive')", - ), + }, + async ({ controller }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + response: { + result: { + baseFeePerGas: '0x1', + }, + }, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + await operation(controller); + + expect(controller.store.getState().networkDetails).toStrictEqual({ + EIPS: { + 1559: true, + }, + }); + }, ); }); }); - it('should add the given network if all required properties are present but nither rpcPrefs nor nickname properties are passed', async () => { - uuidV4Mock.mockImplementationOnce(() => 'networkConfigurationId'); + it('emits infuraIsUnblocked', async () => { await withController( { - state: { - networkConfigurations: {}, - }, + state: initialState, }, - async ({ controller }) => { - const rpcUrlNetwork = { - chainId: '0x1' as const, - rpcUrl: 'https://test-rpc-url', - ticker: 'test_ticker', - }; + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubLookupNetworkWhileSetting: true, + }); - await controller.upsertNetworkConfiguration(rpcUrlNetwork, { - referrer: 'https://test-dapp.com', - source: MetaMetricsNetworkEventSource.Dapp, + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + operation: async () => { + await operation(controller); + }, }); - expect( - Object.values(controller.store.getState().networkConfigurations), - ).toStrictEqual( - expect.arrayContaining([ - { - ...rpcUrlNetwork, - nickname: undefined, - rpcPrefs: undefined, - id: 'networkConfigurationId', - }, - ]), - ); + expect(payloads).toBeTruthy(); }, ); }); - it('adds new networkConfiguration to networkController store, but only adds valid properties (rpcUrl, chainId, ticker, nickname, rpcPrefs) and fills any missing properties from this list as undefined', async function () { - uuidV4Mock.mockImplementationOnce(() => 'networkConfigurationId'); + it('does not emit infuraIsBlocked', async () => { await withController( { - state: { - networkConfigurations: {}, - }, + state: initialState, }, - async ({ controller }) => { - const rpcUrlNetwork = { - chainId: '0x1' as const, - rpcUrl: 'https://test-rpc-url', - ticker: 'test_ticker', - invalidKey: 'new-chain', - invalidKey2: {}, - }; + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubLookupNetworkWhileSetting: true, + }); - await controller.upsertNetworkConfiguration(rpcUrlNetwork, { - referrer: 'https://test-dapp.com', - source: MetaMetricsNetworkEventSource.Dapp, + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsBlocked, + count: 0, + operation: async () => { + await operation(controller); + }, }); - expect( - Object.values(controller.store.getState().networkConfigurations), - ).toStrictEqual( - expect.arrayContaining([ + expect(payloads).toBeTruthy(); + }, + ); + }); + }); + + describe('if an RPC error is encountered while retrieving the version of the current network', () => { + it('updates the network in state to "unavailable"', async () => { + await withController( + { + state: initialState, + }, + async ({ controller }) => { + await setFakeProvider(controller, { + stubs: [ { - chainId: '0x1', - rpcUrl: 'https://test-rpc-url', - ticker: 'test_ticker', - nickname: undefined, - rpcPrefs: undefined, - id: 'networkConfigurationId', + request: { method: 'net_version' }, + error: ethErrors.rpc.limitExceeded('some error'), }, - ]), - ); + ], + stubLookupNetworkWhileSetting: true, + }); + + await operation(controller); + + expect(controller.store.getState().networkStatus).toBe('unavailable'); + }, + ); + }); + + if (expectedProviderConfig.type === NETWORK_TYPES.RPC) { + it('emits infuraIsUnblocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { method: 'net_version' }, + error: ethErrors.rpc.limitExceeded('some error'), + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); + }, + ); + }); + } else { + it('does not emit infuraIsUnblocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { method: 'net_version' }, + error: ethErrors.rpc.limitExceeded('some error'), + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + count: 0, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); + }, + ); + }); + } + + it('does not emit infuraIsBlocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { method: 'net_version' }, + error: ethErrors.rpc.limitExceeded('some error'), + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsBlocked, + count: 0, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); }, ); }); + }); + + describe('if a country blocked error is encountered while retrieving the version of the current network', () => { + if (expectedProviderConfig.type === NETWORK_TYPES.RPC) { + it('updates the network in state to "unknown"', async () => { + await withController( + { + state: initialState, + }, + async ({ controller }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { method: 'net_version' }, + error: BLOCKED_INFURA_JSON_RPC_ERROR, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + await operation(controller); + + expect(controller.store.getState().networkStatus).toBe('unknown'); + }, + ); + }); + + it('emits infuraIsUnblocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { method: 'net_version' }, + error: BLOCKED_INFURA_JSON_RPC_ERROR, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); + }, + ); + }); + + it('does not emit infuraIsBlocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { method: 'net_version' }, + error: BLOCKED_INFURA_JSON_RPC_ERROR, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsBlocked, + count: 0, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); + }, + ); + }); + } else { + it('updates the network in state to "blocked"', async () => { + await withController( + { + state: initialState, + }, + async ({ controller }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { method: 'net_version' }, + error: BLOCKED_INFURA_JSON_RPC_ERROR, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + await operation(controller); + + expect(controller.store.getState().networkStatus).toBe('blocked'); + }, + ); + }); + + it('does not emit infuraIsUnblocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { method: 'net_version' }, + error: BLOCKED_INFURA_JSON_RPC_ERROR, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + count: 0, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); + }, + ); + }); + + it('emits infuraIsBlocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { method: 'net_version' }, + error: BLOCKED_INFURA_JSON_RPC_ERROR, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsBlocked, + operation: async () => { + await operation(controller); + }, + }); - it('should add the given network configuration if its rpcURL does not match an existing configuration without changing or overwriting other configurations', async () => { - uuidV4Mock.mockImplementationOnce(() => 'networkConfigurationId2'); + expect(payloads).toBeTruthy(); + }, + ); + }); + } + }); + + describe('if an internal error is encountered while retrieving the version of the current network', () => { + it('updates the network in state to "unknown"', async () => { await withController( { - state: { - networkConfigurations: { - networkConfigurationId: { - rpcUrl: 'https://test-rpc-url', - ticker: 'ticker', - nickname: 'nickname', - rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, - chainId: '0x1', - id: 'networkConfigurationId', - }, - }, - }, + state: initialState, }, async ({ controller }) => { - const rpcUrlNetwork = { - chainId: '0x1' as const, - nickname: 'RPC', - rpcPrefs: undefined, - rpcUrl: 'https://test-rpc-url-2', - ticker: 'RPC', - }; - - await controller.upsertNetworkConfiguration(rpcUrlNetwork, { - referrer: 'https://test-dapp.com', - source: MetaMetricsNetworkEventSource.Dapp, - }); - - expect( - Object.values(controller.store.getState().networkConfigurations), - ).toStrictEqual( - expect.arrayContaining([ + await setFakeProvider(controller, { + stubs: [ { - rpcUrl: 'https://test-rpc-url', - ticker: 'ticker', - nickname: 'nickname', - rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, - chainId: '0x1', - id: 'networkConfigurationId', + request: { method: 'net_version' }, + error: ethErrors.rpc.internal('some error'), }, - { ...rpcUrlNetwork, id: 'networkConfigurationId2' }, - ]), - ); + ], + stubLookupNetworkWhileSetting: true, + }); + + await operation(controller); + + expect(controller.store.getState().networkStatus).toBe('unknown'); }, ); }); - it('should use the given configuration to update an existing network configuration that has a matching rpcUrl', async () => { - await withController( - { - state: { - networkConfigurations: { - networkConfigurationId: { - rpcUrl: 'https://test-rpc-url', - ticker: 'old_rpc_ticker', - nickname: 'old_rpc_chainName', - rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, - chainId: '0x1', - id: 'networkConfigurationId', + if (expectedProviderConfig.type === NETWORK_TYPES.RPC) { + it('emits infuraIsUnblocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { method: 'net_version' }, + error: ethErrors.rpc.internal('some error'), + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + operation: async () => { + await operation(controller); }, - }, + }); + + expect(payloads).toBeTruthy(); }, - }, + ); + }); + } else { + it('does not emit infuraIsUnblocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { method: 'net_version' }, + error: ethErrors.rpc.internal('some error'), + }, + ], + stubLookupNetworkWhileSetting: true, + }); - async ({ controller }) => { - const updatedConfiguration = { - rpcUrl: 'https://test-rpc-url', - ticker: 'new_rpc_ticker', - nickname: 'new_rpc_chainName', - rpcPrefs: { blockExplorerUrl: 'alternativetestchainscan.io' }, - chainId: '0x1' as const, - }; - await controller.upsertNetworkConfiguration(updatedConfiguration, { - referrer: 'https://test-dapp.com', - source: MetaMetricsNetworkEventSource.Dapp, + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + count: 0, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); + }, + ); + }); + } + + it('does not emit infuraIsBlocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { method: 'net_version' }, + error: ethErrors.rpc.internal('some error'), + }, + ], + stubLookupNetworkWhileSetting: true, }); - expect( - Object.values(controller.store.getState().networkConfigurations), - ).toStrictEqual([ - { - rpcUrl: 'https://test-rpc-url', - nickname: 'new_rpc_chainName', - ticker: 'new_rpc_ticker', - rpcPrefs: { blockExplorerUrl: 'alternativetestchainscan.io' }, - chainId: '0x1', - id: 'networkConfigurationId', + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsBlocked, + count: 0, + operation: async () => { + await operation(controller); }, - ]); + }); + + expect(payloads).toBeTruthy(); }, ); }); + }); - it('should use the given configuration to update an existing network configuration that has a matching rpcUrl without changing or overwriting other networkConfigurations', async () => { + describe('if an invalid network ID is returned', () => { + it('updates the network in state to "unknown"', async () => { await withController( { - state: { - networkConfigurations: { - networkConfigurationId: { - rpcUrl: 'https://test-rpc-url', - ticker: 'ticker', - nickname: 'nickname', - rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, - chainId: '0x1', - id: 'networkConfigurationId', - }, - networkConfigurationId2: { - rpcUrl: 'https://test-rpc-url-2', - ticker: 'ticker-2', - nickname: 'nickname-2', - rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, - chainId: '0x9999', - id: 'networkConfigurationId2', - }, - }, - }, + state: initialState, }, async ({ controller }) => { - await controller.upsertNetworkConfiguration( - { - rpcUrl: 'https://test-rpc-url', - ticker: 'new-ticker', - nickname: 'new-nickname', - rpcPrefs: { blockExplorerUrl: 'alternativetestchainscan.io' }, - chainId: '0x1', - }, - { - referrer: 'https://test-dapp.com', - source: MetaMetricsNetworkEventSource.Dapp, - }, - ); + await setFakeProvider(controller, { + stubs: [ + { + request: { method: 'net_version' }, + response: { result: 'invalid' }, + }, + ], + stubLookupNetworkWhileSetting: true, + }); - expect( - Object.values(controller.store.getState().networkConfigurations), - ).toStrictEqual([ - { - rpcUrl: 'https://test-rpc-url', - ticker: 'new-ticker', - nickname: 'new-nickname', - rpcPrefs: { blockExplorerUrl: 'alternativetestchainscan.io' }, - chainId: '0x1', - id: 'networkConfigurationId', - }, - { - rpcUrl: 'https://test-rpc-url-2', - ticker: 'ticker-2', - nickname: 'nickname-2', - rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, - chainId: '0x9999', - id: 'networkConfigurationId2', - }, - ]); + await operation(controller); + + expect(controller.store.getState().networkStatus).toBe('unknown'); }, ); }); - it('should add the given network and not set it to active if the setActive option is not passed (or a falsy value is passed)', async () => { - uuidV4Mock.mockImplementationOnce(() => 'networkConfigurationId'); - const originalProviderConfig = { - type: NETWORK_TYPES.RPC, - rpcUrl: 'https://mock-rpc-url', - chainId: '0xtest' as const, - ticker: 'TEST', - }; - await withController( - { - state: { - providerConfig: originalProviderConfig, - networkConfigurations: { - testNetworkConfigurationId: { - rpcUrl: 'https://mock-rpc-url', - chainId: '0xtest', - ticker: 'TEST', - id: 'testNetworkConfigurationId', + if (expectedProviderConfig.type === NETWORK_TYPES.RPC) { + it('emits infuraIsUnblocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { method: 'net_version' }, + response: { result: 'invalid' }, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + operation: async () => { + await operation(controller); }, - }, + }); + + expect(payloads).toBeTruthy(); + }, + ); + }); + } else { + it('does not emit infuraIsUnblocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { method: 'net_version' }, + response: { result: 'invalid' }, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + count: 0, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); }, + ); + }); + } + + it('does not emit infuraIsBlocked', async () => { + await withController( + { + state: initialState, }, - async ({ controller }) => { - const rpcUrlNetwork = { - chainId: '0x1' as const, - rpcUrl: 'https://test-rpc-url', - ticker: 'test_ticker', - }; + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { method: 'net_version' }, + response: { result: 'invalid' }, + }, + ], + stubLookupNetworkWhileSetting: true, + }); - await controller.upsertNetworkConfiguration(rpcUrlNetwork, { - referrer: 'https://test-dapp.com', - source: MetaMetricsNetworkEventSource.Dapp, + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsBlocked, + count: 0, + operation: async () => { + await operation(controller); + }, }); - expect(controller.store.getState().providerConfig).toStrictEqual( - originalProviderConfig, - ); + expect(payloads).toBeTruthy(); }, ); }); + }); - it('should add the given network and set it to active if the setActive option is passed as true', async () => { - uuidV4Mock.mockImplementationOnce(() => 'networkConfigurationId'); + describe('if an RPC error is encountered while retrieving the network details of the current network', () => { + it('updates the network in state to "unavailable"', async () => { await withController( { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0xtest', - ticker: 'TEST', - }, - networkConfigurations: { - testNetworkConfigurationId: { - rpcUrl: 'https://mock-rpc-url', - chainId: '0xtest', - ticker: 'TEST', - id: 'testNetworkConfigurationId', - }, - }, - }, + state: initialState, }, async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - const rpcUrlNetwork = { - chainId: '0x1' as const, - rpcUrl: 'https://test-rpc-url', - ticker: 'test_ticker', - }; - - await controller.upsertNetworkConfiguration(rpcUrlNetwork, { - setActive: true, - referrer: 'https://test-dapp.com', - source: MetaMetricsNetworkEventSource.Dapp, + await setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + error: ethErrors.rpc.limitExceeded('some error'), + }, + ], + stubLookupNetworkWhileSetting: true, }); - expect(controller.store.getState().providerConfig).toStrictEqual({ - ...rpcUrlNetwork, - nickname: undefined, - rpcPrefs: undefined, - type: 'rpc', - id: 'networkConfigurationId', - }); + await operation(controller); + + expect(controller.store.getState().networkStatus).toBe('unavailable'); }, ); }); - it('adds new networkConfiguration to networkController store and calls to the metametrics event tracking with the correct values', async () => { - uuidV4Mock.mockImplementationOnce(() => 'networkConfigurationId'); - const trackEventSpy = jest.fn(); + if (expectedProviderConfig.type === NETWORK_TYPES.RPC) { + it('emits infuraIsUnblocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + error: ethErrors.rpc.limitExceeded('some error'), + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); + }, + ); + }); + } else { + it('does not emit infuraIsUnblocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + error: ethErrors.rpc.limitExceeded('some error'), + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + count: 0, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); + }, + ); + }); + } + + it('does not emit infuraIsBlocked', async () => { await withController( { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0xtest', - ticker: 'TEST', - }, - networkConfigurations: { - testNetworkConfigurationId: { - rpcUrl: 'https://mock-rpc-url', - chainId: '0xtest', - ticker: 'TEST', - id: 'testNetworkConfigurationId', + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + error: ethErrors.rpc.limitExceeded('some error'), }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsBlocked, + count: 0, + operation: async () => { + await operation(controller); }, + }); + + expect(payloads).toBeTruthy(); + }, + ); + }); + }); + + describe('if a country blocked error is encountered while retrieving the network details of the current network', () => { + if (expectedProviderConfig.type === NETWORK_TYPES.RPC) { + it('updates the network in state to "unknown"', async () => { + await withController( + { + state: initialState, + }, + async ({ controller }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + error: BLOCKED_INFURA_JSON_RPC_ERROR, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + await operation(controller); + + expect(controller.store.getState().networkStatus).toBe('unknown'); + }, + ); + }); + + it('emits infuraIsUnblocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + error: BLOCKED_INFURA_JSON_RPC_ERROR, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); + }, + ); + }); + + it('does not emit infuraIsBlocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + error: BLOCKED_INFURA_JSON_RPC_ERROR, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsBlocked, + count: 0, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); + }, + ); + }); + } else { + it('updates the network in state to "blocked"', async () => { + await withController( + { + state: initialState, + }, + async ({ controller }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + error: BLOCKED_INFURA_JSON_RPC_ERROR, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + await operation(controller); + + expect(controller.store.getState().networkStatus).toBe('blocked'); + }, + ); + }); + + it('does not emit infuraIsUnblocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + error: BLOCKED_INFURA_JSON_RPC_ERROR, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + count: 0, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); }, - trackMetaMetricsEvent: trackEventSpy, - }, - async ({ controller }) => { - const newNetworkConfiguration = { - rpcUrl: 'https://new-chain-rpc-url', - chainId: '0x9999' as const, - ticker: 'NEW', - nickname: 'new-chain', - rpcPrefs: { blockExplorerUrl: 'https://block-explorer' }, - }; + ); + }); - await controller.upsertNetworkConfiguration(newNetworkConfiguration, { - referrer: 'https://test-dapp.com', - source: MetaMetricsNetworkEventSource.Dapp, - }); + it('emits infuraIsBlocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + error: BLOCKED_INFURA_JSON_RPC_ERROR, + }, + ], + stubLookupNetworkWhileSetting: true, + }); - expect( - Object.values(controller.store.getState().networkConfigurations), - ).toStrictEqual([ - { - rpcUrl: 'https://mock-rpc-url', - chainId: '0xtest', - ticker: 'TEST', - id: 'testNetworkConfigurationId', - }, - { - ...newNetworkConfiguration, - id: 'networkConfigurationId', - }, - ]); - expect(trackEventSpy).toHaveBeenCalledWith({ - event: 'Custom Network Added', - category: 'Network', - referrer: { - url: 'https://test-dapp.com', - }, - properties: { - chain_id: '0x9999', - symbol: 'NEW', - source: 'dapp', - }, - }); - }, - ); - }); + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsBlocked, + operation: async () => { + await operation(controller); + }, + }); - it('throws if referrer and source arguments are not passed', async () => { - uuidV4Mock.mockImplementationOnce(() => 'networkConfigurationId'); - const trackEventSpy = jest.fn(); + expect(payloads).toBeTruthy(); + }, + ); + }); + } + }); + + describe('if an internal error is encountered while retrieving the network details of the current network', () => { + it('updates the network in state to "unknown"', async () => { await withController( { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0xtest', - ticker: 'TEST', - }, - networkConfigurations: { - testNetworkConfigurationId: { - rpcUrl: 'https://mock-rpc-url', - chainId: '0xtest', - ticker: 'TEST', - id: 'testNetworkConfigurationId', - }, - }, - }, - trackMetaMetricsEvent: trackEventSpy, + state: initialState, }, async ({ controller }) => { - const newNetworkConfiguration = { - rpcUrl: 'https://new-chain-rpc-url', - chainId: '0x9999' as const, - ticker: 'NEW', - nickname: 'new-chain', - rpcPrefs: { blockExplorerUrl: 'https://block-explorer' }, - }; + await setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + error: ethErrors.rpc.internal('some error'), + }, + ], + }); - await expect(() => - /* @ts-expect-error We are intentionally passing bad input. */ - controller.upsertNetworkConfiguration(newNetworkConfiguration, {}), - ).rejects.toThrow( - 'referrer and source are required arguments for adding or updating a network configuration', - ); + await operation(controller); + + expect(controller.store.getState().networkStatus).toBe('unknown'); }, ); }); - }); - describe('removeNetworkConfigurations', () => { - it('removes a network configuration', async () => { - const networkConfigurationId = 'testNetworkConfigurationId'; - await withController( - { - state: { - networkConfigurations: { - [networkConfigurationId]: { - id: networkConfigurationId, - rpcUrl: 'https://test-rpc-url', - ticker: 'old_rpc_ticker', - nickname: 'old_rpc_chainName', - rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, - chainId: '0x1', + if (expectedProviderConfig.type === NETWORK_TYPES.RPC) { + it('emits infuraIsUnblocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + error: ethErrors.rpc.internal('some error'), + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + operation: async () => { + await operation(controller); }, - }, + }); + + expect(payloads).toBeTruthy(); }, - }, - async ({ controller }) => { - controller.removeNetworkConfiguration(networkConfigurationId); + ); + }); + } else { + it('does not emit infuraIsUnblocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + error: ethErrors.rpc.internal('some error'), + }, + ], + stubLookupNetworkWhileSetting: true, + }); - expect( - controller.store.getState().networkConfigurations, - ).toStrictEqual({}); - }, - ); - }); + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + count: 0, + operation: async () => { + await operation(controller); + }, + }); - it('throws if the networkConfigurationId it is passed does not correspond to a network configuration in state', async () => { - const testNetworkConfigurationId = 'testNetworkConfigurationId'; - const invalidNetworkConfigurationId = 'invalidNetworkConfigurationId'; + expect(payloads).toBeTruthy(); + }, + ); + }); + } + + it('does not emit infuraIsBlocked', async () => { await withController( { - state: { - networkConfigurations: { - [testNetworkConfigurationId]: { - rpcUrl: 'https://rpc-url.com', - ticker: 'old_rpc_ticker', - nickname: 'old_rpc_nickname', - rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, - chainId: '0x1', - id: testNetworkConfigurationId, + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + error: ethErrors.rpc.internal('some error'), }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsBlocked, + count: 0, + operation: async () => { + await operation(controller); }, - }, - }, - async ({ controller }) => { - expect(() => - controller.removeNetworkConfiguration( - invalidNetworkConfigurationId, - ), - ).toThrow( - `networkConfigurationId ${invalidNetworkConfigurationId} does not match a configured networkConfiguration`, - ); + }); + + expect(payloads).toBeTruthy(); }, ); }); }); -}); - -/** - * Creates a mocked version of `createNetworkClient` where multiple mock - * invocations can be specified. A default implementation is provided so that if - * none of the actual invocations of the function match the mock invocations - * then an error will be thrown. - */ -function mockCreateNetworkClient() { - return when(createNetworkClientMock).mockImplementation((options) => { - const inspectedOptions = inspect(options, { depth: null, compact: true }); - const lines = [ - `No fake network client was specified for ${inspectedOptions}.`, - 'Make sure to mock this invocation of `createNetworkClient`.', - ]; - if ('infuraProjectId' in options) { - lines.push( - '(You might have forgotten to pass an `infuraProjectId` to `withController`.)', - ); - } - throw new Error(lines.join('\n')); - }); } /** @@ -8146,6 +7548,41 @@ async function withController( } } +/** + * Builds a complete ProviderConfig object, filling in values that are not + * provided with defaults. + * + * @param config - An incomplete ProviderConfig object. + * @returns The complete ProviderConfig object. + */ +function buildProviderConfig( + config: Partial = {}, +): ProviderConfiguration { + if (config.type && config.type !== NETWORK_TYPES.RPC) { + const networkConfig = INFURA_NETWORKS.find( + ({ networkType }) => networkType === config.type, + ); + if (!networkConfig) { + throw new Error(`Invalid type: ${config.type}`); + } + return { + ...networkConfig, + // This is redundant with the spread operation below, but this was + // required for TypeScript to understand that this property was set to an + // Infura type. + type: config.type, + ...config, + }; + } + return { + type: NETWORK_TYPES.RPC, + chainId: '0x42', + nickname: undefined, + rpcUrl: 'http://doesntmatter.com', + ...config, + }; +} + /** * Builds an object that `createInfuraProvider` or `createJsonRpcClient` returns. * @@ -8191,6 +7628,53 @@ function buildFakeProvider(stubs: FakeProviderStub[] = []) { return new FakeProvider({ stubs: completeStubs }); } +/** + * Asks the controller to set the provider in the simplest way, stubbing the + * provider appropriately so as not to cause any errors to be thrown. This is + * useful in tests where it doesn't matter how the provider gets set, just that + * it does. Canned responses may be optionally provided for certain RPC methods + * on the provider. + * + * @param controller - The network controller. + * @param options - Additional options. + * @param options.stubs - The set of RPC methods you want to stub on the + * provider along with their responses. + * @param options.stubLookupNetworkWhileSetting - Whether to stub the call to + * `lookupNetwork` that happens when the provider is set. This option is useful + * in tests that need a provider to get set but also call `lookupNetwork` on + * their own. In this case, since the `providerConfig` setter already calls + * `lookupNetwork` once, and since `lookupNetwork` is called out of band, the + * test may run with unexpected results. By stubbing `lookupNetwork` before + * setting the provider, the test is free to explicitly call it. + * @returns The set provider. + */ +async function setFakeProvider( + controller: NetworkController, + { + stubs = [], + stubLookupNetworkWhileSetting = false, + }: { + stubs?: FakeProviderStub[]; + stubLookupNetworkWhileSetting?: boolean; + } = {}, +): Promise { + const fakeProvider = buildFakeProvider(stubs); + const fakeNetworkClient = buildFakeClient(fakeProvider); + createNetworkClientMock.mockReturnValue(fakeNetworkClient); + const lookupNetworkMock = jest.spyOn(controller, 'lookupNetwork'); + + if (stubLookupNetworkWhileSetting) { + lookupNetworkMock.mockResolvedValue(undefined); + } + + await controller.initializeProvider(); + assert(controller.getProviderAndBlockTracker().provider); + + if (stubLookupNetworkWhileSetting) { + lookupNetworkMock.mockRestore(); + } +} + /** * For each kind of way that the provider can be set, `lookupNetwork` is always * called. This can cause difficulty when testing the behavior of From 1c346674df00e40fe83d36538eba2198667357a9 Mon Sep 17 00:00:00 2001 From: Harsh Shukla <125105825+PrgrmrHarshShukla@users.noreply.github.com> Date: Mon, 15 May 2023 22:22:02 +0530 Subject: [PATCH 5/8] Part of #18714 Replace deprecated design system typography consts with enums in: 'srp-input' (#19040) * Part of #18714 Replace deprecated design system typography consts with enums in: 'srp-input' * Update ui/components/app/srp-input/srp-input.js Co-authored-by: George Marshall * Update srp-input.js --------- Co-authored-by: Brad Decker Co-authored-by: George Marshall --- ui/components/app/srp-input/srp-input.js | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/ui/components/app/srp-input/srp-input.js b/ui/components/app/srp-input/srp-input.js index 27b7abc1f603..be990e3cf74c 100644 --- a/ui/components/app/srp-input/srp-input.js +++ b/ui/components/app/srp-input/srp-input.js @@ -6,13 +6,12 @@ import TextField from '../../ui/text-field'; import { clearClipboard } from '../../../helpers/utils/util'; import ActionableMessage from '../../ui/actionable-message'; import Dropdown from '../../ui/dropdown'; -import Typography from '../../ui/typography'; import ShowHideToggle from '../../ui/show-hide-toggle'; import { - FONT_WEIGHT, - TEXT_ALIGN, - TypographyVariant, + TextAlign, + TextVariant, } from '../../../helpers/constants/design-system'; +import { Text } from '../../component-library'; import { parseSecretRecoveryPhrase } from './parse-secret-recovery-phrase'; const defaultNumberOfWords = 12; @@ -129,13 +128,9 @@ export default function SrpInput({ onChange, srpText }) { return (
Date: Mon, 15 May 2023 19:36:24 +0200 Subject: [PATCH 6/8] [FLASK] `snaps-monorepo@0.33.1-flask.1` (#18913) * snaps-monorepo@0.33.0-flask.1 * Add browser-passworder * Patch babel/core * Fix PermissionController messenger allowlist * Update test-snaps * Use latest patch * Update LavaMoat policies * Re-enable RPC E2E * Make snaps iframe URL be a env variable and bump it * Add new env variable to test env * Add iframe URL to desktop build --- .../@babel-core-npm-7.21.5-c72c337956.patch | 12 + app/scripts/metamask-controller.js | 6 +- builds.yml | 3 + lavamoat/browserify/beta/policy.json | 30 +- lavamoat/browserify/desktop/policy.json | 160 +++---- lavamoat/browserify/flask/policy.json | 160 +++---- lavamoat/browserify/main/policy.json | 30 +- lavamoat/build-system/policy.json | 18 +- package.json | 24 +- test/e2e/snaps/test-snap-rpc.spec.js | 5 +- test/env.js | 2 + yarn.lock | 410 +++++++++++------- 12 files changed, 507 insertions(+), 353 deletions(-) create mode 100644 .yarn/patches/@babel-core-npm-7.21.5-c72c337956.patch diff --git a/.yarn/patches/@babel-core-npm-7.21.5-c72c337956.patch b/.yarn/patches/@babel-core-npm-7.21.5-c72c337956.patch new file mode 100644 index 000000000000..c6a1743a7167 --- /dev/null +++ b/.yarn/patches/@babel-core-npm-7.21.5-c72c337956.patch @@ -0,0 +1,12 @@ +diff --git a/lib/index.js b/lib/index.js +index c991f62dc64553502e9911a7f21e77e008d7f438..e503c7494d21b13df85b10e1657b2af8ca4d964f 100644 +--- a/lib/index.js ++++ b/lib/index.js +@@ -222,7 +222,6 @@ var _transform = require("./transform"); + var _transformFile = require("./transform-file"); + var _transformAst = require("./transform-ast"); + var _parse = require("./parse"); +-var thisFile = require("./index"); + const version = "7.21.5"; + exports.version = version; + const DEFAULT_EXTENSIONS = Object.freeze([".js", ".jsx", ".es6", ".es", ".mjs", ".cjs"]); diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 8378cb02a92a..d1019ccb852f 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -49,6 +49,7 @@ import { SubjectType, } from '@metamask/subject-metadata-controller'; ///: BEGIN:ONLY_INCLUDE_IN(snaps) +import { encrypt, decrypt } from '@metamask/browser-passworder'; import { RateLimitController } from '@metamask/rate-limit-controller'; import { NotificationController } from '@metamask/notification-controller'; ///: END:ONLY_INCLUDE_IN @@ -754,6 +755,7 @@ export default class MetamaskController extends EventEmitter { `${this.approvalController.name}:rejectRequest`, `SnapController:getPermitted`, `SnapController:install`, + `SubjectMetadataController:getSubjectMetadata`, ], }), state: initState.PermissionController, @@ -813,7 +815,7 @@ export default class MetamaskController extends EventEmitter { ///: BEGIN:ONLY_INCLUDE_IN(snaps) const snapExecutionServiceArgs = { - iframeUrl: new URL('https://execution.metamask.io/0.15.1/index.html'), + iframeUrl: new URL(process.env.IFRAME_EXECUTION_ENVIRONMENT_URL), messenger: this.controllerMessenger.getRestricted({ name: 'ExecutionService', }), @@ -1572,6 +1574,8 @@ export default class MetamaskController extends EventEmitter { return { ...buildSnapEndowmentSpecifications(), ...buildSnapRestrictedMethodSpecifications({ + encrypt, + decrypt, clearSnapState: this.controllerMessenger.call.bind( this.controllerMessenger, 'SnapController:clearSnapState', diff --git a/builds.yml b/builds.yml index 414641af0343..83b3315237f5 100644 --- a/builds.yml +++ b/builds.yml @@ -50,6 +50,7 @@ buildTypes: - SEGMENT_FLASK_WRITE_KEY - ALLOW_LOCAL_SNAPS: true - REQUIRE_SNAPS_ALLOWLIST: false + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/0.16.0-flask.1/index.html - SUPPORT_LINK: https://metamask-flask.zendesk.com/hc - SUPPORT_REQUEST_LINK: https://metamask-flask.zendesk.com/hc/en-us/requests/new - INFURA_ENV_KEY_REF: INFURA_FLASK_PROJECT_ID @@ -67,6 +68,7 @@ buildTypes: - SEGMENT_FLASK_WRITE_KEY - ALLOW_LOCAL_SNAPS: true - REQUIRE_SNAPS_ALLOWLIST: false + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/0.16.0-flask.1/index.html - SUPPORT_LINK: https://metamask-flask.zendesk.com/hc - SUPPORT_REQUEST_LINK: https://metamask-flask.zendesk.com/hc/en-us/requests/new - INFURA_ENV_KEY_REF: INFURA_FLASK_PROJECT_ID @@ -101,6 +103,7 @@ features: - ALLOW_LOCAL_SNAPS # Whether to verify that a snap can be installed using an allow list - REQUIRE_SNAPS_ALLOWLIST + - IFRAME_EXECUTION_ENVIRONMENT_URL assets: - ./{app,shared,ui}/**/snaps/** desktop: diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index bff7adb06fbe..a049ea57c697 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -868,6 +868,20 @@ "immer": true } }, + "@metamask/browser-passworder": { + "globals": { + "btoa": true, + "crypto.getRandomValues": true, + "crypto.subtle.decrypt": true, + "crypto.subtle.deriveKey": true, + "crypto.subtle.encrypt": true, + "crypto.subtle.exportKey": true, + "crypto.subtle.importKey": true + }, + "packages": { + "browserify>buffer": true + } + }, "@metamask/controller-utils": { "globals": { "URL": true, @@ -998,11 +1012,11 @@ }, "@metamask/eth-keyring-controller": { "packages": { + "@metamask/browser-passworder": true, "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring": true, "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring": true, "@metamask/eth-keyring-controller>obs-store": true, "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, - "@metamask/rpc-methods>@metamask/browser-passworder": true, "browserify>events": true } }, @@ -1610,20 +1624,6 @@ "crypto.getRandomValues": true } }, - "@metamask/rpc-methods>@metamask/browser-passworder": { - "globals": { - "btoa": true, - "crypto.getRandomValues": true, - "crypto.subtle.decrypt": true, - "crypto.subtle.deriveKey": true, - "crypto.subtle.encrypt": true, - "crypto.subtle.exportKey": true, - "crypto.subtle.importKey": true - }, - "packages": { - "browserify>buffer": true - } - }, "@metamask/rpc-methods>nanoid": { "globals": { "crypto.getRandomValues": true diff --git a/lavamoat/browserify/desktop/policy.json b/lavamoat/browserify/desktop/policy.json index c5eee11b6676..54c984578da3 100644 --- a/lavamoat/browserify/desktop/policy.json +++ b/lavamoat/browserify/desktop/policy.json @@ -868,6 +868,20 @@ "immer": true } }, + "@metamask/browser-passworder": { + "globals": { + "btoa": true, + "crypto.getRandomValues": true, + "crypto.subtle.decrypt": true, + "crypto.subtle.deriveKey": true, + "crypto.subtle.encrypt": true, + "crypto.subtle.exportKey": true, + "crypto.subtle.importKey": true + }, + "packages": { + "browserify>buffer": true + } + }, "@metamask/controller-utils": { "globals": { "URL": true, @@ -1069,11 +1083,11 @@ }, "@metamask/eth-keyring-controller": { "packages": { + "@metamask/browser-passworder": true, "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring": true, "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring": true, "@metamask/eth-keyring-controller>obs-store": true, "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, - "@metamask/rpc-methods>@metamask/browser-passworder": true, "browserify>events": true } }, @@ -1683,25 +1697,12 @@ "browser": true, "chrome": true, "location.origin": true, - "onmessage": "write", "postMessage": true, "removeEventListener": true }, "packages": { - "@metamask/post-message-stream>@metamask/utils": true, - "@metamask/post-message-stream>readable-stream": true - } - }, - "@metamask/post-message-stream>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true + "@metamask/post-message-stream>readable-stream": true, + "@metamask/utils": true } }, "@metamask/post-message-stream>readable-stream": { @@ -1753,51 +1754,50 @@ "eth-rpc-errors": true } }, - "@metamask/rpc-methods": { + "@metamask/rpc-methods-flask": { "packages": { "@metamask/key-tree": true, "@metamask/key-tree>@noble/hashes": true, "@metamask/permission-controller": true, - "@metamask/rpc-methods>@metamask/browser-passworder": true, - "@metamask/rpc-methods>nanoid": true, - "@metamask/snaps-ui": true, - "@metamask/snaps-utils": true, + "@metamask/rpc-methods-flask>@metamask/snaps-ui": true, + "@metamask/rpc-methods-flask>@metamask/snaps-utils": true, + "@metamask/rpc-methods-flask>nanoid": true, "@metamask/utils": true, "eth-rpc-errors": true, "superstruct": true } }, - "@metamask/rpc-methods-flask": { + "@metamask/rpc-methods-flask>@metamask/snaps-ui": { "packages": { - "@metamask/key-tree": true, - "@metamask/key-tree>@noble/hashes": true, - "@metamask/permission-controller": true, - "@metamask/rpc-methods-flask>nanoid": true, - "@metamask/rpc-methods>@metamask/browser-passworder": true, - "@metamask/snaps-ui": true, - "@metamask/snaps-utils": true, "@metamask/utils": true, - "eth-rpc-errors": true, "superstruct": true } }, - "@metamask/rpc-methods-flask>nanoid": { + "@metamask/rpc-methods-flask>@metamask/snaps-utils": { "globals": { - "crypto.getRandomValues": true + "TextDecoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "@metamask/key-tree>@scure/base": true, + "@metamask/snaps-utils>cron-parser": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "@metamask/snaps-utils>rfdc": true, + "@metamask/snaps-utils>validate-npm-package-name": true, + "@metamask/utils": true, + "semver": true, + "superstruct": true } }, - "@metamask/rpc-methods>@metamask/browser-passworder": { + "@metamask/rpc-methods-flask>nanoid": { "globals": { - "btoa": true, - "crypto.getRandomValues": true, - "crypto.subtle.decrypt": true, - "crypto.subtle.deriveKey": true, - "crypto.subtle.encrypt": true, - "crypto.subtle.exportKey": true, - "crypto.subtle.importKey": true - }, - "packages": { - "browserify>buffer": true + "crypto.getRandomValues": true } }, "@metamask/rpc-methods>nanoid": { @@ -1922,16 +1922,15 @@ "@metamask/permission-controller": true, "@metamask/post-message-stream": true, "@metamask/providers>@metamask/object-multiplex": true, - "@metamask/rpc-methods": true, + "@metamask/snaps-controllers-flask>@metamask/rpc-methods": true, + "@metamask/snaps-controllers-flask>@metamask/snaps-utils": true, "@metamask/snaps-controllers-flask>concat-stream": true, "@metamask/snaps-controllers-flask>nanoid": true, "@metamask/snaps-controllers>@xstate/fsm": true, "@metamask/snaps-controllers>gunzip-maybe": true, "@metamask/snaps-controllers>readable-web-to-node-stream": true, "@metamask/snaps-controllers>tar-stream": true, - "@metamask/snaps-utils": true, "@metamask/snaps-utils>@metamask/snaps-registry": true, - "@metamask/subject-metadata-controller": true, "@metamask/utils": true, "eth-rpc-errors": true, "json-rpc-engine": true, @@ -1939,6 +1938,47 @@ "pump": true } }, + "@metamask/snaps-controllers-flask>@metamask/rpc-methods": { + "packages": { + "@metamask/key-tree": true, + "@metamask/key-tree>@noble/hashes": true, + "@metamask/permission-controller": true, + "@metamask/snaps-controllers-flask>@metamask/snaps-utils": true, + "@metamask/snaps-controllers-flask>@metamask/snaps-utils>@metamask/snaps-ui": true, + "@metamask/snaps-controllers-flask>nanoid": true, + "@metamask/utils": true, + "eth-rpc-errors": true, + "superstruct": true + } + }, + "@metamask/snaps-controllers-flask>@metamask/snaps-utils": { + "globals": { + "TextDecoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "@metamask/key-tree>@scure/base": true, + "@metamask/snaps-utils>cron-parser": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "@metamask/snaps-utils>rfdc": true, + "@metamask/snaps-utils>validate-npm-package-name": true, + "@metamask/utils": true, + "semver": true, + "superstruct": true + } + }, + "@metamask/snaps-controllers-flask>@metamask/snaps-utils>@metamask/snaps-ui": { + "packages": { + "@metamask/utils": true, + "superstruct": true + } + }, "@metamask/snaps-controllers-flask>concat-stream": { "packages": { "@metamask/snaps-controllers-flask>concat-stream>readable-stream": true, @@ -2090,40 +2130,12 @@ "pumpify>inherits": true } }, - "@metamask/snaps-ui": { - "packages": { - "@metamask/utils": true, - "superstruct": true - } - }, "@metamask/snaps-ui-flask": { "packages": { "@metamask/utils": true, "superstruct": true } }, - "@metamask/snaps-utils": { - "globals": { - "TextDecoder": true, - "URL": true, - "console.error": true, - "console.log": true, - "console.warn": true, - "document.body.appendChild": true, - "document.createElement": true - }, - "packages": { - "@metamask/key-tree>@noble/hashes": true, - "@metamask/key-tree>@scure/base": true, - "@metamask/snaps-utils>cron-parser": true, - "@metamask/snaps-utils>fast-json-stable-stringify": true, - "@metamask/snaps-utils>rfdc": true, - "@metamask/snaps-utils>validate-npm-package-name": true, - "@metamask/utils": true, - "semver": true, - "superstruct": true - } - }, "@metamask/snaps-utils-flask": { "globals": { "TextDecoder": true, diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index c5eee11b6676..54c984578da3 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -868,6 +868,20 @@ "immer": true } }, + "@metamask/browser-passworder": { + "globals": { + "btoa": true, + "crypto.getRandomValues": true, + "crypto.subtle.decrypt": true, + "crypto.subtle.deriveKey": true, + "crypto.subtle.encrypt": true, + "crypto.subtle.exportKey": true, + "crypto.subtle.importKey": true + }, + "packages": { + "browserify>buffer": true + } + }, "@metamask/controller-utils": { "globals": { "URL": true, @@ -1069,11 +1083,11 @@ }, "@metamask/eth-keyring-controller": { "packages": { + "@metamask/browser-passworder": true, "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring": true, "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring": true, "@metamask/eth-keyring-controller>obs-store": true, "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, - "@metamask/rpc-methods>@metamask/browser-passworder": true, "browserify>events": true } }, @@ -1683,25 +1697,12 @@ "browser": true, "chrome": true, "location.origin": true, - "onmessage": "write", "postMessage": true, "removeEventListener": true }, "packages": { - "@metamask/post-message-stream>@metamask/utils": true, - "@metamask/post-message-stream>readable-stream": true - } - }, - "@metamask/post-message-stream>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true + "@metamask/post-message-stream>readable-stream": true, + "@metamask/utils": true } }, "@metamask/post-message-stream>readable-stream": { @@ -1753,51 +1754,50 @@ "eth-rpc-errors": true } }, - "@metamask/rpc-methods": { + "@metamask/rpc-methods-flask": { "packages": { "@metamask/key-tree": true, "@metamask/key-tree>@noble/hashes": true, "@metamask/permission-controller": true, - "@metamask/rpc-methods>@metamask/browser-passworder": true, - "@metamask/rpc-methods>nanoid": true, - "@metamask/snaps-ui": true, - "@metamask/snaps-utils": true, + "@metamask/rpc-methods-flask>@metamask/snaps-ui": true, + "@metamask/rpc-methods-flask>@metamask/snaps-utils": true, + "@metamask/rpc-methods-flask>nanoid": true, "@metamask/utils": true, "eth-rpc-errors": true, "superstruct": true } }, - "@metamask/rpc-methods-flask": { + "@metamask/rpc-methods-flask>@metamask/snaps-ui": { "packages": { - "@metamask/key-tree": true, - "@metamask/key-tree>@noble/hashes": true, - "@metamask/permission-controller": true, - "@metamask/rpc-methods-flask>nanoid": true, - "@metamask/rpc-methods>@metamask/browser-passworder": true, - "@metamask/snaps-ui": true, - "@metamask/snaps-utils": true, "@metamask/utils": true, - "eth-rpc-errors": true, "superstruct": true } }, - "@metamask/rpc-methods-flask>nanoid": { + "@metamask/rpc-methods-flask>@metamask/snaps-utils": { "globals": { - "crypto.getRandomValues": true + "TextDecoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "@metamask/key-tree>@scure/base": true, + "@metamask/snaps-utils>cron-parser": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "@metamask/snaps-utils>rfdc": true, + "@metamask/snaps-utils>validate-npm-package-name": true, + "@metamask/utils": true, + "semver": true, + "superstruct": true } }, - "@metamask/rpc-methods>@metamask/browser-passworder": { + "@metamask/rpc-methods-flask>nanoid": { "globals": { - "btoa": true, - "crypto.getRandomValues": true, - "crypto.subtle.decrypt": true, - "crypto.subtle.deriveKey": true, - "crypto.subtle.encrypt": true, - "crypto.subtle.exportKey": true, - "crypto.subtle.importKey": true - }, - "packages": { - "browserify>buffer": true + "crypto.getRandomValues": true } }, "@metamask/rpc-methods>nanoid": { @@ -1922,16 +1922,15 @@ "@metamask/permission-controller": true, "@metamask/post-message-stream": true, "@metamask/providers>@metamask/object-multiplex": true, - "@metamask/rpc-methods": true, + "@metamask/snaps-controllers-flask>@metamask/rpc-methods": true, + "@metamask/snaps-controllers-flask>@metamask/snaps-utils": true, "@metamask/snaps-controllers-flask>concat-stream": true, "@metamask/snaps-controllers-flask>nanoid": true, "@metamask/snaps-controllers>@xstate/fsm": true, "@metamask/snaps-controllers>gunzip-maybe": true, "@metamask/snaps-controllers>readable-web-to-node-stream": true, "@metamask/snaps-controllers>tar-stream": true, - "@metamask/snaps-utils": true, "@metamask/snaps-utils>@metamask/snaps-registry": true, - "@metamask/subject-metadata-controller": true, "@metamask/utils": true, "eth-rpc-errors": true, "json-rpc-engine": true, @@ -1939,6 +1938,47 @@ "pump": true } }, + "@metamask/snaps-controllers-flask>@metamask/rpc-methods": { + "packages": { + "@metamask/key-tree": true, + "@metamask/key-tree>@noble/hashes": true, + "@metamask/permission-controller": true, + "@metamask/snaps-controllers-flask>@metamask/snaps-utils": true, + "@metamask/snaps-controllers-flask>@metamask/snaps-utils>@metamask/snaps-ui": true, + "@metamask/snaps-controllers-flask>nanoid": true, + "@metamask/utils": true, + "eth-rpc-errors": true, + "superstruct": true + } + }, + "@metamask/snaps-controllers-flask>@metamask/snaps-utils": { + "globals": { + "TextDecoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "@metamask/key-tree>@scure/base": true, + "@metamask/snaps-utils>cron-parser": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "@metamask/snaps-utils>rfdc": true, + "@metamask/snaps-utils>validate-npm-package-name": true, + "@metamask/utils": true, + "semver": true, + "superstruct": true + } + }, + "@metamask/snaps-controllers-flask>@metamask/snaps-utils>@metamask/snaps-ui": { + "packages": { + "@metamask/utils": true, + "superstruct": true + } + }, "@metamask/snaps-controllers-flask>concat-stream": { "packages": { "@metamask/snaps-controllers-flask>concat-stream>readable-stream": true, @@ -2090,40 +2130,12 @@ "pumpify>inherits": true } }, - "@metamask/snaps-ui": { - "packages": { - "@metamask/utils": true, - "superstruct": true - } - }, "@metamask/snaps-ui-flask": { "packages": { "@metamask/utils": true, "superstruct": true } }, - "@metamask/snaps-utils": { - "globals": { - "TextDecoder": true, - "URL": true, - "console.error": true, - "console.log": true, - "console.warn": true, - "document.body.appendChild": true, - "document.createElement": true - }, - "packages": { - "@metamask/key-tree>@noble/hashes": true, - "@metamask/key-tree>@scure/base": true, - "@metamask/snaps-utils>cron-parser": true, - "@metamask/snaps-utils>fast-json-stable-stringify": true, - "@metamask/snaps-utils>rfdc": true, - "@metamask/snaps-utils>validate-npm-package-name": true, - "@metamask/utils": true, - "semver": true, - "superstruct": true - } - }, "@metamask/snaps-utils-flask": { "globals": { "TextDecoder": true, diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index bff7adb06fbe..a049ea57c697 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -868,6 +868,20 @@ "immer": true } }, + "@metamask/browser-passworder": { + "globals": { + "btoa": true, + "crypto.getRandomValues": true, + "crypto.subtle.decrypt": true, + "crypto.subtle.deriveKey": true, + "crypto.subtle.encrypt": true, + "crypto.subtle.exportKey": true, + "crypto.subtle.importKey": true + }, + "packages": { + "browserify>buffer": true + } + }, "@metamask/controller-utils": { "globals": { "URL": true, @@ -998,11 +1012,11 @@ }, "@metamask/eth-keyring-controller": { "packages": { + "@metamask/browser-passworder": true, "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring": true, "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring": true, "@metamask/eth-keyring-controller>obs-store": true, "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, - "@metamask/rpc-methods>@metamask/browser-passworder": true, "browserify>events": true } }, @@ -1610,20 +1624,6 @@ "crypto.getRandomValues": true } }, - "@metamask/rpc-methods>@metamask/browser-passworder": { - "globals": { - "btoa": true, - "crypto.getRandomValues": true, - "crypto.subtle.decrypt": true, - "crypto.subtle.deriveKey": true, - "crypto.subtle.encrypt": true, - "crypto.subtle.exportKey": true, - "crypto.subtle.importKey": true - }, - "packages": { - "browserify>buffer": true - } - }, "@metamask/rpc-methods>nanoid": { "globals": { "crypto.getRandomValues": true diff --git a/lavamoat/build-system/policy.json b/lavamoat/build-system/policy.json index fd2759c0b15c..29f62566ac78 100644 --- a/lavamoat/build-system/policy.json +++ b/lavamoat/build-system/policy.json @@ -15,13 +15,19 @@ "fs": true, "module": true, "path": true, + "process": true, "url": true, - "util": true + "util": true, + "v8": true }, "globals": { "console.error": true, "console.log": true, - "process": true + "process.env.BABEL_ENV": true, + "process.env.BABEL_SHOW_CONFIG_FOR": true, + "process.env.NODE_ENV": true, + "process.versions.node": true, + "process.versions.pnp": true }, "packages": { "$root$": true, @@ -66,7 +72,8 @@ "packages": { "@babel/core>@babel/generator>jsesc": true, "@babel/core>@babel/types": true, - "terser>@jridgewell/source-map>@jridgewell/gen-mapping": true + "terser>@jridgewell/source-map>@jridgewell/gen-mapping": true, + "terser>@jridgewell/source-map>@jridgewell/trace-mapping": true } }, "@babel/core>@babel/generator>jsesc": { @@ -139,7 +146,7 @@ }, "@babel/core>@babel/types": { "globals": { - "console.trace": true, + "console.warn": true, "process.env.BABEL_TYPES_8_BREAKING": true }, "packages": { @@ -2225,8 +2232,7 @@ }, "depcheck>@babel/traverse": { "globals": { - "console.log": true, - "console.trace": true + "console.log": true }, "packages": { "@babel/code-frame": true, diff --git a/package.json b/package.json index 5c2920ed622c..2d0c117ac7be 100644 --- a/package.json +++ b/package.json @@ -210,7 +210,18 @@ "fast-json-patch@^3.1.1": "patch:fast-json-patch@npm%3A3.1.1#./.yarn/patches/fast-json-patch-npm-3.1.1-7e8bb70a45.patch", "request@^2.83.0": "patch:request@npm%3A2.88.2#./.yarn/patches/request-npm-2.88.2-f4a57c72c4.patch", "request@^2.88.2": "patch:request@npm%3A2.88.2#./.yarn/patches/request-npm-2.88.2-f4a57c72c4.patch", - "request@^2.85.0": "patch:request@npm%3A2.88.2#./.yarn/patches/request-npm-2.88.2-f4a57c72c4.patch" + "request@^2.85.0": "patch:request@npm%3A2.88.2#./.yarn/patches/request-npm-2.88.2-f4a57c72c4.patch", + "@babel/core@^7.12.1": "patch:@babel/core@npm%3A7.21.5#./.yarn/patches/@babel-core-npm-7.21.5-c72c337956.patch", + "@babel/core@^7.20.12": "patch:@babel/core@npm%3A7.21.5#./.yarn/patches/@babel-core-npm-7.21.5-c72c337956.patch", + "@babel/core@^7.18.6": "patch:@babel/core@npm%3A7.21.5#./.yarn/patches/@babel-core-npm-7.21.5-c72c337956.patch", + "@babel/core@^7.18.13": "patch:@babel/core@npm%3A7.21.5#./.yarn/patches/@babel-core-npm-7.21.5-c72c337956.patch", + "@babel/core@^7.12.10": "patch:@babel/core@npm%3A7.21.5#./.yarn/patches/@babel-core-npm-7.21.5-c72c337956.patch", + "@babel/core@^7.7.5": "patch:@babel/core@npm%3A7.21.5#./.yarn/patches/@babel-core-npm-7.21.5-c72c337956.patch", + "@babel/core@^7.11.6": "patch:@babel/core@npm%3A7.21.5#./.yarn/patches/@babel-core-npm-7.21.5-c72c337956.patch", + "@babel/core@>=7.9.0": "patch:@babel/core@npm%3A7.21.5#./.yarn/patches/@babel-core-npm-7.21.5-c72c337956.patch", + "@babel/core@^7.1.0": "patch:@babel/core@npm%3A7.21.5#./.yarn/patches/@babel-core-npm-7.21.5-c72c337956.patch", + "@babel/core@^7.12.3": "patch:@babel/core@npm%3A7.21.5#./.yarn/patches/@babel-core-npm-7.21.5-c72c337956.patch", + "@babel/core@7.12.9": "patch:@babel/core@npm%3A7.21.5#./.yarn/patches/@babel-core-npm-7.21.5-c72c337956.patch" }, "dependencies": { "@actions/core": "^1.10.0", @@ -237,6 +248,7 @@ "@metamask/approval-controller": "^2.1.0", "@metamask/assets-controllers": "^6.0.0", "@metamask/base-controller": "^2.0.0", + "@metamask/browser-passworder": "^4.1.0", "@metamask/contract-metadata": "^2.3.1", "@metamask/controller-utils": "^3.3.0", "@metamask/design-tokens": "^1.9.0", @@ -257,24 +269,24 @@ "@metamask/metamask-eth-abis": "^3.0.0", "@metamask/notification-controller": "^2.0.0", "@metamask/obs-store": "^8.1.0", - "@metamask/permission-controller": "^3.1.0", + "@metamask/permission-controller": "^3.2.0", "@metamask/phishing-controller": "^3.0.0", "@metamask/post-message-stream": "^6.0.0", "@metamask/providers": "^10.2.1", "@metamask/rate-limit-controller": "^2.0.0", "@metamask/rpc-methods": "^0.32.2", - "@metamask/rpc-methods-flask": "npm:@metamask/rpc-methods@0.32.2", + "@metamask/rpc-methods-flask": "npm:@metamask/rpc-methods@0.33.1-flask.1", "@metamask/safe-event-emitter": "^2.0.0", "@metamask/scure-bip39": "^2.0.3", "@metamask/signature-controller": "^2.0.0", "@metamask/slip44": "^3.0.0", "@metamask/smart-transactions-controller": "^3.1.0", "@metamask/snaps-controllers": "^0.32.2", - "@metamask/snaps-controllers-flask": "npm:@metamask/snaps-controllers@0.32.2", + "@metamask/snaps-controllers-flask": "npm:@metamask/snaps-controllers@0.33.1-flask.1", "@metamask/snaps-ui": "^0.32.2", - "@metamask/snaps-ui-flask": "npm:@metamask/snaps-ui@0.32.2", + "@metamask/snaps-ui-flask": "npm:@metamask/snaps-ui@0.33.1-flask.1", "@metamask/snaps-utils": "^0.32.2", - "@metamask/snaps-utils-flask": "npm:@metamask/snaps-utils@0.32.2", + "@metamask/snaps-utils-flask": "npm:@metamask/snaps-utils@0.33.1-flask.1", "@metamask/subject-metadata-controller": "^2.0.0", "@metamask/swappable-obj-proxy": "^2.1.0", "@metamask/utils": "^5.0.0", diff --git a/test/e2e/snaps/test-snap-rpc.spec.js b/test/e2e/snaps/test-snap-rpc.spec.js index 9135801c306b..3505a6c85d2f 100644 --- a/test/e2e/snaps/test-snap-rpc.spec.js +++ b/test/e2e/snaps/test-snap-rpc.spec.js @@ -3,10 +3,7 @@ const FixtureBuilder = require('../fixture-builder'); const { TEST_SNAPS_WEBSITE_URL } = require('./enums'); describe('Test Snap RPC', function () { - // Disabled for now due to a bug. - // TODO: Re-enable when fixed. - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('can use the cross-snap RPC endowment and produce a public key', async function () { + it('can use the cross-snap RPC endowment and produce a public key', async function () { const ganacheOptions = { accounts: [ { diff --git a/test/env.js b/test/env.js index fd055ee8ac19..cb66e3d3bf66 100644 --- a/test/env.js +++ b/test/env.js @@ -1,2 +1,4 @@ process.env.METAMASK_ENVIRONMENT = 'test'; process.env.SUPPORT_LINK = 'https://support.metamask.io'; +process.env.IFRAME_EXECUTION_ENVIRONMENT_URL = + 'https://execution.metamask.io/0.16.0-flask.1/index.html'; diff --git a/yarn.lock b/yarn.lock index 7737542671f0..669dbbd2f71a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -36,7 +36,7 @@ __metadata: languageName: node linkType: hard -"@ampproject/remapping@npm:^2.1.0": +"@ampproject/remapping@npm:^2.2.0": version: 2.2.1 resolution: "@ampproject/remapping@npm:2.2.1" dependencies: @@ -62,66 +62,65 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.16.7, @babel/code-frame@npm:^7.18.6, @babel/code-frame@npm:^7.5.5, @babel/code-frame@npm:^7.8.3": - version: 7.18.6 - resolution: "@babel/code-frame@npm:7.18.6" +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.16.7, @babel/code-frame@npm:^7.18.6, @babel/code-frame@npm:^7.21.4, @babel/code-frame@npm:^7.5.5, @babel/code-frame@npm:^7.8.3": + version: 7.21.4 + resolution: "@babel/code-frame@npm:7.21.4" dependencies: "@babel/highlight": ^7.18.6 - checksum: 195e2be3172d7684bf95cff69ae3b7a15a9841ea9d27d3c843662d50cdd7d6470fd9c8e64be84d031117e4a4083486effba39f9aef6bbb2c89f7f21bcfba33ba + checksum: e5390e6ec1ac58dcef01d4f18eaf1fd2f1325528661ff6d4a5de8979588b9f5a8e852a54a91b923846f7a5c681b217f0a45c2524eb9560553160cd963b7d592c languageName: node linkType: hard -"@babel/compat-data@npm:^7.17.7, @babel/compat-data@npm:^7.20.1, @babel/compat-data@npm:^7.20.5": - version: 7.20.10 - resolution: "@babel/compat-data@npm:7.20.10" - checksum: 6ed6c1bb6fc03c225d63b8611788cd976107d1692402b560ebffbf1fa53e63705f8625bb12e12d17ce7f7af34e61e1ca96c77858aac6f57010045271466200c0 +"@babel/compat-data@npm:^7.17.7, @babel/compat-data@npm:^7.20.1, @babel/compat-data@npm:^7.20.5, @babel/compat-data@npm:^7.21.5": + version: 7.21.7 + resolution: "@babel/compat-data@npm:7.21.7" + checksum: 28747eb3fc084d088ba2db0336f52118cfa730a57bdbac81630cae1f38ad0336605b95b3390325937802f344e0b7fa25e2f1b67e3ee2d7383b877f88dee0e51c languageName: node linkType: hard -"@babel/core@npm:7.12.9": - version: 7.12.9 - resolution: "@babel/core@npm:7.12.9" +"@babel/core@npm:7.21.5": + version: 7.21.5 + resolution: "@babel/core@npm:7.21.5" dependencies: - "@babel/code-frame": ^7.10.4 - "@babel/generator": ^7.12.5 - "@babel/helper-module-transforms": ^7.12.1 - "@babel/helpers": ^7.12.5 - "@babel/parser": ^7.12.7 - "@babel/template": ^7.12.7 - "@babel/traverse": ^7.12.9 - "@babel/types": ^7.12.7 + "@ampproject/remapping": ^2.2.0 + "@babel/code-frame": ^7.21.4 + "@babel/generator": ^7.21.5 + "@babel/helper-compilation-targets": ^7.21.5 + "@babel/helper-module-transforms": ^7.21.5 + "@babel/helpers": ^7.21.5 + "@babel/parser": ^7.21.5 + "@babel/template": ^7.20.7 + "@babel/traverse": ^7.21.5 + "@babel/types": ^7.21.5 convert-source-map: ^1.7.0 debug: ^4.1.0 - gensync: ^1.0.0-beta.1 - json5: ^2.1.2 - lodash: ^4.17.19 - resolve: ^1.3.2 - semver: ^5.4.1 - source-map: ^0.5.0 - checksum: 4d34eca4688214a4eb6bd5dde906b69a7824f17b931f52cd03628a8ac94d8fbe15565aebffdde106e974c8738cd64ac62c6a6060baa7139a06db1f18c4ff872d + gensync: ^1.0.0-beta.2 + json5: ^2.2.2 + semver: ^6.3.0 + checksum: 77ca0e6493860fb6f91cf441313c0bb464d21f8c6842cf3f1dbb083a910370e37a4c0ada35cf11ef0ebe7d0ee2d6bde2f4ee9b4caa3328e807988aa282787677 languageName: node linkType: hard -"@babel/core@npm:>=7.9.0, @babel/core@npm:^7.1.0, @babel/core@npm:^7.11.6, @babel/core@npm:^7.12.1, @babel/core@npm:^7.12.10, @babel/core@npm:^7.12.3, @babel/core@npm:^7.18.13, @babel/core@npm:^7.18.6, @babel/core@npm:^7.7.5": - version: 7.20.12 - resolution: "@babel/core@npm:7.20.12" +"@babel/core@patch:@babel/core@npm%3A7.21.5#./.yarn/patches/@babel-core-npm-7.21.5-c72c337956.patch::locator=metamask-crx%40workspace%3A.": + version: 7.21.5 + resolution: "@babel/core@patch:@babel/core@npm%3A7.21.5#./.yarn/patches/@babel-core-npm-7.21.5-c72c337956.patch::version=7.21.5&hash=c84981&locator=metamask-crx%40workspace%3A." dependencies: - "@ampproject/remapping": ^2.1.0 - "@babel/code-frame": ^7.18.6 - "@babel/generator": ^7.20.7 - "@babel/helper-compilation-targets": ^7.20.7 - "@babel/helper-module-transforms": ^7.20.11 - "@babel/helpers": ^7.20.7 - "@babel/parser": ^7.20.7 + "@ampproject/remapping": ^2.2.0 + "@babel/code-frame": ^7.21.4 + "@babel/generator": ^7.21.5 + "@babel/helper-compilation-targets": ^7.21.5 + "@babel/helper-module-transforms": ^7.21.5 + "@babel/helpers": ^7.21.5 + "@babel/parser": ^7.21.5 "@babel/template": ^7.20.7 - "@babel/traverse": ^7.20.12 - "@babel/types": ^7.20.7 + "@babel/traverse": ^7.21.5 + "@babel/types": ^7.21.5 convert-source-map: ^1.7.0 debug: ^4.1.0 gensync: ^1.0.0-beta.2 json5: ^2.2.2 semver: ^6.3.0 - checksum: 62e6c3e2149a70b5c9729ef5f0d3e2e97e9dcde89fc039c8d8e3463d5d7ba9b29ee84d10faf79b61532ac1645aa62f2bd42338320617e6e3a8a4d8e2a27076e7 + checksum: 1b067f9452f1f9218418742e3ff28d5967bce51b251daf007cf8bc6a8e78ffee34f156d7b7f80b44017f48e3661ef88a44d4c2e9f01ad5bc43c247722c5cd9ed languageName: node linkType: hard @@ -151,14 +150,15 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.12.11, @babel/generator@npm:^7.12.5, @babel/generator@npm:^7.18.13, @babel/generator@npm:^7.20.7, @babel/generator@npm:^7.7.2": - version: 7.20.7 - resolution: "@babel/generator@npm:7.20.7" +"@babel/generator@npm:^7.12.11, @babel/generator@npm:^7.18.13, @babel/generator@npm:^7.21.5, @babel/generator@npm:^7.7.2": + version: 7.21.5 + resolution: "@babel/generator@npm:7.21.5" dependencies: - "@babel/types": ^7.20.7 + "@babel/types": ^7.21.5 "@jridgewell/gen-mapping": ^0.3.2 + "@jridgewell/trace-mapping": ^0.3.17 jsesc: ^2.5.1 - checksum: 84b6983ffdb50c80c1c2e3f3c32617a7133d8effd1065f3e0f9bba188a7d54ab42a4dd5e42b61b843c65f9dd1aa870036ff0f848ebd42707aaa8a2b6d31d04f5 + checksum: 78af737b9dd701d4c657f9731880430fa1c177767b562f4e8a330a7fe72a4abe857e3d24de4e6d9dafc1f6a11f894162d27e523d7e5948ff9e3925a0ce9867c4 languageName: node linkType: hard @@ -181,18 +181,18 @@ __metadata: languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.13.0, @babel/helper-compilation-targets@npm:^7.17.7, @babel/helper-compilation-targets@npm:^7.18.9, @babel/helper-compilation-targets@npm:^7.20.0, @babel/helper-compilation-targets@npm:^7.20.7": - version: 7.20.7 - resolution: "@babel/helper-compilation-targets@npm:7.20.7" +"@babel/helper-compilation-targets@npm:^7.13.0, @babel/helper-compilation-targets@npm:^7.17.7, @babel/helper-compilation-targets@npm:^7.18.9, @babel/helper-compilation-targets@npm:^7.20.0, @babel/helper-compilation-targets@npm:^7.20.7, @babel/helper-compilation-targets@npm:^7.21.5": + version: 7.21.5 + resolution: "@babel/helper-compilation-targets@npm:7.21.5" dependencies: - "@babel/compat-data": ^7.20.5 - "@babel/helper-validator-option": ^7.18.6 + "@babel/compat-data": ^7.21.5 + "@babel/helper-validator-option": ^7.21.0 browserslist: ^4.21.3 lru-cache: ^5.1.1 semver: ^6.3.0 peerDependencies: "@babel/core": ^7.0.0 - checksum: 8c32c873ba86e2e1805b30e0807abd07188acbe00ebb97576f0b09061cc65007f1312b589eccb4349c5a8c7f8bb9f2ab199d41da7030bf103d9f347dcd3a3cf4 + checksum: 0edecb9c970ddc22ebda1163e77a7f314121bef9e483e0e0d9a5802540eed90d5855b6bf9bce03419b35b2e07c323e62d0353b153fa1ca34f17dbba897a83c25 languageName: node linkType: hard @@ -259,10 +259,10 @@ __metadata: languageName: node linkType: hard -"@babel/helper-environment-visitor@npm:^7.18.9": - version: 7.18.9 - resolution: "@babel/helper-environment-visitor@npm:7.18.9" - checksum: b25101f6162ddca2d12da73942c08ad203d7668e06663df685634a8fde54a98bc015f6f62938e8554457a592a024108d45b8f3e651fd6dcdb877275b73cc4420 +"@babel/helper-environment-visitor@npm:^7.18.9, @babel/helper-environment-visitor@npm:^7.21.5": + version: 7.21.5 + resolution: "@babel/helper-environment-visitor@npm:7.21.5" + checksum: e436af7b62956e919066448013a3f7e2cd0b51010c26c50f790124dcd350be81d5597b4e6ed0a4a42d098a27de1e38561cd7998a116a42e7899161192deac9a6 languageName: node linkType: hard @@ -275,13 +275,13 @@ __metadata: languageName: node linkType: hard -"@babel/helper-function-name@npm:^7.18.9, @babel/helper-function-name@npm:^7.19.0": - version: 7.19.0 - resolution: "@babel/helper-function-name@npm:7.19.0" +"@babel/helper-function-name@npm:^7.18.9, @babel/helper-function-name@npm:^7.19.0, @babel/helper-function-name@npm:^7.21.0": + version: 7.21.0 + resolution: "@babel/helper-function-name@npm:7.21.0" dependencies: - "@babel/template": ^7.18.10 - "@babel/types": ^7.19.0 - checksum: eac1f5db428ba546270c2b8d750c24eb528b8fcfe50c81de2e0bdebf0e20f24bec688d4331533b782e4a907fad435244621ca2193cfcf80a86731299840e0f6e + "@babel/template": ^7.20.7 + "@babel/types": ^7.21.0 + checksum: d63e63c3e0e3e8b3138fa47b0cd321148a300ef12b8ee951196994dcd2a492cc708aeda94c2c53759a5c9177fffaac0fd8778791286746f72a000976968daf4e languageName: node linkType: hard @@ -303,28 +303,28 @@ __metadata: languageName: node linkType: hard -"@babel/helper-module-imports@npm:^7.0.0, @babel/helper-module-imports@npm:^7.12.13, @babel/helper-module-imports@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helper-module-imports@npm:7.18.6" +"@babel/helper-module-imports@npm:^7.0.0, @babel/helper-module-imports@npm:^7.12.13, @babel/helper-module-imports@npm:^7.18.6, @babel/helper-module-imports@npm:^7.21.4": + version: 7.21.4 + resolution: "@babel/helper-module-imports@npm:7.21.4" dependencies: - "@babel/types": ^7.18.6 - checksum: f393f8a3b3304b1b7a288a38c10989de754f01d29caf62ce7c4e5835daf0a27b81f3ac687d9d2780d39685aae7b55267324b512150e7b2be967b0c493b6a1def + "@babel/types": ^7.21.4 + checksum: bd330a2edaafeb281fbcd9357652f8d2666502567c0aad71db926e8499c773c9ea9c10dfaae30122452940326d90c8caff5c649ed8e1bf15b23f858758d3abc6 languageName: node linkType: hard -"@babel/helper-module-transforms@npm:^7.12.1, @babel/helper-module-transforms@npm:^7.18.6, @babel/helper-module-transforms@npm:^7.20.11": - version: 7.20.11 - resolution: "@babel/helper-module-transforms@npm:7.20.11" +"@babel/helper-module-transforms@npm:^7.18.6, @babel/helper-module-transforms@npm:^7.20.11, @babel/helper-module-transforms@npm:^7.21.5": + version: 7.21.5 + resolution: "@babel/helper-module-transforms@npm:7.21.5" dependencies: - "@babel/helper-environment-visitor": ^7.18.9 - "@babel/helper-module-imports": ^7.18.6 - "@babel/helper-simple-access": ^7.20.2 + "@babel/helper-environment-visitor": ^7.21.5 + "@babel/helper-module-imports": ^7.21.4 + "@babel/helper-simple-access": ^7.21.5 "@babel/helper-split-export-declaration": ^7.18.6 "@babel/helper-validator-identifier": ^7.19.1 "@babel/template": ^7.20.7 - "@babel/traverse": ^7.20.10 - "@babel/types": ^7.20.7 - checksum: 29319ebafa693d48756c6ba0d871677bb0037e0da084fbe221a17c38d57093fc8aa38543c07d76e788266a937976e37ab4901971ca7f237c5ab45f524b9ecca0 + "@babel/traverse": ^7.21.5 + "@babel/types": ^7.21.5 + checksum: 1ccfc88830675a5d485d198e918498f9683cdd46f973fdd4fe1c85b99648fb70f87fca07756c7a05dc201bd9b248c74ced06ea80c9991926ac889f53c3659675 languageName: node linkType: hard @@ -379,12 +379,12 @@ __metadata: languageName: node linkType: hard -"@babel/helper-simple-access@npm:^7.20.2": - version: 7.20.2 - resolution: "@babel/helper-simple-access@npm:7.20.2" +"@babel/helper-simple-access@npm:^7.20.2, @babel/helper-simple-access@npm:^7.21.5": + version: 7.21.5 + resolution: "@babel/helper-simple-access@npm:7.21.5" dependencies: - "@babel/types": ^7.20.2 - checksum: ad1e96ee2e5f654ffee2369a586e5e8d2722bf2d8b028a121b4c33ebae47253f64d420157b9f0a8927aea3a9e0f18c0103e74fdd531815cf3650a0a4adca11a1 + "@babel/types": ^7.21.5 + checksum: ad212beaa24be3864c8c95bee02f840222457ccf5419991e2d3e3e39b0f75b77e7e857e0bf4ed428b1cd97acefc87f3831bdb0b9696d5ad0557421f398334fc3 languageName: node linkType: hard @@ -406,10 +406,10 @@ __metadata: languageName: node linkType: hard -"@babel/helper-string-parser@npm:^7.19.4": - version: 7.19.4 - resolution: "@babel/helper-string-parser@npm:7.19.4" - checksum: b2f8a3920b30dfac81ec282ac4ad9598ea170648f8254b10f475abe6d944808fb006aab325d3eb5a8ad3bea8dfa888cfa6ef471050dae5748497c110ec060943 +"@babel/helper-string-parser@npm:^7.21.5": + version: 7.21.5 + resolution: "@babel/helper-string-parser@npm:7.21.5" + checksum: 36c0ded452f3858e67634b81960d4bde1d1cd2a56b82f4ba2926e97864816021c885f111a7cf81de88a0ed025f49d84a393256700e9acbca2d99462d648705d8 languageName: node linkType: hard @@ -420,10 +420,10 @@ __metadata: languageName: node linkType: hard -"@babel/helper-validator-option@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helper-validator-option@npm:7.18.6" - checksum: f9cc6eb7cc5d759c5abf006402180f8d5e4251e9198197428a97e05d65eb2f8ae5a0ce73b1dfd2d35af41d0eb780627a64edf98a4e71f064eeeacef8de58f2cf +"@babel/helper-validator-option@npm:^7.18.6, @babel/helper-validator-option@npm:^7.21.0": + version: 7.21.0 + resolution: "@babel/helper-validator-option@npm:7.21.0" + checksum: 8ece4c78ffa5461fd8ab6b6e57cc51afad59df08192ed5d84b475af4a7193fc1cb794b59e3e7be64f3cdc4df7ac78bf3dbb20c129d7757ae078e6279ff8c2f07 languageName: node linkType: hard @@ -439,14 +439,14 @@ __metadata: languageName: node linkType: hard -"@babel/helpers@npm:^7.12.5, @babel/helpers@npm:^7.20.7": - version: 7.20.7 - resolution: "@babel/helpers@npm:7.20.7" +"@babel/helpers@npm:^7.21.5": + version: 7.21.5 + resolution: "@babel/helpers@npm:7.21.5" dependencies: "@babel/template": ^7.20.7 - "@babel/traverse": ^7.20.7 - "@babel/types": ^7.20.7 - checksum: 3fb10df3510ba7116a180d5fd983d0f558f7a65c3d599385dba991bff66b74174c88881bc12c2b3cf7284294fcac5b301ded49a8b0098bdf2ef61d0cad8010db + "@babel/traverse": ^7.21.5 + "@babel/types": ^7.21.5 + checksum: a6f74b8579713988e7f5adf1a986d8b5255757632ba65b2552f0f609ead5476edb784044c7e4b18f3681ee4818ca9d08c41feb9bd4e828648c25a00deaa1f9e4 languageName: node linkType: hard @@ -470,12 +470,12 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.0.0, @babel/parser@npm:^7.1.0, @babel/parser@npm:^7.10.1, @babel/parser@npm:^7.12.0, @babel/parser@npm:^7.12.11, @babel/parser@npm:^7.12.7, @babel/parser@npm:^7.13.9, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7": - version: 7.20.7 - resolution: "@babel/parser@npm:7.20.7" +"@babel/parser@npm:^7.0.0, @babel/parser@npm:^7.1.0, @babel/parser@npm:^7.10.1, @babel/parser@npm:^7.12.0, @babel/parser@npm:^7.12.11, @babel/parser@npm:^7.13.9, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.21.5": + version: 7.21.5 + resolution: "@babel/parser@npm:7.21.5" bin: parser: ./bin/babel-parser.js - checksum: 25b5266e3bd4be837092685f6b7ef886f1308ff72659a24342eb646ae5014f61ed1771ce8fc20636c890fcae19304fc72c069564ca6075207b7fbf3f75367275 + checksum: c7ec0dae795f2a43885fdd5c1c53c7f11b3428628ae82ebe1e1537cb3d13e25e7993549e026662a3e05dcc743b595f82b25f0a49ef9155459a9a424eedb7e2b0 languageName: node linkType: hard @@ -1645,7 +1645,7 @@ __metadata: languageName: node linkType: hard -"@babel/template@npm:^7.12.7, @babel/template@npm:^7.14.5, @babel/template@npm:^7.18.10, @babel/template@npm:^7.18.6, @babel/template@npm:^7.20.7, @babel/template@npm:^7.3.3": +"@babel/template@npm:^7.14.5, @babel/template@npm:^7.18.6, @babel/template@npm:^7.20.7, @babel/template@npm:^7.3.3": version: 7.20.7 resolution: "@babel/template@npm:7.20.7" dependencies: @@ -1656,32 +1656,32 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.10.1, @babel/traverse@npm:^7.12.11, @babel/traverse@npm:^7.12.5, @babel/traverse@npm:^7.12.9, @babel/traverse@npm:^7.13.0, @babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.20.10, @babel/traverse@npm:^7.20.12, @babel/traverse@npm:^7.20.7, @babel/traverse@npm:^7.7.2": - version: 7.20.12 - resolution: "@babel/traverse@npm:7.20.12" +"@babel/traverse@npm:^7.10.1, @babel/traverse@npm:^7.12.11, @babel/traverse@npm:^7.12.5, @babel/traverse@npm:^7.13.0, @babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.20.7, @babel/traverse@npm:^7.21.5, @babel/traverse@npm:^7.7.2": + version: 7.21.5 + resolution: "@babel/traverse@npm:7.21.5" dependencies: - "@babel/code-frame": ^7.18.6 - "@babel/generator": ^7.20.7 - "@babel/helper-environment-visitor": ^7.18.9 - "@babel/helper-function-name": ^7.19.0 + "@babel/code-frame": ^7.21.4 + "@babel/generator": ^7.21.5 + "@babel/helper-environment-visitor": ^7.21.5 + "@babel/helper-function-name": ^7.21.0 "@babel/helper-hoist-variables": ^7.18.6 "@babel/helper-split-export-declaration": ^7.18.6 - "@babel/parser": ^7.20.7 - "@babel/types": ^7.20.7 + "@babel/parser": ^7.21.5 + "@babel/types": ^7.21.5 debug: ^4.1.0 globals: ^11.1.0 - checksum: d758b355ab4f1e87984524b67785fa23d74e8a45d2ceb8bcf4d5b2b0cd15ee160db5e68c7078808542805774ca3802e2eafb1b9638afa4cd7f9ecabd0ca7fd56 + checksum: b403733fa7d858f0c8e224f0434a6ade641bc469a4f92975363391e796629d5bf53e544761dfe85039aab92d5389ebe7721edb309d7a5bb7df2bf74f37bf9f47 languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.12.0, @babel/types@npm:^7.12.11, @babel/types@npm:^7.12.7, @babel/types@npm:^7.13.0, @babel/types@npm:^7.14.8, @babel/types@npm:^7.18.6, @babel/types@npm:^7.18.7, @babel/types@npm:^7.18.9, @babel/types@npm:^7.19.0, @babel/types@npm:^7.20.0, @babel/types@npm:^7.20.2, @babel/types@npm:^7.20.7, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": - version: 7.20.7 - resolution: "@babel/types@npm:7.20.7" +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.12.0, @babel/types@npm:^7.12.11, @babel/types@npm:^7.13.0, @babel/types@npm:^7.14.8, @babel/types@npm:^7.18.6, @babel/types@npm:^7.18.7, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.0, @babel/types@npm:^7.20.2, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.0, @babel/types@npm:^7.21.4, @babel/types@npm:^7.21.5, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": + version: 7.21.5 + resolution: "@babel/types@npm:7.21.5" dependencies: - "@babel/helper-string-parser": ^7.19.4 + "@babel/helper-string-parser": ^7.21.5 "@babel/helper-validator-identifier": ^7.19.1 to-fast-properties: ^2.0.0 - checksum: b39af241f0b72bba67fd6d0d23914f6faec8c0eba8015c181cbd5ea92e59fc91a52a1ab490d3520c7dbd19ddb9ebb76c476308f6388764f16d8201e37fae6811 + checksum: 43242a99c612d13285ee4af46cc0f1066bcb6ffd38307daef7a76e8c70f36cfc3255eb9e75c8e768b40e761176c313aec4d5c0b9d97a21e494d49d5fd123a9f7 languageName: node linkType: hard @@ -3736,10 +3736,10 @@ __metadata: languageName: node linkType: hard -"@metamask/browser-passworder@npm:^4.0.2": - version: 4.0.2 - resolution: "@metamask/browser-passworder@npm:4.0.2" - checksum: 997c330b1c72f7135d0fd2a7f4b0dce37b3c2e6b30e92f048fa8d4f8c949f5b669dcc3064790f41df30ee2e53a9e64a812df72e00527736be704cce2cf4f6e49 +"@metamask/browser-passworder@npm:^4.0.2, @metamask/browser-passworder@npm:^4.1.0": + version: 4.1.0 + resolution: "@metamask/browser-passworder@npm:4.1.0" + checksum: f1edb3b75594b8e8d075b3134c9ce6c73573160eb48184ef722b9d96a5763db1e2e9acb166fc5c66c7c82936e134a02a3fb4c0022ca9a948857a30181cb84d7e languageName: node linkType: hard @@ -4208,23 +4208,23 @@ __metadata: languageName: node linkType: hard -"@metamask/permission-controller@npm:^3.0.0, @metamask/permission-controller@npm:^3.1.0": - version: 3.1.0 - resolution: "@metamask/permission-controller@npm:3.1.0" +"@metamask/permission-controller@npm:^3.0.0, @metamask/permission-controller@npm:^3.1.0, @metamask/permission-controller@npm:^3.2.0": + version: 3.2.0 + resolution: "@metamask/permission-controller@npm:3.2.0" dependencies: - "@metamask/approval-controller": ^2.0.0 + "@metamask/approval-controller": ^2.1.1 "@metamask/base-controller": ^2.0.0 - "@metamask/controller-utils": ^3.1.0 + "@metamask/controller-utils": ^3.4.0 "@metamask/types": ^1.1.0 "@types/deep-freeze-strict": ^1.1.0 deep-freeze-strict: ^1.1.1 - eth-rpc-errors: ^4.0.0 + eth-rpc-errors: ^4.0.2 immer: ^9.0.6 json-rpc-engine: ^6.1.0 nanoid: ^3.1.31 peerDependencies: - "@metamask/approval-controller": ^2.0.0 - checksum: 91c615e2faf9c1ce16371602e99354178d79a0af0697f1c3c81c860c2e9972f23e8189290cd41423c3df3048906bbf55c01fef8326677f2ea5cbcfdfb6a58db6 + "@metamask/approval-controller": ^2.1.1 + checksum: 34650f2d9b51170fc9b2a56d2004a2cd2ca3f427e9dad2e3229bbd0b1dfad40a98f639752b1bfe17946c39677e4c8fe52581b5dbbf42e4a18e83b72e8e06d9b0 languageName: node linkType: hard @@ -4258,13 +4258,13 @@ __metadata: languageName: node linkType: hard -"@metamask/post-message-stream@npm:^6.0.0, @metamask/post-message-stream@npm:^6.1.1": - version: 6.1.1 - resolution: "@metamask/post-message-stream@npm:6.1.1" +"@metamask/post-message-stream@npm:^6.0.0, @metamask/post-message-stream@npm:^6.1.1, @metamask/post-message-stream@npm:^6.1.2": + version: 6.1.2 + resolution: "@metamask/post-message-stream@npm:6.1.2" dependencies: - "@metamask/utils": ^4.0.0 + "@metamask/utils": ^5.0.0 readable-stream: 2.3.3 - checksum: 588988d555ff20e0d60ed2de71e804d96963a9d4c07716901d5ae13100eb0f97c49eb6b2dbd93ac408098067c0e12cbfca90fed345fecdb69c92e852ab28c0b4 + checksum: 3591ec9b7fd602806b07cbc0fed5075fb7a347c279c43ef1f25fbdd8634dfcad9ce192ae59457fb76554ef0bc15cbf25cfaa5875aee2d72668a273b7a6852c32 languageName: node linkType: hard @@ -4309,7 +4309,25 @@ __metadata: languageName: node linkType: hard -"@metamask/rpc-methods-flask@npm:@metamask/rpc-methods@0.32.2, @metamask/rpc-methods@npm:^0.32.2": +"@metamask/rpc-methods-flask@npm:@metamask/rpc-methods@0.33.1-flask.1, @metamask/rpc-methods@npm:^0.33.1-flask.1": + version: 0.33.1-flask.1 + resolution: "@metamask/rpc-methods@npm:0.33.1-flask.1" + dependencies: + "@metamask/key-tree": ^7.0.0 + "@metamask/permission-controller": ^3.2.0 + "@metamask/snaps-ui": ^0.33.1-flask.1 + "@metamask/snaps-utils": ^0.33.1-flask.1 + "@metamask/types": ^1.1.0 + "@metamask/utils": ^5.0.0 + "@noble/hashes": ^1.1.3 + eth-rpc-errors: ^4.0.3 + nanoid: ^3.1.31 + superstruct: ^1.0.3 + checksum: 1a7fd246944b68c8b70ab64e5db544b175b68195cca4a2692bf34bca7f5c55eab248156e4702152f044c49c644ad82a92d1b328fcfa876291665d2a8c2ae7dce + languageName: node + linkType: hard + +"@metamask/rpc-methods@npm:^0.32.2": version: 0.32.2 resolution: "@metamask/rpc-methods@npm:0.32.2" dependencies: @@ -4386,7 +4404,37 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-controllers-flask@npm:@metamask/snaps-controllers@0.32.2, @metamask/snaps-controllers@npm:^0.32.2": +"@metamask/snaps-controllers-flask@npm:@metamask/snaps-controllers@0.33.1-flask.1": + version: 0.33.1-flask.1 + resolution: "@metamask/snaps-controllers@npm:0.33.1-flask.1" + dependencies: + "@metamask/approval-controller": ^2.0.0 + "@metamask/base-controller": ^2.0.0 + "@metamask/object-multiplex": ^1.2.0 + "@metamask/permission-controller": ^3.2.0 + "@metamask/post-message-stream": ^6.1.2 + "@metamask/rpc-methods": ^0.33.1-flask.1 + "@metamask/snaps-execution-environments": ^0.33.1-flask.1 + "@metamask/snaps-registry": ^1.2.0 + "@metamask/snaps-utils": ^0.33.1-flask.1 + "@metamask/utils": ^5.0.0 + "@xstate/fsm": ^2.0.0 + concat-stream: ^2.0.0 + cron-parser: ^4.5.0 + eth-rpc-errors: ^4.0.3 + gunzip-maybe: ^1.4.2 + immer: ^9.0.6 + json-rpc-engine: ^6.1.0 + json-rpc-middleware-stream: ^4.2.0 + nanoid: ^3.1.31 + pump: ^3.0.0 + readable-web-to-node-stream: ^3.0.2 + tar-stream: ^2.2.0 + checksum: 26fcba72c8bcb84783a970db4af2bfec65429a7206eadc05856873739b85aebd75b0d8d6062eadb9fffce30c436f79ff4bbca9d15e9e80016f1b27ac5e8693bc + languageName: node + linkType: hard + +"@metamask/snaps-controllers@npm:^0.32.2": version: 0.32.2 resolution: "@metamask/snaps-controllers@npm:0.32.2" dependencies: @@ -4437,6 +4485,27 @@ __metadata: languageName: node linkType: hard +"@metamask/snaps-execution-environments@npm:^0.33.1-flask.1": + version: 0.33.1-flask.1 + resolution: "@metamask/snaps-execution-environments@npm:0.33.1-flask.1" + dependencies: + "@metamask/object-multiplex": ^1.2.0 + "@metamask/post-message-stream": ^6.1.2 + "@metamask/providers": ^10.2.1 + "@metamask/rpc-methods": ^0.33.1-flask.1 + "@metamask/snaps-utils": ^0.33.1-flask.1 + "@metamask/utils": ^5.0.0 + eth-rpc-errors: ^4.0.3 + json-rpc-engine: ^6.1.0 + nanoid: ^3.1.31 + pump: ^3.0.0 + ses: ^0.18.1 + stream-browserify: ^3.0.0 + superstruct: ^1.0.3 + checksum: e86d70befbb6e3639368d7974f3e89ef343be1932a606a4b77ddfafdc0ed672a227481458e8935392bad9494c82deb75b6e1761606baea08ff165f4e6974f262 + languageName: node + linkType: hard + "@metamask/snaps-registry@npm:^1.2.0": version: 1.2.0 resolution: "@metamask/snaps-registry@npm:1.2.0" @@ -4448,7 +4517,17 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-ui-flask@npm:@metamask/snaps-ui@0.32.2, @metamask/snaps-ui@npm:^0.32.2": +"@metamask/snaps-ui-flask@npm:@metamask/snaps-ui@0.33.1-flask.1, @metamask/snaps-ui@npm:^0.33.1-flask.1": + version: 0.33.1-flask.1 + resolution: "@metamask/snaps-ui@npm:0.33.1-flask.1" + dependencies: + "@metamask/utils": ^5.0.0 + superstruct: ^1.0.3 + checksum: 52f00f1ac1e23da1fbb0307cd5bdc620cf9c235ee94a6cb6fb4fd38da0677a5d8df501952e3b7119bac918c3a56e8ffe453cbff0b2be35d3b882882deac65dc4 + languageName: node + linkType: hard + +"@metamask/snaps-ui@npm:^0.32.2": version: 0.32.2 resolution: "@metamask/snaps-ui@npm:0.32.2" dependencies: @@ -4458,7 +4537,34 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-utils-flask@npm:@metamask/snaps-utils@0.32.2, @metamask/snaps-utils@npm:^0.32.2": +"@metamask/snaps-utils-flask@npm:@metamask/snaps-utils@0.33.1-flask.1, @metamask/snaps-utils@npm:^0.33.1-flask.1": + version: 0.33.1-flask.1 + resolution: "@metamask/snaps-utils@npm:0.33.1-flask.1" + dependencies: + "@babel/core": ^7.20.12 + "@babel/types": ^7.18.7 + "@metamask/base-controller": ^2.0.0 + "@metamask/permission-controller": ^3.2.0 + "@metamask/providers": ^10.2.1 + "@metamask/snaps-registry": ^1.2.0 + "@metamask/snaps-ui": ^0.33.1-flask.1 + "@metamask/utils": ^5.0.0 + "@noble/hashes": ^1.1.3 + "@scure/base": ^1.1.1 + cron-parser: ^4.5.0 + eth-rpc-errors: ^4.0.3 + fast-deep-equal: ^3.1.3 + fast-json-stable-stringify: ^2.1.0 + rfdc: ^1.3.0 + semver: ^7.3.7 + ses: ^0.18.1 + superstruct: ^1.0.3 + validate-npm-package-name: ^5.0.0 + checksum: 15a87d290423a31f3113aa65cb7076e5220f5697e4614eb9f4b7bf1e4b55e83a6545f05311d34555e05e111e94309f75154ae889eb593beef71010e9dae18c12 + languageName: node + linkType: hard + +"@metamask/snaps-utils@npm:^0.32.2": version: 0.32.2 resolution: "@metamask/snaps-utils@npm:0.32.2" dependencies: @@ -4530,18 +4636,6 @@ __metadata: languageName: node linkType: hard -"@metamask/utils@npm:^4.0.0": - version: 4.0.0 - resolution: "@metamask/utils@npm:4.0.0" - dependencies: - "@types/debug": ^4.1.7 - debug: ^4.3.4 - semver: ^7.3.8 - superstruct: ^1.0.3 - checksum: 6d4edca78fe1f66504ed5e5ca021a67f4b4e0893e86484c746b87039c2161c39d3b8bd8e4b9235ddfd023b2d76dd54210af94ec5550e27bc4ad9c0d7d5f3f231 - languageName: node - linkType: hard - "@metamask/utils@npm:^5.0.0, @metamask/utils@npm:^5.0.1": version: 5.0.1 resolution: "@metamask/utils@npm:5.0.1" @@ -17738,7 +17832,7 @@ __metadata: languageName: node linkType: hard -"gensync@npm:^1.0.0-beta.1, gensync@npm:^1.0.0-beta.2": +"gensync@npm:^1.0.0-beta.2": version: 1.0.0-beta.2 resolution: "gensync@npm:1.0.0-beta.2" checksum: a7437e58c6be12aa6c90f7730eac7fa9833dc78872b4ad2963d2031b00a3367a93f98aec75f9aaac7220848e4026d67a8655e870b24f20a543d103c0d65952ec @@ -24058,6 +24152,7 @@ __metadata: "@metamask/assets-controllers": ^6.0.0 "@metamask/auto-changelog": ^2.1.0 "@metamask/base-controller": ^2.0.0 + "@metamask/browser-passworder": ^4.1.0 "@metamask/contract-metadata": ^2.3.1 "@metamask/controller-utils": ^3.3.0 "@metamask/design-tokens": ^1.9.0 @@ -24084,25 +24179,25 @@ __metadata: "@metamask/metamask-eth-abis": ^3.0.0 "@metamask/notification-controller": ^2.0.0 "@metamask/obs-store": ^8.1.0 - "@metamask/permission-controller": ^3.1.0 + "@metamask/permission-controller": ^3.2.0 "@metamask/phishing-controller": ^3.0.0 "@metamask/phishing-warning": ^2.1.0 "@metamask/post-message-stream": ^6.0.0 "@metamask/providers": ^10.2.1 "@metamask/rate-limit-controller": ^2.0.0 "@metamask/rpc-methods": ^0.32.2 - "@metamask/rpc-methods-flask": "npm:@metamask/rpc-methods@0.32.2" + "@metamask/rpc-methods-flask": "npm:@metamask/rpc-methods@0.33.1-flask.1" "@metamask/safe-event-emitter": ^2.0.0 "@metamask/scure-bip39": ^2.0.3 "@metamask/signature-controller": ^2.0.0 "@metamask/slip44": ^3.0.0 "@metamask/smart-transactions-controller": ^3.1.0 "@metamask/snaps-controllers": ^0.32.2 - "@metamask/snaps-controllers-flask": "npm:@metamask/snaps-controllers@0.32.2" + "@metamask/snaps-controllers-flask": "npm:@metamask/snaps-controllers@0.33.1-flask.1" "@metamask/snaps-ui": ^0.32.2 - "@metamask/snaps-ui-flask": "npm:@metamask/snaps-ui@0.32.2" + "@metamask/snaps-ui-flask": "npm:@metamask/snaps-ui@0.33.1-flask.1" "@metamask/snaps-utils": ^0.32.2 - "@metamask/snaps-utils-flask": "npm:@metamask/snaps-utils@0.32.2" + "@metamask/snaps-utils-flask": "npm:@metamask/snaps-utils@0.33.1-flask.1" "@metamask/subject-metadata-controller": ^2.0.0 "@metamask/swappable-obj-proxy": ^2.1.0 "@metamask/test-dapp": ^6.0.0 @@ -29797,7 +29892,7 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.1.4, resolve@npm:^1.1.5, resolve@npm:^1.1.6, resolve@npm:^1.1.7, resolve@npm:^1.10.0, resolve@npm:^1.10.1, resolve@npm:^1.11.1, resolve@npm:^1.12.0, resolve@npm:^1.14.2, resolve@npm:^1.15.1, resolve@npm:^1.17.0, resolve@npm:^1.18.1, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.21.0, resolve@npm:^1.22.0, resolve@npm:^1.22.1, resolve@npm:^1.3.2, resolve@npm:^1.4.0": +"resolve@npm:^1.1.4, resolve@npm:^1.1.5, resolve@npm:^1.1.6, resolve@npm:^1.1.7, resolve@npm:^1.10.0, resolve@npm:^1.10.1, resolve@npm:^1.11.1, resolve@npm:^1.12.0, resolve@npm:^1.14.2, resolve@npm:^1.15.1, resolve@npm:^1.17.0, resolve@npm:^1.18.1, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.21.0, resolve@npm:^1.22.0, resolve@npm:^1.22.1, resolve@npm:^1.4.0": version: 1.22.3 resolution: "resolve@npm:1.22.3" dependencies: @@ -29830,8 +29925,7 @@ __metadata: languageName: node linkType: hard -? "resolve@patch:resolve@^1.1.4#~builtin, resolve@patch:resolve@^1.1.5#~builtin, resolve@patch:resolve@^1.1.6#~builtin, resolve@patch:resolve@^1.1.7#~builtin, resolve@patch:resolve@^1.10.0#~builtin, resolve@patch:resolve@^1.10.1#~builtin, resolve@patch:resolve@^1.11.1#~builtin, resolve@patch:resolve@^1.12.0#~builtin, resolve@patch:resolve@^1.14.2#~builtin, resolve@patch:resolve@^1.15.1#~builtin, resolve@patch:resolve@^1.17.0#~builtin, resolve@patch:resolve@^1.18.1#~builtin, resolve@patch:resolve@^1.19.0#~builtin, resolve@patch:resolve@^1.20.0#~builtin, resolve@patch:resolve@^1.21.0#~builtin, resolve@patch:resolve@^1.22.0#~builtin, resolve@patch:resolve@^1.22.1#~builtin, resolve@patch:resolve@^1.3.2#~builtin, resolve@patch:resolve@^1.4.0#~builtin" -: +"resolve@patch:resolve@^1.1.4#~builtin, resolve@patch:resolve@^1.1.5#~builtin, resolve@patch:resolve@^1.1.6#~builtin, resolve@patch:resolve@^1.1.7#~builtin, resolve@patch:resolve@^1.10.0#~builtin, resolve@patch:resolve@^1.10.1#~builtin, resolve@patch:resolve@^1.11.1#~builtin, resolve@patch:resolve@^1.12.0#~builtin, resolve@patch:resolve@^1.14.2#~builtin, resolve@patch:resolve@^1.15.1#~builtin, resolve@patch:resolve@^1.17.0#~builtin, resolve@patch:resolve@^1.18.1#~builtin, resolve@patch:resolve@^1.19.0#~builtin, resolve@patch:resolve@^1.20.0#~builtin, resolve@patch:resolve@^1.21.0#~builtin, resolve@patch:resolve@^1.22.0#~builtin, resolve@patch:resolve@^1.22.1#~builtin, resolve@patch:resolve@^1.4.0#~builtin": version: 1.22.3 resolution: "resolve@patch:resolve@npm%3A1.22.3#~builtin::version=1.22.3&hash=07638b" dependencies: @@ -30507,7 +30601,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:2 || 3 || 4 || 5, semver@npm:^5.0.3, semver@npm:^5.1.0, semver@npm:^5.3.0, semver@npm:^5.4.1, semver@npm:^5.5.0, semver@npm:^5.6.0": +"semver@npm:2 || 3 || 4 || 5, semver@npm:^5.0.3, semver@npm:^5.1.0, semver@npm:^5.3.0, semver@npm:^5.5.0, semver@npm:^5.6.0": version: 5.7.1 resolution: "semver@npm:5.7.1" bin: @@ -31196,7 +31290,7 @@ __metadata: languageName: node linkType: hard -"source-map@npm:^0.5.0, source-map@npm:^0.5.1, source-map@npm:^0.5.6, source-map@npm:^0.5.7, source-map@npm:~0.5.3": +"source-map@npm:^0.5.1, source-map@npm:^0.5.6, source-map@npm:^0.5.7, source-map@npm:~0.5.3": version: 0.5.7 resolution: "source-map@npm:0.5.7" checksum: 5dc2043b93d2f194142c7f38f74a24670cd7a0063acdaf4bf01d2964b402257ae843c2a8fa822ad5b71013b5fcafa55af7421383da919752f22ff488bc553f4d From 8a8c8fad1075c2ec40686a387b889d6374fe7f7c Mon Sep 17 00:00:00 2001 From: Harsh Shukla <125105825+PrgrmrHarshShukla@users.noreply.github.com> Date: Mon, 15 May 2023 23:20:49 +0530 Subject: [PATCH 7/8] Part of ##18714 Replace deprecated design system typography consts with enums in: confirm-page-container.component.js (#19065) --- .../confirm-page-container.component.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/components/app/confirm-page-container/confirm-page-container.component.js b/ui/components/app/confirm-page-container/confirm-page-container.component.js index 5ae5819eaf31..b4fb0e5d1c7d 100644 --- a/ui/components/app/confirm-page-container/confirm-page-container.component.js +++ b/ui/components/app/confirm-page-container/confirm-page-container.component.js @@ -25,7 +25,7 @@ import { INSUFFICIENT_FUNDS_ERROR_KEY } from '../../../helpers/constants/error-k import { Text } from '../../component-library'; import { TextVariant, - TEXT_ALIGN, + TextAlign, } from '../../../helpers/constants/design-system'; import NetworkAccountBalanceHeader from '../network-account-balance-header/network-account-balance-header'; @@ -247,7 +247,7 @@ const ConfirmPageContainer = (props) => { isBuyableChain ? ( {t('insufficientCurrencyBuyOrDeposit', [ @@ -278,7 +278,7 @@ const ConfirmPageContainer = (props) => { ) : ( {t('insufficientCurrencyDeposit', [ From c9f420234fe56158011fb9f4938db6ae765a18e3 Mon Sep 17 00:00:00 2001 From: Dhruv <79097544+dhruvv173@users.noreply.github.com> Date: Mon, 15 May 2023 23:26:20 +0530 Subject: [PATCH 8/8] Changes to gas-items-details (#19075) --- .../gas-details-item-title.js | 16 ++++++++-------- .../gas-details-item-title.stories.js | 10 ++++++++++ .../gas-details-item/gas-details-item.stories.js | 10 ++++++++++ 3 files changed, 28 insertions(+), 8 deletions(-) create mode 100644 ui/components/app/gas-details-item/gas-details-item-title/gas-details-item-title.stories.js create mode 100644 ui/components/app/gas-details-item/gas-details-item.stories.js diff --git a/ui/components/app/gas-details-item/gas-details-item-title/gas-details-item-title.js b/ui/components/app/gas-details-item/gas-details-item-title/gas-details-item-title.js index 00a25e36e704..9e7c267dd483 100644 --- a/ui/components/app/gas-details-item/gas-details-item-title/gas-details-item-title.js +++ b/ui/components/app/gas-details-item/gas-details-item-title/gas-details-item-title.js @@ -1,12 +1,12 @@ import React from 'react'; import { useSelector } from 'react-redux'; -import { TypographyVariant } from '../../../../helpers/constants/design-system'; +import { TextVariant } from '../../../../helpers/constants/design-system'; import { useI18nContext } from '../../../../hooks/useI18nContext'; import { getIsMainnet } from '../../../../selectors'; import Box from '../../../ui/box'; import InfoTooltip from '../../../ui/info-tooltip/info-tooltip'; -import Typography from '../../../ui/typography/typography'; +import { Text } from '../../../component-library'; const GasDetailsItemTitle = () => { const t = useI18nContext(); @@ -21,15 +21,15 @@ const GasDetailsItemTitle = () => { - + {t('transactionDetailGasTooltipIntro', [ isMainnet ? t('networkNameEthereum') : '', ])} - - + + {t('transactionDetailGasTooltipExplanation')} - - + + { > {t('transactionDetailGasTooltipConversion')} - + } position="bottom" diff --git a/ui/components/app/gas-details-item/gas-details-item-title/gas-details-item-title.stories.js b/ui/components/app/gas-details-item/gas-details-item-title/gas-details-item-title.stories.js new file mode 100644 index 000000000000..33978b4074ac --- /dev/null +++ b/ui/components/app/gas-details-item/gas-details-item-title/gas-details-item-title.stories.js @@ -0,0 +1,10 @@ +import React from 'react'; +import GasDetailsItemTitle from '.'; + +export default { + title: 'Components/App/GasDetailsItemTitle', +}; + +export const DefaultStory = () => ; + +DefaultStory.storyName = 'Default'; diff --git a/ui/components/app/gas-details-item/gas-details-item.stories.js b/ui/components/app/gas-details-item/gas-details-item.stories.js new file mode 100644 index 000000000000..f676c77860f0 --- /dev/null +++ b/ui/components/app/gas-details-item/gas-details-item.stories.js @@ -0,0 +1,10 @@ +import React from 'react'; +import GasDetailsItem from '.'; + +export default { + title: 'Components/App/GasDetailsItem', +}; + +export const DefaultStory = () => ; + +DefaultStory.storyName = 'Default';