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/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 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/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', 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/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/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', [ 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'; 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 (
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; } 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