1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-23 02:10:12 +01:00
metamask-extension/app/scripts/controllers/network/network-controller.test.ts
Mark Stacey d1cea85f33
Rename provider to providerConfig (#18907)
* Rename `provider` to `providerConfig`

The network controller `provider` state has been renamed to
 `providerConfig`. This better reflects what this state is, and makes
this controller more closely aligned with the core network controller.

All references to the provider configuration have been updated to
prefer `providerConfig` over `provider`, to make the distinction clear
between a provider and provider config.

Closes #18902

* Add migration
2023-05-02 13:23:20 -02:30

8579 lines
292 KiB
TypeScript

import { inspect, isDeepStrictEqual, promisify } from 'util';
import assert from 'assert';
import { get } from 'lodash';
import { v4 } from 'uuid';
import nock from 'nock';
import { ControllerMessenger } from '@metamask/base-controller';
import { SafeEventEmitterProvider } from '@metamask/eth-json-rpc-provider';
import { when, resetAllWhenMocks } from 'jest-when';
import { ethErrors } from 'eth-rpc-errors';
import { NETWORK_TYPES } from '../../../../shared/constants/network';
import { MetaMetricsNetworkEventSource } from '../../../../shared/constants/metametrics';
import {
NetworkController,
NetworkControllerEvent,
NetworkControllerEventType,
NetworkControllerOptions,
NetworkControllerState,
} from './network-controller';
import {
createNetworkClient,
NetworkClientType,
} from './create-network-client';
import { FakeBlockTracker } from './test/fake-block-tracker';
import { FakeProvider, FakeProviderStub } from './test/fake-provider';
jest.mock('./create-network-client');
jest.mock('uuid', () => {
const actual = jest.requireActual('uuid');
return {
...actual,
v4: jest.fn(),
};
});
/**
* A block header object that `eth_getBlockByNumber` can be mocked to return.
* Note that this type does not specify all of the properties present within the
* block header; within these tests, we are only interested in `number` and
* `baseFeePerGas`.
*/
type Block = {
number: string;
baseFeePerGas?: string;
};
const createNetworkClientMock = jest.mocked(createNetworkClient);
// const providerFromEngineMock = jest.mocked(providerFromEngine);
const uuidV4Mock = jest.mocked(v4);
/**
* A dummy block that matches the pre-EIP-1559 format (i.e. it doesn't have the
* `baseFeePerGas` property).
*/
const PRE_1559_BLOCK: Block = {
number: '0x42',
};
/**
* A dummy block that matches the pre-EIP-1559 format (i.e. it has the
* `baseFeePerGas` property).
*/
const POST_1559_BLOCK: Block = {
...PRE_1559_BLOCK,
baseFeePerGas: '0x63c498a46',
};
/**
* An alias for `POST_1559_BLOCK`, for tests that don't care about which kind of
* block they're looking for.
*/
const BLOCK: Block = POST_1559_BLOCK;
/**
* A dummy value for the `projectId` option that `createInfuraClient` needs.
* (Infura should not be hit during tests, but just in case, this should not
* refer to a real project ID.)
*/
const DEFAULT_INFURA_PROJECT_ID = 'fake-infura-project-id';
/**
* The networks that NetworkController recognizes as built-in Infura networks,
* along with information we expect to be true for those networks.
*/
const INFURA_NETWORKS = [
{
networkType: NETWORK_TYPES.MAINNET,
chainId: '0x1',
ticker: 'ETH',
blockExplorerUrl: 'https://etherscan.io',
},
{
networkType: NETWORK_TYPES.GOERLI,
chainId: '0x5',
ticker: 'GoerliETH',
blockExplorerUrl: 'https://goerli.etherscan.io',
},
{
networkType: NETWORK_TYPES.SEPOLIA,
chainId: '0xaa36a7',
ticker: 'SepoliaETH',
blockExplorerUrl: 'https://sepolia.etherscan.io',
},
];
/**
* A response object for a successful request to `eth_getBlockByNumber`. It is
* assumed that the block number here is insignificant to the test.
*/
const SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE = {
result: BLOCK,
};
/**
* A response object for a successful request to `net_version`. It is assumed
* that the network ID here is insignificant to the test.
*/
const SUCCESSFUL_NET_VERSION_RESPONSE = {
result: '42',
};
/**
* A response object for a request that has been geoblocked by Infura.
*/
const BLOCKED_INFURA_JSON_RPC_ERROR = ethErrors.rpc.internal(
JSON.stringify({ error: 'countryBlocked' }),
);
/**
* A response object for a unsuccessful request to any RPC method. It is assumed
* that the error here is insignificant to the test.
*/
const GENERIC_JSON_RPC_ERROR = ethErrors.rpc.internal(
JSON.stringify({ error: 'oops' }),
);
describe('NetworkController', () => {
beforeEach(() => {
// Disable all requests, even those to localhost
nock.disableNetConnect();
});
afterEach(() => {
nock.enableNetConnect('localhost');
nock.cleanAll();
resetAllWhenMocks();
});
describe('constructor', () => {
const invalidInfuraProjectIds = [undefined, null, {}, 1];
invalidInfuraProjectIds.forEach((invalidProjectId) => {
it(`throws if an invalid Infura ID of "${inspect(
invalidProjectId,
)}" is provided`, () => {
expect(
// @ts-expect-error We are intentionally passing bad input.
() => new NetworkController({ infuraProjectId: invalidProjectId }),
).toThrow('Invalid Infura project ID');
});
});
it('accepts initial state', async () => {
await withController(
{
state: {
providerConfig: {
type: 'rpc',
rpcUrl: 'http://example-custom-rpc.metamask.io',
chainId: '0x9999' as const,
nickname: 'Test initial state',
},
networkDetails: {
EIPS: {
1559: false,
},
},
},
},
({ controller }) => {
expect(controller.store.getState()).toMatchInlineSnapshot(`
{
"networkConfigurations": {},
"networkDetails": {
"EIPS": {
"1559": false,
},
},
"networkId": null,
"networkStatus": "unknown",
"providerConfig": {
"chainId": "0x9999",
"nickname": "Test initial state",
"rpcUrl": "http://example-custom-rpc.metamask.io",
"type": "rpc",
},
}
`);
},
);
});
it('sets default state without initial state', async () => {
await withController(({ controller }) => {
expect(controller.store.getState()).toMatchInlineSnapshot(`
{
"networkConfigurations": {},
"networkDetails": {
"EIPS": {
"1559": undefined,
},
},
"networkId": null,
"networkStatus": "unknown",
"providerConfig": {
"chainId": "0x539",
"nickname": "Localhost 8545",
"rpcUrl": "http://localhost:8545",
"ticker": "ETH",
"type": "rpc",
},
}
`);
});
});
});
describe('destroy', () => {
it('does not throw if called before the provider is initialized', async () => {
await withController(async ({ controller }) => {
expect(await controller.destroy()).toBeUndefined();
});
});
it('stops the block tracker for the currently selected network as long as the provider has been initialized', async () => {
await withController(async ({ controller }) => {
const fakeProvider = buildFakeProvider();
const fakeNetworkClient = buildFakeClient(fakeProvider);
mockCreateNetworkClient().mockReturnValue(fakeNetworkClient);
await controller.initializeProvider();
const { blockTracker } = controller.getProviderAndBlockTracker();
assert(blockTracker, 'Block tracker is somehow unset');
// The block tracker starts running after a listener is attached
blockTracker.addListener('latest', () => {
// do nothing
});
expect(blockTracker.isRunning()).toBeTruthy();
await controller.destroy();
expect(blockTracker.isRunning()).toBe(false);
});
});
});
describe('initializeProvider', () => {
it('throws if the provider configuration is invalid', async () => {
const invalidProviderConfig = {};
await withController(
/* @ts-expect-error We're intentionally passing bad input. */
{
state: {
providerConfig: invalidProviderConfig,
},
},
async ({ controller }) => {
await expect(async () => {
await controller.initializeProvider();
}).rejects.toThrow(
'NetworkController - #configureProvider - unknown type "undefined"',
);
},
);
});
for (const { networkType } of INFURA_NETWORKS) {
describe(`when the type in the provider configuration is "${networkType}"`, () => {
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: '0x9999999',
},
},
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.initializeProvider();
const { provider } = controller.getProviderAndBlockTracker();
assert(provider, 'Provider is somehow unset');
const promisifiedSendAsync = promisify(provider.sendAsync).bind(
provider,
);
const response = await promisifiedSendAsync({
id: '1',
jsonrpc: '2.0',
method: 'test',
});
expect(response.result).toBe('test response');
},
);
});
it('emits 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();
const fakeNetworkClient = buildFakeClient(fakeProvider);
mockCreateNetworkClient().mockReturnValue(fakeNetworkClient);
const infuraIsUnblocked = await waitForPublishedEvents({
messenger,
eventType: NetworkControllerEventType.InfuraIsUnblocked,
operation: async () => {
await controller.initializeProvider();
},
});
expect(infuraIsUnblocked).toBeTruthy();
},
);
});
it('determines the status of the network, storing it in 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',
},
},
},
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);
expect(controller.store.getState().networkStatus).toBe('unknown');
await controller.initializeProvider();
expect(controller.store.getState().networkStatus).toBe(
'available',
);
},
);
});
it('determines whether the network supports EIP-1559 and stores the result in state without overwriting other state in the networkDetails store', 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.initializeProvider();
expect(controller.store.getState().networkDetails).toStrictEqual({
EIPS: {
1559: true,
},
other: 'details',
});
},
);
});
});
}
describe(`when the type in the provider configuration is "rpc"`, () => {
it('initializes a provider pointed to the given RPC URL whose chain ID matches the configured chain ID', async () => {
await withController(
{
state: {
providerConfig: {
type: 'rpc',
chainId: '0x1337',
rpcUrl: 'https://mock-rpc-url',
ticker: 'TEST',
},
networkConfigurations: {
testNetworkConfigurationId: {
rpcUrl: 'https://mock-rpc-url',
chainId: '0x1337',
ticker: 'TEST',
id: 'testNetworkConfigurationId',
},
},
},
},
async ({ controller }) => {
const fakeProvider = buildFakeProvider([
{
request: {
method: 'test',
},
response: {
result: 'test response',
},
},
]);
const fakeNetworkClient = buildFakeClient(fakeProvider);
mockCreateNetworkClient()
.calledWith({
chainId: '0x1337',
rpcUrl: 'https://mock-rpc-url',
type: NetworkClientType.Custom,
})
.mockReturnValue(fakeNetworkClient);
mockCreateNetworkClient().mockReturnValue(fakeNetworkClient);
await controller.initializeProvider();
const { provider } = controller.getProviderAndBlockTracker();
assert(provider, 'Provider is somehow unset');
const promisifiedSendAsync = promisify(provider.sendAsync).bind(
provider,
);
const response = await promisifiedSendAsync({
id: '1',
jsonrpc: '2.0',
method: 'test',
});
expect(response.result).toBe('test response');
},
);
});
it('emits infuraIsUnblocked', async () => {
await withController(
{
state: {
providerConfig: {
type: 'rpc',
rpcUrl: 'https://mock-rpc-url',
chainId: '0xtest',
ticker: 'TEST',
},
networkConfigurations: {
testNetworkConfigurationId: {
rpcUrl: 'https://mock-rpc-url',
chainId: '0xtest',
ticker: 'TEST',
id: 'testNetworkConfigurationId',
},
},
},
},
async ({ controller, messenger }) => {
const fakeProvider = buildFakeProvider();
const fakeNetworkClient = buildFakeClient(fakeProvider);
mockCreateNetworkClient().mockReturnValue(fakeNetworkClient);
const infuraIsUnblocked = await waitForPublishedEvents({
messenger,
eventType: NetworkControllerEventType.InfuraIsUnblocked,
operation: async () => {
await controller.initializeProvider();
},
});
expect(infuraIsUnblocked).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',
},
},
},
},
async ({ controller, messenger }) => {
const fakeProvider = buildFakeProvider();
const fakeNetworkClient = buildFakeClient(fakeProvider);
mockCreateNetworkClient().mockReturnValue(fakeNetworkClient);
const promiseForNoInfuraIsBlockedEvents = waitForPublishedEvents({
messenger,
eventType: NetworkControllerEventType.InfuraIsBlocked,
count: 0,
operation: async () => {
await controller.initializeProvider();
},
});
expect(await promiseForNoInfuraIsBlockedEvents).toBeTruthy();
},
);
});
it('determines the status of the network, storing it in state', 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: 'testNetworkConfigurationId1',
},
},
},
},
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);
expect(controller.store.getState().networkStatus).toBe('unknown');
await controller.initializeProvider();
expect(controller.store.getState().networkStatus).toBe('available');
},
);
});
it('determines whether the network supports EIP-1559, storing it in state', 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',
},
},
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();
expect(controller.store.getState().networkDetails).toStrictEqual({
EIPS: {
1559: true,
},
other: 'details',
});
},
);
});
});
});
describe('getProviderAndBlockTracker', () => {
it('returns objects that proxy to the provider and block tracker as long as the provider has been initialized', async () => {
await withController(async ({ controller }) => {
const fakeProvider = buildFakeProvider();
const fakeNetworkClient = buildFakeClient(fakeProvider);
mockCreateNetworkClient().mockReturnValue(fakeNetworkClient);
await controller.initializeProvider();
const { provider, blockTracker } =
controller.getProviderAndBlockTracker();
expect(provider).toHaveProperty('sendAsync');
expect(blockTracker).toHaveProperty('checkForLatestBlock');
});
});
it("returns null for both the provider and block tracker if the provider hasn't been initialized yet", async () => {
await withController(async ({ controller }) => {
const { provider, blockTracker } =
controller.getProviderAndBlockTracker();
expect(provider).toBeNull();
expect(blockTracker).toBeNull();
});
});
for (const { networkType } of INFURA_NETWORKS) {
describe(`when the type in the provider configuration is changed to "${networkType}"`, () => {
it(`returns a provider object that was pointed to another network before the switch and is pointed to "${networkType}" afterward`, async () => {
await withController(
{
state: {
providerConfig: {
type: 'rpc',
rpcUrl: 'https://mock-rpc-url',
chainId: '0x1337',
},
},
infuraProjectId: 'some-infura-project-id',
},
async ({ controller }) => {
const fakeProviders = [
buildFakeProvider([
{
request: {
method: 'test',
},
response: {
result: 'test response 1',
},
},
]),
buildFakeProvider([
{
request: {
method: 'test',
},
response: {
result: 'test response 2',
},
},
]),
];
const fakeNetworkClients = [
buildFakeClient(fakeProviders[0]),
buildFakeClient(fakeProviders[1]),
];
mockCreateNetworkClient()
.calledWith({
chainId: '0x1337',
rpcUrl: 'https://mock-rpc-url',
type: NetworkClientType.Custom,
})
.mockReturnValue(fakeNetworkClients[0])
.calledWith({
network: networkType,
infuraProjectId: 'some-infura-project-id',
type: NetworkClientType.Infura,
})
.mockReturnValue(fakeNetworkClients[1]);
await controller.initializeProvider();
const { provider } = controller.getProviderAndBlockTracker();
assert(provider, 'Provider is somehow unset');
const promisifiedSendAsync1 = promisify(provider.sendAsync).bind(
provider,
);
const response1 = await promisifiedSendAsync1({
id: '1',
jsonrpc: '2.0',
method: 'test',
});
expect(response1.result).toBe('test response 1');
await controller.setProviderType(networkType);
const promisifiedSendAsync2 = promisify(provider.sendAsync).bind(
provider,
);
const response2 = await promisifiedSendAsync2({
id: '2',
jsonrpc: '2.0',
method: 'test',
});
expect(response2.result).toBe('test response 2');
},
);
});
});
}
describe('when the type in the provider configuration is changed to "rpc"', () => {
it('returns a provider object that was pointed to another network before the switch and is pointed to the new network', async () => {
await withController(
{
state: {
providerConfig: {
type: 'goerli',
// NOTE: This doesn't need to match the logical chain ID of
// the network selected, it just needs to exist
chainId: '0x9999999',
},
networkConfigurations: {
testNetworkConfigurationId: {
rpcUrl: 'https://mock-rpc-url',
chainId: '0x1337',
ticker: 'ABC',
id: 'testNetworkConfigurationId',
},
},
},
infuraProjectId: 'some-infura-project-id',
},
async ({ controller }) => {
const fakeProviders = [
buildFakeProvider([
{
request: {
method: 'test',
},
response: {
result: 'test response 1',
},
},
]),
buildFakeProvider([
{
request: {
method: 'test',
},
response: {
result: 'test response 2',
},
},
]),
];
const fakeNetworkClients = [
buildFakeClient(fakeProviders[0]),
buildFakeClient(fakeProviders[1]),
];
mockCreateNetworkClient()
.calledWith({
network: NETWORK_TYPES.GOERLI,
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 controller.initializeProvider();
const { provider } = controller.getProviderAndBlockTracker();
assert(provider, 'Provider is somehow unset');
const promisifiedSendAsync1 = promisify(provider.sendAsync).bind(
provider,
);
const response1 = await promisifiedSendAsync1({
id: '1',
jsonrpc: '2.0',
method: 'test',
});
expect(response1.result).toBe('test response 1');
await controller.setActiveNetwork('testNetworkConfigurationId');
const promisifiedSendAsync2 = promisify(provider.sendAsync).bind(
provider,
);
const response2 = await promisifiedSendAsync2({
id: '2',
jsonrpc: '2.0',
method: 'test',
});
expect(response2.result).toBe('test response 2');
},
);
});
});
});
describe('getEIP1559Compatibility', () => {
describe('when the latest block has a baseFeePerGas property', () => {
it('stores the fact that the network supports EIP-1559', async () => {
await withController(
{
state: {
networkDetails: {
EIPS: {},
},
},
},
async ({ controller }) => {
const fakeProvider = buildFakeProvider([
{
request: {
method: 'eth_getBlockByNumber',
},
response: {
result: POST_1559_BLOCK,
},
},
]);
const fakeNetworkClient = buildFakeClient(fakeProvider);
mockCreateNetworkClient().mockReturnValue(fakeNetworkClient);
await controller.initializeProvider();
await controller.getEIP1559Compatibility();
expect(controller.store.getState().networkDetails.EIPS[1559]).toBe(
true,
);
},
);
});
it('returns true', async () => {
await withController(async ({ controller }) => {
const fakeProvider = buildFakeProvider([
{
request: {
method: 'eth_getBlockByNumber',
},
response: {
result: POST_1559_BLOCK,
},
},
]);
const fakeNetworkClient = buildFakeClient(fakeProvider);
mockCreateNetworkClient().mockReturnValue(fakeNetworkClient);
await controller.initializeProvider();
const supportsEIP1559 = await controller.getEIP1559Compatibility();
expect(supportsEIP1559).toBeTruthy();
});
});
});
describe('when the latest block does not have a baseFeePerGas property', () => {
it('stores the fact that the network does not support EIP-1559', async () => {
await withController(
{
state: {
networkDetails: {
EIPS: {},
},
},
},
async ({ controller }) => {
const fakeProvider = buildFakeProvider([
{
request: {
method: 'eth_getBlockByNumber',
},
response: {
result: PRE_1559_BLOCK,
},
},
]);
const fakeNetworkClient = buildFakeClient(fakeProvider);
mockCreateNetworkClient().mockReturnValue(fakeNetworkClient);
await controller.initializeProvider();
await controller.getEIP1559Compatibility();
expect(controller.store.getState().networkDetails.EIPS[1559]).toBe(
false,
);
},
);
});
it('returns false', async () => {
await withController(async ({ controller }) => {
const fakeProvider = buildFakeProvider([
{
request: {
method: 'eth_getBlockByNumber',
},
response: {
result: PRE_1559_BLOCK,
},
},
]);
const fakeNetworkClient = buildFakeClient(fakeProvider);
mockCreateNetworkClient().mockReturnValue(fakeNetworkClient);
await controller.initializeProvider();
const supportsEIP1559 = await controller.getEIP1559Compatibility();
expect(supportsEIP1559).toBe(false);
});
});
});
describe('when the request for the latest block responds with null', () => {
it('persists false to state as whether the network supports EIP-1559', async () => {
await withController(
{
state: {
networkDetails: {
EIPS: {},
},
},
},
async ({ controller }) => {
const fakeProvider = buildFakeProvider([
{
request: {
method: 'eth_getBlockByNumber',
},
response: {
result: null,
},
},
]);
const fakeNetworkClient = buildFakeClient(fakeProvider);
mockCreateNetworkClient().mockReturnValue(fakeNetworkClient);
await controller.initializeProvider();
await controller.getEIP1559Compatibility();
expect(controller.store.getState().networkDetails.EIPS[1559]).toBe(
false,
);
},
);
});
it('returns false', async () => {
await withController(async ({ controller }) => {
const fakeProvider = buildFakeProvider([
{
request: {
method: 'eth_getBlockByNumber',
},
response: {
result: null,
},
},
]);
const fakeNetworkClient = buildFakeClient(fakeProvider);
mockCreateNetworkClient().mockReturnValue(fakeNetworkClient);
await controller.initializeProvider();
const supportsEIP1559 = await controller.getEIP1559Compatibility();
expect(supportsEIP1559).toBe(false);
});
});
});
it('does not make multiple requests to eth_getBlockByNumber when called multiple times and the request to eth_getBlockByNumber succeeded the first time', async () => {
await withController(async ({ controller }) => {
const fakeProvider = buildFakeProvider([
{
request: {
method: 'eth_getBlockByNumber',
},
response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE,
},
]);
const fakeNetworkClient = buildFakeClient(fakeProvider);
mockCreateNetworkClient().mockReturnValue(fakeNetworkClient);
await withoutCallingGetEIP1559Compatibility({
controller,
operation: async () => {
await controller.initializeProvider();
},
});
await controller.getEIP1559Compatibility();
await controller.getEIP1559Compatibility();
expect(
fakeProvider.calledStubs.filter(
(stub) => stub.request.method === 'eth_getBlockByNumber',
),
).toHaveLength(1);
});
});
});
describe('lookupNetwork', () => {
describe('if the provider has not been initialized', () => {
it('does not update state in any way', async () => {
const providerConfig = {
type: NETWORK_TYPES.RPC,
rpcUrl: 'http://example-custom-rpc.metamask.io',
chainId: '0x9999' as const,
nickname: 'Test initial state',
};
const initialState = {
providerConfig,
networkDetails: {
EIPS: {
1559: true,
},
},
};
await withController(
{
state: initialState,
},
async ({ controller }) => {
const fakeProvider = buildFakeProvider();
const fakeNetworkClient = buildFakeClient(fakeProvider);
mockCreateNetworkClient().mockReturnValue(fakeNetworkClient);
const stateAfterConstruction = controller.store.getState();
await controller.lookupNetwork();
expect(controller.store.getState()).toStrictEqual(
stateAfterConstruction,
);
},
);
});
it('does not emit infuraIsUnblocked', async () => {
await withController(async ({ controller, messenger }) => {
const fakeProvider = buildFakeProvider();
const fakeNetworkClient = buildFakeClient(fakeProvider);
mockCreateNetworkClient().mockReturnValue(fakeNetworkClient);
const promiseForNoInfuraIsUnblockedEvents = waitForPublishedEvents({
messenger,
eventType: NetworkControllerEventType.InfuraIsUnblocked,
count: 0,
operation: async () => {
await controller.lookupNetwork();
},
});
expect(await promiseForNoInfuraIsUnblockedEvents).toBeTruthy();
});
});
it('does not emit infuraIsBlocked', async () => {
await withController(async ({ controller, messenger }) => {
const fakeProvider = buildFakeProvider();
const fakeNetworkClient = buildFakeClient(fakeProvider);
mockCreateNetworkClient().mockReturnValue(fakeNetworkClient);
const promiseForNoInfuraIsBlockedEvents = waitForPublishedEvents({
messenger,
eventType: NetworkControllerEventType.InfuraIsBlocked,
count: 0,
operation: async () => {
await controller.lookupNetwork();
},
});
expect(await promiseForNoInfuraIsBlockedEvents).toBeTruthy();
});
});
});
describe('if the provider has initialized, but the current network has no chainId', () => {
it('does not update state in any way', async () => {
await withController(
/* @ts-expect-error We are intentionally not including a chainId in the provider config. */
{
state: {
providerConfig: {
type: 'rpc',
rpcUrl: 'http://example-custom-rpc.metamask.io',
},
networkDetails: {
EIPS: {
1559: true,
},
},
},
},
async ({ controller }) => {
const fakeProvider = buildFakeProvider();
const fakeNetworkClient = buildFakeClient(fakeProvider);
mockCreateNetworkClient().mockReturnValue(fakeNetworkClient);
await controller.initializeProvider();
const stateAfterInitialization = controller.store.getState();
await controller.lookupNetwork();
expect(controller.store.getState()).toStrictEqual(
stateAfterInitialization,
);
},
);
});
it('does not emit infuraIsUnblocked', async () => {
await withController(
/* @ts-expect-error We are intentionally not including a chainId in the provider config. */
{
state: {
providerConfig: {
type: 'rpc',
rpcUrl: 'http://example-custom-rpc.metamask.io',
},
},
},
async ({ controller, messenger }) => {
const fakeProvider = buildFakeProvider();
const fakeNetworkClient = buildFakeClient(fakeProvider);
mockCreateNetworkClient().mockReturnValue(fakeNetworkClient);
await controller.initializeProvider();
const promiseForNoInfuraIsUnblockedEvents = waitForPublishedEvents({
messenger,
eventType: NetworkControllerEventType.InfuraIsUnblocked,
count: 0,
operation: async () => {
await controller.lookupNetwork();
},
});
expect(await promiseForNoInfuraIsUnblockedEvents).toBeTruthy();
},
);
});
it('does not emit infuraIsBlocked', async () => {
await withController(
/* @ts-expect-error We are intentionally not including a chainId in the provider config. */
{
state: {
providerConfig: {
type: 'rpc',
rpcUrl: 'http://example-custom-rpc.metamask.io',
},
},
},
async ({ controller, messenger }) => {
const fakeProvider = buildFakeProvider();
const fakeNetworkClient = buildFakeClient(fakeProvider);
mockCreateNetworkClient().mockReturnValue(fakeNetworkClient);
await controller.initializeProvider();
const promiseForNoInfuraIsBlockedEvents = waitForPublishedEvents({
messenger,
eventType: NetworkControllerEventType.InfuraIsBlocked,
count: 0,
operation: async () => {
await controller.lookupNetwork();
},
});
expect(await promiseForNoInfuraIsBlockedEvents).toBeTruthy();
},
);
});
});
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 () => {
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: '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();
},
});
expect(controller.store.getState().networkStatus).toBe(
'unknown',
);
await waitForStateChanges({
controller,
propertyPath: ['networkStatus'],
operation: async () => {
await controller.lookupNetwork();
},
});
expect(controller.store.getState().networkStatus).toBe(
'available',
);
},
);
});
it('stores the ID of the 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: '0x9999999',
},
},
},
async ({ controller }) => {
const fakeProvider = buildFakeProvider([
{
request: {
method: 'net_version',
},
response: {
result: '1',
},
},
]);
const fakeNetworkClient = buildFakeClient(fakeProvider);
mockCreateNetworkClient().mockReturnValue(fakeNetworkClient);
await withoutCallingLookupNetwork({
controller,
operation: async () => {
await controller.initializeProvider();
},
});
expect(controller.store.getState().networkId).toBeNull();
await waitForStateChanges({
controller,
propertyPath: ['networkId'],
operation: async () => {
await controller.lookupNetwork();
},
});
expect(controller.store.getState().networkId).toBe('1');
},
);
});
it('stores the fact that the network supports EIP-1559 when baseFeePerGas is in the block header', 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: {},
},
},
},
async ({ controller }) => {
const fakeProvider = buildFakeProvider([
{
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();
},
});
expect(
controller.store.getState().networkDetails.EIPS[1559],
).toBeTruthy();
},
);
});
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: 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: {},
},
},
},
async ({ controller }) => {
const fakeProvider = buildFakeProvider([
{
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: 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,
},
},
]);
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();
},
});
expect(infuraIsUnblocked).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',
},
},
},
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: ['networkStatus'],
operation: async () => {
await controller.lookupNetwork();
},
});
expect(controller.store.getState().networkStatus).toBe(
'blocked',
);
},
);
});
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',
},
},
},
async ({ controller }) => {
const fakeProvider = 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: '2',
},
},
{
request: {
method: 'eth_getBlockByNumber',
},
error: BLOCKED_INFURA_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 () => {
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',
},
},
},
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();
},
);
});
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',
},
},
},
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();
},
);
});
});
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',
},
},
},
async ({ controller }) => {
const fakeProvider = 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: '2',
},
},
{
request: {
method: 'eth_getBlockByNumber',
},
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',
},
},
{
request: {
method: 'eth_getBlockByNumber',
},
response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE,
},
{
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: ['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('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',
},
},
},
async ({ controller }) => {
const fakeProvider = 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: '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',
},
},
},
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: GENERIC_JSON_RPC_ERROR,
},
]);
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('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',
},
},
},
async ({ controller, messenger }) => {
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();
},
});
const promiseForNoInfuraIsUnblockedEvents =
waitForPublishedEvents({
messenger,
eventType: NetworkControllerEventType.InfuraIsUnblocked,
count: 0,
operation: async () => {
await controller.lookupNetwork();
},
});
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',
},
networkConfigurations: {
testNetworkConfigurationId: {
id: 'testNetworkConfigurationId',
rpcUrl: 'https://mock-rpc-url',
chainId: '0x1337',
ticker: 'ABC',
},
},
},
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.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(
'available',
);
await waitForStateChanges({
controller,
propertyPath: ['networkStatus'],
operation: async () => {
await controller.lookupNetwork();
},
});
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',
},
networkConfigurations: {
testNetworkConfigurationId: {
id: 'testNetworkConfigurationId',
rpcUrl: 'https://mock-rpc-url',
chainId: '0x1337',
ticker: 'ABC',
},
},
},
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();
},
});
await waitForStateChanges({
controller,
propertyPath: ['networkId'],
operation: async () => {
await controller.lookupNetwork();
},
});
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: networkType,
// NOTE: This doesn't need to match the logical chain ID of
// 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 fakeProviders = [
buildFakeProvider([
{
request: {
method: 'eth_getBlockByNumber',
},
response: {
result: POST_1559_BLOCK,
},
beforeCompleting: async () => {
await controller.setActiveNetwork(
'testNetworkConfigurationId',
);
},
},
]),
buildFakeProvider([
{
request: {
method: 'eth_getBlockByNumber',
},
response: {
result: PRE_1559_BLOCK,
},
},
]),
];
const fakeNetworkClients = [
buildFakeClient(fakeProviders[0]),
buildFakeClient(fakeProviders[1]),
];
mockCreateNetworkClient()
.calledWith({
network: networkType,
infuraProjectId: 'some-infura-project-id',
type: NetworkClientType.Infura,
})
.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();
},
});
expect(
controller.store.getState().networkDetails,
).toStrictEqual({
EIPS: {
1559: false,
},
});
},
);
});
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,
);
},
},
]),
buildFakeProvider([
{
request: {
method: 'eth_getBlockByNumber',
},
error: BLOCKED_INFURA_JSON_RPC_ERROR,
},
]),
];
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',
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({
controller,
operation: async () => {
await controller.initializeProvider();
},
});
expect(controller.store.getState().networkStatus).toBe('unknown');
await waitForStateChanges({
controller,
propertyPath: ['networkStatus'],
operation: async () => {
await controller.lookupNetwork();
},
});
expect(controller.store.getState().networkStatus).toBe(
'available',
);
},
);
});
it('stores the ID of the network', async () => {
await withController(
{
state: {
providerConfig: {
type: 'rpc',
rpcUrl: 'https://mock-rpc-url',
chainId: '0x1337',
},
},
},
async ({ controller }) => {
const fakeProvider = buildFakeProvider([
{
request: {
method: 'net_version',
},
response: {
result: '42',
},
},
{
request: {
method: 'eth_getBlockByNumber',
},
response: {
result: POST_1559_BLOCK,
},
},
]);
const fakeNetworkClient = buildFakeClient(fakeProvider);
mockCreateNetworkClient().mockReturnValue(fakeNetworkClient);
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();
},
});
expect(controller.store.getState().networkId).toBe('42');
},
);
});
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',
},
},
},
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();
},
});
expect(
controller.store.getState().networkDetails.EIPS[1559],
).toBeTruthy();
},
);
});
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',
},
},
},
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',
},
},
},
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();
},
});
const infuraIsUnblocked = await waitForPublishedEvents({
messenger,
eventType: NetworkControllerEventType.InfuraIsUnblocked,
operation: async () => {
await controller.lookupNetwork();
},
});
expect(infuraIsUnblocked).toBeTruthy();
},
);
});
});
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',
},
},
},
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(),
},
{
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().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: 'rpc',
rpcUrl: 'https://mock-rpc-url',
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,
},
{
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();
},
});
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('clears the ID of the network from state', async () => {
await withController(
{
state: {
providerConfig: {
type: 'rpc',
rpcUrl: 'https://mock-rpc-url',
chainId: '0x1337',
},
},
},
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',
},
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();
},
});
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',
},
},
},
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();
},
});
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',
},
},
},
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(
{
state: {
providerConfig: {
type: 'rpc',
rpcUrl: 'https://mock-rpc-url',
chainId: '0x1337',
},
},
},
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 infuraIsUnblocked = await waitForPublishedEvents({
messenger,
eventType: NetworkControllerEventType.InfuraIsUnblocked,
operation: async () => {
await controller.lookupNetwork();
},
});
expect(infuraIsUnblocked).toBeTruthy();
},
);
});
});
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',
},
},
},
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',
);
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: 'rpc',
rpcUrl: 'https://mock-rpc-url',
chainId: '0x1337',
},
},
},
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');
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: {},
other: 'details',
},
},
},
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',
});
await waitForStateChanges({
controller,
propertyPath: ['networkDetails'],
operation: async () => {
await controller.lookupNetwork();
},
});
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',
},
},
},
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 promiseForNoInfuraIsBlockedEvents = waitForPublishedEvents({
messenger,
eventType: NetworkControllerEventType.InfuraIsBlocked,
count: 0,
operation: async () => {
await controller.lookupNetwork();
},
});
expect(await promiseForNoInfuraIsBlockedEvents).toBeTruthy();
},
);
});
it('emits infuraIsUnblocked', async () => {
await withController(
{
state: {
providerConfig: {
type: 'rpc',
rpcUrl: 'https://mock-rpc-url',
chainId: '0x1337',
},
},
},
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 infuraIsUnblocked = await waitForPublishedEvents({
messenger,
eventType: NetworkControllerEventType.InfuraIsUnblocked,
operation: async () => {
await controller.lookupNetwork();
},
});
expect(infuraIsUnblocked).toBeTruthy();
},
);
});
});
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',
},
},
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,
},
{
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,
},
]),
];
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 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',
},
},
{
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',
},
response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE,
},
]),
buildFakeProvider([
{
request: {
method: 'net_version',
},
response: {
result: '2',
},
},
{
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();
},
});
expect(controller.store.getState().networkId).toBe('1');
await waitForStateChanges({
controller,
propertyPath: ['networkId'],
operation: async () => {
await controller.lookupNetwork();
},
});
expect(controller.store.getState().networkId).toBe('2');
},
);
});
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',
},
},
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 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: ['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().networkDetails).toStrictEqual({
EIPS: {
1559: false,
},
});
},
);
});
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',
},
},
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({
messenger,
eventType: NetworkControllerEventType.InfuraIsBlocked,
});
await controller.lookupNetwork();
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: {},
},
},
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,
},
{
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();
},
});
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',
},
},
{
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',
},
},
{
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();
},
});
expect(controller.store.getState().networkId).toBe('1');
await waitForStateChanges({
controller,
propertyPath: ['networkId'],
operation: async () => {
await controller.lookupNetwork();
},
});
expect(controller.store.getState().networkId).toBe('2');
},
);
});
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',
},
},
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 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: ['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().networkDetails).toStrictEqual({
EIPS: {
1559: false,
},
});
},
);
});
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',
},
},
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({
messenger,
eventType: NetworkControllerEventType.InfuraIsBlocked,
});
await controller.lookupNetwork();
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',
},
},
},
},
async ({ controller }) => {
const fakeProvider = buildFakeProvider();
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',
),
);
},
);
});
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',
},
},
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 }) => {
const fakeProvider = buildFakeProvider();
const fakeNetworkClient = buildFakeClient(fakeProvider);
mockCreateNetworkClient()
.calledWith({
rpcUrl: 'https://mock-rpc-url-2',
chainId: '0x222',
type: NetworkClientType.Custom,
})
.mockReturnValue(fakeNetworkClient);
await controller.setActiveNetwork('testNetworkConfiguration');
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',
},
});
},
);
});
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, messenger }) => {
const fakeProviders = [
buildFakeProvider([
{
request: {
method: 'net_version',
},
response: SUCCESSFUL_NET_VERSION_RESPONSE,
},
]),
buildFakeProvider([
{
request: {
method: 'net_version',
},
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(
initialNetworkStatus,
);
},
});
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-1',
chainId: '0x111',
ticker: 'TEST1',
},
networkConfigurations: {
testNetworkConfiguration: {
id: 'testNetworkConfiguration',
rpcUrl: 'https://mock-rpc-url-2',
chainId: '0x222',
ticker: 'TEST2',
},
},
},
},
async ({ controller }) => {
const fakeProviders = [
buildFakeProvider([
{
request: {
method: 'net_version',
},
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 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',
},
},
networkDetails: {
EIPS: {},
other: 'details',
},
},
},
async ({ controller }) => {
const fakeProviders = [
buildFakeProvider([
{
request: {
method: 'eth_getBlockByNumber',
},
response: {
result: POST_1559_BLOCK,
},
},
]),
buildFakeProvider([
{
request: {
method: 'eth_getBlockByNumber',
},
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,
},
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('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 }) => {
const fakeProvider = buildFakeProvider([
{
request: {
method: 'test',
},
response: {
result: 'test response',
},
},
]);
const fakeNetworkClient = buildFakeClient(fakeProvider);
mockCreateNetworkClient()
.calledWith({
rpcUrl: 'https://mock-rpc-url',
chainId: '0x1337',
type: NetworkClientType.Custom,
})
.mockReturnValue(fakeNetworkClient);
await controller.setActiveNetwork('testNetworkConfiguration');
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',
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);
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);
const infuraIsUnblocked = await waitForPublishedEvents({
messenger,
eventType: NetworkControllerEventType.InfuraIsUnblocked,
operation: async () => {
await controller.setActiveNetwork('testNetworkConfiguration');
},
});
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([
{
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()
.calledWith({
rpcUrl: 'https://mock-rpc-url',
chainId: '0x1337',
type: NetworkClientType.Custom,
})
.mockReturnValue(fakeNetworkClient);
await controller.setActiveNetwork('testNetworkConfiguration');
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',
},
networkConfigurations: {
testNetworkConfigurationId: {
id: 'testNetworkConfigurationId',
rpcUrl: 'https://mock-rpc-url',
chainId: '0x1337',
ticker: 'TEST',
},
},
},
},
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 () => {
await withController(
{
state: {
providerConfig: {
type: 'rpc',
rpcUrl: 'https://mock-rpc-url',
chainId: '0x1337',
nickname: 'test-chain',
ticker: 'TEST',
rpcPrefs: {
blockExplorerUrl: 'https://test-block-explorer.com',
},
},
},
},
async ({ controller }) => {
const fakeProvider = buildFakeProvider();
const fakeNetworkClient = buildFakeClient(fakeProvider);
mockCreateNetworkClient().mockReturnValue(fakeNetworkClient);
controller.setProviderType(networkType);
expect(controller.store.getState().providerConfig).toStrictEqual({
type: networkType,
rpcUrl: undefined,
chainId,
ticker,
nickname: undefined,
rpcPrefs: { blockExplorerUrl },
});
},
);
});
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',
},
},
infuraProjectId: 'some-infura-project-id',
},
async ({ controller }) => {
const fakeProviders = [
buildFakeProvider([
{
request: {
method: 'net_version',
},
response: SUCCESSFUL_NET_VERSION_RESPONSE,
},
]),
buildFakeProvider(),
];
const fakeNetworkClients = [
buildFakeClient(fakeProviders[0]),
buildFakeClient(fakeProviders[1]),
];
mockCreateNetworkClient()
.calledWith({
rpcUrl: 'https://mock-rpc-url',
chainId: '0x1337',
type: NetworkClientType.Custom,
})
.mockReturnValue(fakeNetworkClients[0])
.calledWith({
network: networkType,
infuraProjectId: 'some-infura-project-id',
type: NetworkClientType.Infura,
})
.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 the state
// partway through the operation
controller.setProviderType(networkType);
},
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: 'rpc',
rpcUrl: 'https://mock-rpc-url',
chainId: '0x1337',
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([
{
request: {
method: 'eth_getBlockByNumber',
},
response: {
result: PRE_1559_BLOCK,
},
},
]),
];
const fakeNetworkClients = [
buildFakeClient(fakeProviders[0]),
buildFakeClient(fakeProviders[1]),
];
mockCreateNetworkClient()
.calledWith({
rpcUrl: 'https://mock-rpc-url',
chainId: '0x1337',
type: NetworkClientType.Custom,
})
.mockReturnValue(fakeNetworkClients[0])
.calledWith({
network: networkType,
infuraProjectId: 'some-infura-project-id',
type: NetworkClientType.Infura,
})
.mockReturnValue(fakeNetworkClients[1]);
await controller.initializeProvider();
expect(controller.store.getState().networkDetails).toStrictEqual({
EIPS: {
1559: true,
},
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 the state
// partway through the operation
controller.setProviderType(networkType);
},
beforeResolving: () => {
expect(
controller.store.getState().networkDetails,
).toStrictEqual({
EIPS: {
1559: undefined,
},
});
},
});
},
);
});
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('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',
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: '0x1337',
type: NetworkClientType.Custom,
})
.mockReturnValue(fakeNetworkClients[0])
.calledWith({
network: networkType,
infuraProjectId: 'some-infura-project-id',
type: NetworkClientType.Infura,
})
.mockReturnValue(fakeNetworkClients[1]);
await controller.initializeProvider();
const { provider: providerBefore } =
controller.getProviderAndBlockTracker();
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: true,
},
});
},
);
});
});
}
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: 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);
const networkWillChange = await waitForPublishedEvents({
messenger,
eventType: NetworkControllerEventType.NetworkWillChange,
operation: () => {
// Intentionally not awaited because we want to capture an
// event emitted partway throught this operation
controller.resetConnection();
},
});
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: '0x9999999',
},
},
},
async ({ controller }) => {
const fakeProvider = buildFakeProvider([
{
request: {
method: 'net_version',
},
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 capture a
// state change made 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: 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: 'eth_getBlockByNumber',
},
response: {
result: POST_1559_BLOCK,
},
},
]);
const fakeNetworkClient = buildFakeClient(fakeProvider);
mockCreateNetworkClient().mockReturnValue(fakeNetworkClient);
await controller.initializeProvider();
expect(controller.store.getState().networkDetails).toStrictEqual({
EIPS: {
1559: true,
},
});
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(`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',
},
},
},
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',
},
},
},
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);
},
);
});
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);
const networkDidChange = await waitForPublishedEvents({
messenger,
eventType: NetworkControllerEventType.NetworkDidChange,
operation: async () => {
await controller.resetConnection();
},
});
expect(networkDidChange).toBeTruthy();
},
);
});
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({
messenger,
eventType: NetworkControllerEventType.InfuraIsUnblocked,
count: 0,
});
const promiseForInfuraIsBlocked = waitForPublishedEvents({
messenger,
eventType: NetworkControllerEventType.InfuraIsBlocked,
});
await controller.resetConnection();
expect(await promiseForNoInfuraIsUnblockedEvents).toBeTruthy();
expect(await promiseForInfuraIsBlocked).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',
},
},
},
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.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',
rpcUrl: 'https://mock-rpc-url',
chainId: '0x1337',
},
},
},
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 }) => {
const fakeProvider = buildFakeProvider([
{
request: {
method: 'net_version',
},
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',
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();
expect(controller.store.getState().networkDetails).toStrictEqual({
EIPS: {
1559: true,
},
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 want to check state
// partway through the operation
controller.resetConnection();
},
});
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 () => {
await withController(
{
state: {
providerConfig: {
type: 'rpc',
rpcUrl: 'https://mock-rpc-url',
chainId: '0x1337',
},
},
},
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: 'rpc',
rpcUrl: 'https://mock-rpc-url',
chainId: '0x1337',
},
},
},
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);
},
);
});
it('emits networkDidChange', 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();
},
);
});
it('emits infuraIsUnblocked', 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 infuraIsUnblocked = await waitForPublishedEvents({
messenger,
eventType: NetworkControllerEventType.InfuraIsUnblocked,
operation: async () => {
await controller.resetConnection();
},
});
expect(infuraIsUnblocked).toBeTruthy();
},
);
});
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',
},
},
},
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.resetConnection();
expect(controller.store.getState().networkStatus).toBe('available');
},
);
});
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',
},
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('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: 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',
},
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',
},
});
},
);
});
it('emits networkWillChange', 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, 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,
},
});
},
});
},
});
},
);
});
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');
},
);
});
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: '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(
{
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 networkDidChange = await waitForPublishedEvents({
messenger,
eventType: NetworkControllerEventType.NetworkDidChange,
operation: async () => {
await controller.rollbackToPreviousProvider();
},
});
expect(networkDidChange).toBeTruthy();
},
});
},
);
});
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',
},
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();
},
});
expect(await promiseForNoInfuraIsUnblockedEvents).toBeTruthy();
expect(await promiseForInfuraIsBlocked).toBeTruthy();
},
);
});
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',
},
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',
);
await waitForStateChanges({
controller,
propertyPath: ['networkStatus'],
operation: async () => {
await controller.rollbackToPreviousProvider();
},
});
expect(controller.store.getState().networkStatus).toBe(
'available',
);
},
);
});
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',
},
},
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,
},
});
},
);
});
});
}
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',
},
},
},
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',
},
});
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 networkWillChange', async () => {
await withController(
{
state: {
providerConfig: {
type: 'rpc',
rpcUrl: 'https://mock-rpc-url',
chainId: '0x1337',
},
},
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();
},
});
expect(networkWillChange).toBeTruthy();
},
});
},
);
});
it('resets the network state to "unknown" before emitting networkDidChange', async () => {
await withController(
{
state: {
providerConfig: {
type: 'rpc',
rpcUrl: 'https://mock-rpc-url',
chainId: '0x1337',
},
},
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');
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: 'rpc',
rpcUrl: 'https://mock-rpc-url',
chainId: '0x1337',
},
},
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,
},
});
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 given RPC URL', async () => {
await withController(
{
state: {
providerConfig: {
type: 'rpc',
rpcUrl: 'https://mock-rpc-url',
chainId: '0x1337',
},
},
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');
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');
},
);
});
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',
},
},
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();
},
});
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',
},
},
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 networkDidChange = await waitForPublishedEvents({
messenger,
eventType: NetworkControllerEventType.NetworkDidChange,
operation: async () => {
await controller.rollbackToPreviousProvider();
},
});
expect(networkDidChange).toBeTruthy();
},
});
},
);
});
it('emits infuraIsUnblocked', async () => {
await withController(
{
state: {
providerConfig: {
type: 'rpc',
rpcUrl: 'https://mock-rpc-url',
chainId: '0x1337',
},
},
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 infuraIsUnblocked = await waitForPublishedEvents({
messenger,
eventType: NetworkControllerEventType.InfuraIsUnblocked,
operation: async () => {
await controller.rollbackToPreviousProvider();
},
});
expect(infuraIsUnblocked).toBeTruthy();
},
});
},
);
});
it('checks the status of the previous network again and updates state accordingly', async () => {
await withController(
{
state: {
providerConfig: {
type: 'rpc',
rpcUrl: 'https://mock-rpc-url',
chainId: '0x1337',
},
},
infuraProjectId: 'some-infura-project-id',
},
async ({ controller }) => {
const fakeProviders = [
buildFakeProvider([
{
request: {
method: 'net_version',
},
error: ethErrors.rpc.methodNotFound(),
},
]),
buildFakeProvider([
{
request: {
method: 'net_version',
},
response: SUCCESSFUL_NET_VERSION_RESPONSE,
},
{
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('checks whether the previous network supports EIP-1559 again and updates state accordingly', async () => {
await withController(
{
state: {
providerConfig: {
type: 'rpc',
rpcUrl: 'https://mock-rpc-url',
chainId: '0x1337',
},
networkDetails: {
EIPS: {},
other: 'details',
},
},
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({
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,
},
});
await waitForLookupNetworkToComplete({
controller,
operation: async () => {
await controller.rollbackToPreviousProvider();
},
});
expect(controller.store.getState().networkDetails).toStrictEqual({
EIPS: {
1559: true,
},
});
},
);
});
});
});
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,
},
),
).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 controller.upsertNetworkConfiguration(rpcUrlNetwork, {
referrer: 'https://test-dapp.com',
source: MetaMetricsNetworkEventSource.Dapp,
});
expect(
Object.values(controller.store.getState().networkConfigurations),
).toStrictEqual(
expect.arrayContaining([
{
...rpcUrlNetwork,
nickname: undefined,
rpcPrefs: undefined,
id: 'networkConfigurationId',
},
]),
);
},
);
});
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',
},
]),
);
},
);
});
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',
},
},
},
},
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([
{
rpcUrl: 'https://test-rpc-url',
ticker: 'ticker',
nickname: 'nickname',
rpcPrefs: { blockExplorerUrl: 'testchainscan.io' },
chainId: '0x1',
id: 'networkConfigurationId',
},
{ ...rpcUrlNetwork, id: 'networkConfigurationId2' },
]),
);
},
);
});
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',
},
},
},
},
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',
},
]);
},
);
});
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',
},
networkConfigurationId2: {
rpcUrl: 'https://test-rpc-url-2',
ticker: 'ticker-2',
nickname: 'nickname-2',
rpcPrefs: { blockExplorerUrl: 'testchainscan.io' },
chainId: '0x9999',
id: 'networkConfigurationId2',
},
},
},
},
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,
},
);
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('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',
},
},
},
},
async ({ controller }) => {
const rpcUrlNetwork = {
chainId: '0x1' as const,
rpcUrl: 'https://test-rpc-url',
ticker: 'test_ticker',
};
await controller.upsertNetworkConfiguration(rpcUrlNetwork, {
referrer: 'https://test-dapp.com',
source: MetaMetricsNetworkEventSource.Dapp,
});
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: '0xtest',
ticker: 'TEST',
id: 'testNetworkConfigurationId',
},
},
},
},
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,
});
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' },
};
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: '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' },
};
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',
);
},
);
});
});
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',
},
},
},
},
async ({ controller }) => {
controller.removeNetworkConfiguration(networkConfigurationId);
expect(
controller.store.getState().networkConfigurations,
).toStrictEqual({});
},
);
});
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`,
);
},
);
});
});
});
/**
* 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'));
});
}
/**
* Builds a controller messenger.
*
* @returns The controller messenger.
*/
function buildMessenger() {
return new ControllerMessenger<never, NetworkControllerEvent>();
}
/**
* Build a restricted controller messenger for the network controller.
*
* @param messenger - A controller messenger.
* @returns The network controller restricted messenger.
*/
function buildNetworkControllerMessenger(messenger = buildMessenger()) {
return messenger.getRestricted<
'NetworkController',
never,
NetworkControllerEventType
>({
name: 'NetworkController',
allowedEvents: [
NetworkControllerEventType.NetworkDidChange,
NetworkControllerEventType.NetworkWillChange,
NetworkControllerEventType.InfuraIsBlocked,
NetworkControllerEventType.InfuraIsUnblocked,
],
});
}
/**
* The callback that `withController` takes.
*/
type WithControllerCallback<ReturnValue> = ({
controller,
}: {
controller: NetworkController;
messenger: ControllerMessenger<never, NetworkControllerEvent>;
}) => Promise<ReturnValue> | ReturnValue;
/**
* A partial form of the options that `NetworkController` takes.
*/
type WithControllerOptions = Partial<NetworkControllerOptions>;
/**
* The arguments that `withController` takes.
*/
type WithControllerArgs<ReturnValue> =
| [WithControllerCallback<ReturnValue>]
| [
Omit<WithControllerOptions, 'messenger'>,
WithControllerCallback<ReturnValue>,
];
/**
* Builds a controller based on the given options, and calls the given function
* with that controller.
*
* @param args - Either a function, or an options bag + a function. The options
* bag is equivalent to the options that NetworkController takes (although
* `messenger` is filled in if not given); the function will be called with the
* built controller.
* @returns Whatever the callback returns.
*/
async function withController<ReturnValue>(
...args: WithControllerArgs<ReturnValue>
): Promise<ReturnValue> {
const [givenNetworkControllerOptions, fn] =
args.length === 2 ? args : [{}, args[0]];
const messenger = buildMessenger();
const restrictedMessenger = buildNetworkControllerMessenger(messenger);
const controller = new NetworkController({
infuraProjectId: DEFAULT_INFURA_PROJECT_ID,
messenger: restrictedMessenger,
trackMetaMetricsEvent: jest.fn(),
...givenNetworkControllerOptions,
});
try {
return await fn({ controller, messenger });
} finally {
await controller.destroy();
}
}
/**
* Builds an object that `createInfuraProvider` or `createJsonRpcClient` returns.
*
* @param provider - provider to use if you dont want the defaults
* @returns The object.
*/
function buildFakeClient(
provider: SafeEventEmitterProvider = buildFakeProvider(),
) {
return {
provider,
blockTracker: new FakeBlockTracker({ provider }),
};
}
/**
* Builds fake provider engine object that `createMetamaskProvider` returns,
* with canned responses optionally provided for certain RPC methods.
*
* @param stubs - The list of RPC methods you want to stub along with their
* responses.
* @returns The object.
*/
function buildFakeProvider(stubs: FakeProviderStub[] = []) {
const completeStubs = stubs.slice();
if (!stubs.some((stub) => stub.request.method === 'eth_getBlockByNumber')) {
completeStubs.unshift({
request: {
method: 'eth_getBlockByNumber',
params: ['latest', false],
},
response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE,
discardAfterMatching: false,
});
}
if (!stubs.some((stub) => stub.request.method === 'net_version')) {
completeStubs.unshift({
request: { method: 'net_version' },
response: SUCCESSFUL_NET_VERSION_RESPONSE,
discardAfterMatching: false,
});
}
return new FakeProvider({ stubs: completeStubs });
}
/**
* For each kind of way that the provider can be set, `lookupNetwork` is always
* called. This can cause difficulty when testing the behavior of
* `lookupNetwork` itself, as extra requests then have to be mocked.
* This function takes a function that presumably sets the provider,
* stubbing `lookupNetwork` before the function and releasing the stub
* afterward.
*
* @param args - The arguments.
* @param args.controller - The network controller.
* @param args.operation - The function that presumably involves
* `lookupNetwork`.
*/
async function withoutCallingLookupNetwork({
controller,
operation,
}: {
controller: NetworkController;
operation: () => void | Promise<void>;
}) {
const spy = jest
.spyOn(controller, 'lookupNetwork')
.mockResolvedValue(undefined);
await operation();
spy.mockRestore();
}
/**
* For each kind of way that the provider can be set, `getEIP1559Compatibility`
* is always called. This can cause difficulty when testing the behavior of
* `getEIP1559Compatibility` itself, as extra requests then have to be
* mocked. This function takes a function that presumably sets the provider,
* stubbing `getEIP1559Compatibility` before the function and releasing the stub
* afterward.
*
* @param args - The arguments.
* @param args.controller - The network controller.
* @param args.operation - The function that presumably involves
* `getEIP1559Compatibility`.
*/
async function withoutCallingGetEIP1559Compatibility({
controller,
operation,
}: {
controller: NetworkController;
operation: () => void | Promise<void>;
}) {
const spy = jest
.spyOn(controller, 'getEIP1559Compatibility')
.mockResolvedValue(false);
await operation();
spy.mockRestore();
}
/**
* Waits for changes to the primary observable store of a controller to occur
* before proceeding. May be called with a function, in which case waiting will
* occur after the function is called; or may be called standalone if you want
* to assert that no state changes occurred.
*
* @param args - The arguments.
* @param args.controller - The network controller.
* @param args.propertyPath - The path of the property you expect the state
* changes to concern.
* @param args.count - The number of events you expect to occur. If null, this
* function will wait until no events have occurred in `wait` number of
* milliseconds. Default: 1.
* @param args.duration - The amount of time in milliseconds to wait for the
* expected number of filtered state changes to occur before resolving the
* promise that this function returns (default: 150).
* @param args.operation - A function to run that will presumably produce the
* state changes in question.
* @param args.beforeResolving - In some tests, state updates happen so fast, we
* need to make an assertion immediately after the event in question occurs.
* However, if we wait until the promise this function returns resolves to do
* so, some other state update to the same
* property may have happened. This option allows you to make an assertion
* _before_ the promise resolves. This has the added benefit of allowing you to
* maintain the "arrange, act, assert" ordering in your test, meaning that you
* can still call the method that kicks off the event and then make the
* assertion afterward instead of the other way around.
* @returns A promise that resolves to an array of state objects (that is, the
* contents of the store) when the specified number of filtered state changes
* have occurred, or all of them if no number has been specified.
*/
async function waitForStateChanges({
controller,
propertyPath,
count: expectedInterestingStateCount = 1,
duration: timeBeforeAssumingNoMoreStateChanges = 150,
operation = () => {
// do nothing
},
beforeResolving = async () => {
// do nothing
},
}: {
controller: NetworkController;
propertyPath: string[];
count?: number | null;
duration?: number;
operation?: () => void | Promise<void>;
beforeResolving?: () => void | Promise<void>;
}) {
const initialState = { ...controller.store.getState() };
let isTimerRunning = false;
const promiseForStateChanges = new Promise((resolve, reject) => {
// We need to declare this variable first, then assign it later, so that
// ESLint won't complain that resetTimer is referring to this variable
// before it's declared. And we need to use let so that we can assign it
// below.
/* eslint-disable-next-line prefer-const */
let eventListener: (...args: any[]) => void;
let timer: NodeJS.Timeout | undefined;
const allStates: NetworkControllerState[] = [];
const interestingStates: NetworkControllerState[] = [];
const stopTimer = () => {
if (timer) {
clearTimeout(timer);
}
isTimerRunning = false;
};
async function end() {
stopTimer();
controller.store.unsubscribe(eventListener);
await beforeResolving();
const shouldEnd =
expectedInterestingStateCount === null
? interestingStates.length > 0
: interestingStates.length === expectedInterestingStateCount;
if (shouldEnd) {
resolve(interestingStates);
} else {
// Using a string instead of an Error leads to better backtraces.
/* eslint-disable-next-line prefer-promise-reject-errors */
const expectedInterestingStateCountFragment =
expectedInterestingStateCount === null
? 'any number of'
: expectedInterestingStateCount;
const propertyPathFragment =
propertyPath === undefined ? '' : ` on \`${propertyPath.join('.')}\``;
const actualInterestingStateCountFragment =
expectedInterestingStateCount === null
? 'none'
: interestingStates.length;
const primaryMessage = `Expected to receive ${expectedInterestingStateCountFragment} state change(s)${propertyPathFragment}, but received ${actualInterestingStateCountFragment} after ${timeBeforeAssumingNoMoreStateChanges}ms.`;
reject(
[
primaryMessage,
'Initial state:',
inspect(initialState, { depth: null }),
'All state changes (without filtering):',
inspect(allStates, { depth: null }),
'Filtered state changes:',
inspect(interestingStates, { depth: null }),
].join('\n\n'),
);
}
}
const resetTimer = () => {
stopTimer();
timer = setTimeout(() => {
if (isTimerRunning) {
end();
}
}, timeBeforeAssumingNoMoreStateChanges);
isTimerRunning = true;
};
eventListener = (newState) => {
const isInteresting = isStateChangeInteresting(
newState,
allStates.length > 0 ? allStates[allStates.length - 1] : initialState,
propertyPath,
);
allStates.push({ ...newState });
if (isInteresting) {
interestingStates.push(newState);
if (interestingStates.length === expectedInterestingStateCount) {
end();
} else {
resetTimer();
}
}
};
controller.store.subscribe(eventListener);
resetTimer();
});
await operation();
return await promiseForStateChanges;
}
/**
* Waits for controller events to be emitted before proceeding.
*
* @param args - The arguments to this function.
* @param args.messenger - The messenger suited for NetworkController.
* @param args.eventType - The type of NetworkController event you want to wait
* for.
* @param args.count - The number of events you expect to occur (default: 1).
* @param args.filter - A function used to discard events that are not of
* interest.
* @param args.wait - The amount of time in milliseconds to wait for the
* expected number of filtered events to occur before resolving the promise that
* this function returns (default: 150).
* @param args.operation - A function to run that will presumably produce the
* events in question.
* @param args.beforeResolving - In some tests, state updates happen so fast, we
* need to make an assertion immediately after the event in question occurs.
* However, if we wait until the promise this function returns resolves to do
* so, some other state update to the same
* property may have happened. This option allows you to make an assertion
* _before_ the promise resolves. This has the added benefit of allowing you to
* maintain the "arrange, act, assert" ordering in your test, meaning that you
* can still call the method that kicks off the event and then make the
* assertion afterward instead of the other way around.
* @returns A promise that resolves to the list of payloads for the set of
* events, optionally filtered, when a specific number of them have occurred.
*/
async function waitForPublishedEvents<E extends NetworkControllerEvent>({
messenger,
eventType,
count: expectedNumberOfEvents = 1,
filter: isEventPayloadInteresting = () => true,
wait: timeBeforeAssumingNoMoreEvents = 150,
operation = () => {
// do nothing
},
beforeResolving = async () => {
// do nothing
},
}: {
messenger: ControllerMessenger<never, NetworkControllerEvent>;
eventType: E['type'];
count?: number;
filter?: (payload: E['payload']) => boolean;
wait?: number;
operation?: () => void | Promise<void>;
beforeResolving?: () => void | Promise<void>;
}): Promise<E['payload'][]> {
const promiseForEventPayloads = new Promise<E['payload'][]>(
(resolve, reject) => {
let timer: NodeJS.Timeout | undefined;
const allEventPayloads: E['payload'][] = [];
const interestingEventPayloads: E['payload'][] = [];
let alreadyEnded = false;
// We're using `any` here because there seems to be some mismatch between
// the signature of `subscribe` and the way that we're using it. Try
// changing `any` to either `((...args: E['payload']) => void)` or
// `ExtractEventHandler<E, E['type']>` to see the issue.
const eventListener: any = (...payload: E['payload']) => {
allEventPayloads.push(payload);
if (isEventPayloadInteresting(payload)) {
interestingEventPayloads.push(payload);
if (interestingEventPayloads.length === expectedNumberOfEvents) {
stopTimer();
end();
} else {
resetTimer();
}
}
};
async function end() {
if (!alreadyEnded) {
alreadyEnded = true;
messenger.unsubscribe(eventType, eventListener);
await beforeResolving();
if (interestingEventPayloads.length === expectedNumberOfEvents) {
resolve(interestingEventPayloads);
} else {
// Using a string instead of an Error leads to better backtraces.
/* eslint-disable-next-line prefer-promise-reject-errors */
reject(
`Expected to receive ${expectedNumberOfEvents} ${eventType} event(s), but received ${
interestingEventPayloads.length
} after ${timeBeforeAssumingNoMoreEvents}ms.\n\nAll payloads:\n\n${inspect(
allEventPayloads,
{ depth: null },
)}`,
);
}
}
}
function stopTimer() {
if (timer) {
clearTimeout(timer);
}
}
function resetTimer() {
stopTimer();
timer = setTimeout(() => {
end();
}, timeBeforeAssumingNoMoreEvents);
}
messenger.subscribe(eventType, eventListener);
resetTimer();
},
);
if (operation) {
await operation();
}
return await promiseForEventPayloads;
}
/**
* `lookupNetwork` is a method in NetworkController which is called internally
* by a few methods. `lookupNetwork` is asynchronous as it makes network
* requests under the hood, but unfortunately, the method is not awaited after
* being called. Hence, if it is called during a test, even if the network
* requests are initiated within the test, they may complete after that test
* ends. This is a problem because it may cause Nock mocks set up in a later
* test to get used up prematurely, causing failures.
*
* To fix this, we need to wait for `lookupNetwork` to fully finish before
* continuing. Since the latest thing that happens in `lookupNetwork` is to
* update EIP-1559 compatibility in state, we can wait for the `networkDetails`
* state to get updated specifically. Unfortunately, we don't know how many
* times this will happen, so this function does incur some time when it's used.
* To speed up tests, you can pass `numberOfNetworkDetailsChanges`.
*
* @param args - The arguments.
* @param args.controller - The network controller.
* @param args.numberOfNetworkDetailsChanges - The number of times that
* `networkDetails` is expected to be updated.
* @param args.operation - The function that presumably involves
* `lookupNetwork`.
*/
async function waitForLookupNetworkToComplete({
controller,
numberOfNetworkDetailsChanges = null,
operation,
}: {
controller: NetworkController;
numberOfNetworkDetailsChanges?: number | null;
operation: () => void | Promise<void>;
}) {
await waitForStateChanges({
controller,
propertyPath: ['networkDetails'],
operation,
count: numberOfNetworkDetailsChanges,
});
}
/**
* Returns whether two places in different state objects have different values.
*
* @param currentState - The current state object.
* @param prevState - The previous state object.
* @param propertyPath - A property path within both objects.
* @returns True or false, depending on the result.
*/
function isStateChangeInteresting(
currentState: Record<PropertyKey, unknown>,
prevState: Record<PropertyKey, unknown>,
propertyPath: PropertyKey[],
): boolean {
return !isDeepStrictEqual(
get(currentState, propertyPath),
get(prevState, propertyPath),
);
}