1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-10-22 03:12:42 +02:00

Replace NetworkController w/ core version (#19486)

This commit fulfills a long-standing desire to get the extension using
the same network controller as mobile by removing NetworkController from
this repo and replacing it with NetworkController from the
`@metamask/network-controller` package.

The new version of NetworkController is different the old one in a few
ways:

- The new controller inherits from BaseControllerV2, so the `state`
  property is used to access the state instead of `store.getState()`.
  All references of the latter have been replaced with the former.
- As the new controller no longer has a `store` property, it cannot be
  subscribed to; the controller takes a messenger which can be
  subscribed to instead. There were various places within
  MetamaskController where the old way of subscribing has been replaced
  with the new way. In addition, DetectTokensController has been updated
  to take a messenger object so that it can listen for NetworkController
  state changes.
- The state of the new controller is not updatable from the outside.
  This affected BackupController, which dumps state from
  NetworkController (among other controllers), but also loads the same
  state into NetworkController on import. A method `loadBackup` has been
  added to NetworkController to facilitate this use case, and
  BackupController is now using this method instead of attempting to
  call `update` on NetworkController.
- The new controller does not have a `getCurrentChainId` method;
  instead, the chain ID can be read from the provider config in state.
  This affected MmiController. (MmiController was also updated to read
  custom networks from the new network controller instead of the
  preferences controller).
- The default network that the new controller is set to is always
  Mainnet (previously it could be either localhost or Goerli in test
  mode, depending on environment variables). This has been addressed
  by feeding the NetworkController initial state using the old logic, so
  this should not apply.
This commit is contained in:
Elliot Winkler 2023-06-22 11:46:09 -07:00 committed by GitHub
parent bd12ea733a
commit 89cec5335f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 688 additions and 14215 deletions

View File

@ -238,7 +238,6 @@ module.exports = {
excludedFiles: [
'app/scripts/controllers/app-state.test.js',
'app/scripts/controllers/mmi-controller.test.js',
'app/scripts/controllers/network/**/*.test.js',
'app/scripts/controllers/permissions/**/*.test.js',
'app/scripts/lib/**/*.test.js',
'app/scripts/migrations/*.test.js',
@ -267,9 +266,6 @@ module.exports = {
'**/__snapshots__/*.snap',
'app/scripts/controllers/app-state.test.js',
'app/scripts/controllers/mmi-controller.test.js',
'app/scripts/controllers/network/**/*.test.js',
'app/scripts/controllers/network/**/*.test.ts',
'app/scripts/controllers/network/provider-api-tests/*.ts',
'app/scripts/controllers/permissions/**/*.test.js',
'app/scripts/lib/**/*.test.js',
'app/scripts/migrations/*.test.js',

View File

@ -6,7 +6,6 @@ module.exports = {
'./app/scripts/migrations/*.test.js',
'./app/scripts/platforms/*.test.js',
'./app/scripts/controllers/app-state.test.js',
'./app/scripts/controllers/network/**/*.test.js',
'./app/scripts/controllers/permissions/**/*.test.js',
'./app/scripts/controllers/mmi-controller.test.js',
'./app/scripts/constants/error-utils.test.js',

View File

@ -462,7 +462,7 @@ export function setupController(
setupEnsIpfsResolver({
getCurrentChainId: () =>
controller.networkController.store.getState().providerConfig.chainId,
controller.networkController.state.providerConfig.chainId,
getIpfsGateway: controller.preferencesController.getIpfsGateway.bind(
controller.preferencesController,
),

View File

@ -31,7 +31,7 @@ export default class BackupController {
}
if (network) {
this.networkController.store.updateState(network);
this.networkController.loadBackup(network);
}
if (preferences || addressBook || network) {
@ -48,7 +48,7 @@ export default class BackupController {
addressBook: { ...this.addressBookController.state },
network: {
networkConfigurations:
this.networkController.store.getState().networkConfigurations,
this.networkController.state.networkConfigurations,
},
};

View File

@ -57,18 +57,15 @@ function getMockAddressBookController() {
}
function getMockNetworkController() {
const mcState = {
const state = {
networkConfigurations: {},
update: (store) => (mcState.store = store),
};
mcState.store = {
getState: sinon.stub().returns(mcState),
updateState: (store) => (mcState.store = store),
const loadBackup = ({ networkConfigurations }) => {
Object.assign(state, { networkConfigurations });
};
return mcState;
return { state, loadBackup };
}
const jsonData = JSON.stringify({
@ -174,28 +171,28 @@ describe('BackupController', function () {
it('should restore backup', async function () {
const backupController = getBackupController();
backupController.restoreUserData(jsonData);
await backupController.restoreUserData(jsonData);
// check networks backup
assert.equal(
backupController.networkController.store.networkConfigurations[
backupController.networkController.state.networkConfigurations[
'network-configuration-id-1'
].chainId,
'0x539',
);
assert.equal(
backupController.networkController.store.networkConfigurations[
backupController.networkController.state.networkConfigurations[
'network-configuration-id-2'
].chainId,
'0x38',
);
assert.equal(
backupController.networkController.store.networkConfigurations[
backupController.networkController.state.networkConfigurations[
'network-configuration-id-3'
].chainId,
'0x61',
);
assert.equal(
backupController.networkController.store.networkConfigurations[
backupController.networkController.state.networkConfigurations[
'network-configuration-id-4'
].chainId,
'0x89',

View File

@ -33,8 +33,10 @@ export default class DetectTokensController {
* @param config.tokensController
* @param config.assetsContractController
* @param config.trackMetaMetricsEvent
* @param config.messenger
*/
constructor({
messenger,
interval = DEFAULT_INTERVAL,
preferences,
network,
@ -44,6 +46,7 @@ export default class DetectTokensController {
assetsContractController = null,
trackMetaMetricsEvent,
} = {}) {
this.messenger = messenger;
this.assetsContractController = assetsContractController;
this.tokensController = tokensController;
this.preferences = preferences;
@ -59,7 +62,7 @@ export default class DetectTokensController {
});
this.hiddenTokens = this.tokensController?.state.ignoredTokens;
this.detectedTokens = this.tokensController?.state.detectedTokens;
this.chainId = this.getChainIdFromNetworkStore(network);
this.chainId = this.getChainIdFromNetworkStore();
this._trackMetaMetricsEvent = trackMetaMetricsEvent;
preferences?.store.subscribe(({ selectedAddress, useTokenDetection }) => {
@ -81,6 +84,13 @@ export default class DetectTokensController {
this.detectedTokens = detectedTokens;
},
);
messenger.subscribe('NetworkController:stateChange', () => {
if (this.chainId !== this.getChainIdFromNetworkStore()) {
const chainId = this.getChainIdFromNetworkStore();
this.chainId = chainId;
this.restartTokenDetection({ chainId: this.chainId });
}
});
}
/**
@ -93,7 +103,7 @@ export default class DetectTokensController {
async detectNewTokens({ selectedAddress, chainId } = {}) {
const addressAgainstWhichToDetect = selectedAddress ?? this.selectedAddress;
const chainIdAgainstWhichToDetect =
chainId ?? this.getChainIdFromNetworkStore(this._network);
chainId ?? this.getChainIdFromNetworkStore();
if (!this.isActive) {
return;
}
@ -208,8 +218,8 @@ export default class DetectTokensController {
this.interval = DEFAULT_INTERVAL;
}
getChainIdFromNetworkStore(network) {
return network?.store.getState().providerConfig.chainId;
getChainIdFromNetworkStore() {
return this.network?.state.providerConfig.chainId;
}
/* eslint-disable accessor-pairs */
@ -226,23 +236,6 @@ export default class DetectTokensController {
}, interval);
}
/**
* @type {object}
*/
set network(network) {
if (!network) {
return;
}
this._network = network;
this._network.store.subscribe(() => {
if (this.chainId !== this.getChainIdFromNetworkStore(network)) {
const chainId = this.getChainIdFromNetworkStore(network);
this.chainId = chainId;
this.restartTokenDetection({ chainId: this.chainId });
}
});
}
/**
* In setter when isUnlocked is updated to true, detectNewTokens and restart polling
*

View File

@ -10,12 +10,19 @@ import {
AssetsContractController,
} from '@metamask/assets-controllers';
import { toHex } from '@metamask/controller-utils';
import { NetworkController } from '@metamask/network-controller';
import { NETWORK_TYPES } from '../../../shared/constants/network';
import { toChecksumHexAddress } from '../../../shared/modules/hexstring-utils';
import DetectTokensController from './detect-tokens';
import { NetworkController } from './network';
import PreferencesController from './preferences';
function buildMessenger() {
return new ControllerMessenger().getRestricted({
name: 'DetectTokensController',
allowedEvents: ['NetworkController:stateChange'],
});
}
describe('DetectTokensController', function () {
let sandbox,
assetsContractController,
@ -230,23 +237,20 @@ describe('DetectTokensController', function () {
onPreferencesStateChange: preferences.store.subscribe.bind(
preferences.store,
),
onNetworkStateChange: (cb) =>
network.store.subscribe((networkState) => {
const modifiedNetworkState = {
...networkState,
providerConfig: {
...networkState.providerConfig,
},
};
return cb(modifiedNetworkState);
}),
onNetworkStateChange: networkControllerMessenger.subscribe.bind(
networkControllerMessenger,
'NetworkController:stateChange',
),
});
assetsContractController = new AssetsContractController({
onPreferencesStateChange: preferences.store.subscribe.bind(
preferences.store,
),
onNetworkStateChange: network.store.subscribe.bind(network.store),
onNetworkStateChange: networkControllerMessenger.subscribe.bind(
networkControllerMessenger,
'NetworkController:stateChange',
),
});
});
@ -257,7 +261,7 @@ describe('DetectTokensController', function () {
it('should poll on correct interval', async function () {
const stub = sinon.stub(global, 'setInterval');
new DetectTokensController({ interval: 1337 }); // eslint-disable-line no-new
new DetectTokensController({ messenger: buildMessenger(), interval: 1337 }); // eslint-disable-line no-new
assert.strictEqual(stub.getCall(0).args[1], 1337);
stub.restore();
});
@ -266,6 +270,7 @@ describe('DetectTokensController', function () {
const clock = sandbox.useFakeTimers();
await network.setProviderType(NETWORK_TYPES.MAINNET);
const controller = new DetectTokensController({
messenger: buildMessenger(),
preferences,
network,
keyringMemStore,
@ -302,6 +307,7 @@ describe('DetectTokensController', function () {
});
await tokenListController.start();
const controller = new DetectTokensController({
messenger: buildMessenger(),
preferences,
network,
keyringMemStore,
@ -325,6 +331,7 @@ describe('DetectTokensController', function () {
sandbox.useFakeTimers();
await network.setProviderType(NETWORK_TYPES.MAINNET);
const controller = new DetectTokensController({
messenger: buildMessenger(),
preferences,
network,
keyringMemStore,
@ -376,6 +383,7 @@ describe('DetectTokensController', function () {
sandbox.useFakeTimers();
await network.setProviderType(NETWORK_TYPES.MAINNET);
const controller = new DetectTokensController({
messenger: buildMessenger(),
preferences,
network,
keyringMemStore,
@ -434,6 +442,7 @@ describe('DetectTokensController', function () {
it('should trigger detect new tokens when change address', async function () {
sandbox.useFakeTimers();
const controller = new DetectTokensController({
messenger: buildMessenger(),
preferences,
network,
keyringMemStore,
@ -453,6 +462,7 @@ describe('DetectTokensController', function () {
it('should trigger detect new tokens when submit password', async function () {
sandbox.useFakeTimers();
const controller = new DetectTokensController({
messenger: buildMessenger(),
preferences,
network,
keyringMemStore,
@ -471,6 +481,7 @@ describe('DetectTokensController', function () {
const clock = sandbox.useFakeTimers();
await network.setProviderType(NETWORK_TYPES.MAINNET);
const controller = new DetectTokensController({
messenger: buildMessenger(),
preferences,
network,
keyringMemStore,
@ -492,6 +503,7 @@ describe('DetectTokensController', function () {
const clock = sandbox.useFakeTimers();
await network.setProviderType(NETWORK_TYPES.MAINNET);
const controller = new DetectTokensController({
messenger: buildMessenger(),
preferences,
network,
keyringMemStore,

View File

@ -9,11 +9,7 @@ import {
MetaMetricsUserTrait,
} from '../../../shared/constants/metametrics';
import waitUntilCalled from '../../../test/lib/wait-until-called';
import {
CHAIN_IDS,
CURRENCY_SYMBOLS,
NETWORK_TYPES,
} from '../../../shared/constants/network';
import { CHAIN_IDS, CURRENCY_SYMBOLS } from '../../../shared/constants/network';
import * as Utils from '../lib/util';
import MetaMetricsController from './metametrics';
@ -77,28 +73,6 @@ const DEFAULT_PAGE_PROPERTIES = {
...DEFAULT_SHARED_PROPERTIES,
};
function getMockNetworkController() {
let state = {
providerConfig: {
type: NETWORK_TYPES.GOERLI,
chainId: FAKE_CHAIN_ID,
},
network: 'loading',
};
const onNetworkDidChange = sinon.stub();
const updateState = (newState) => {
state = { ...state, ...newState };
onNetworkDidChange.getCall(0).args[0]();
};
return {
store: {
getState: () => state,
updateState,
},
onNetworkDidChange,
};
}
function getMockPreferencesStore({ currentLocale = LOCALE } = {}) {
let preferencesStore = {
currentLocale,
@ -142,15 +116,16 @@ function getMetaMetricsController({
participateInMetaMetrics = true,
metaMetricsId = TEST_META_METRICS_ID,
preferencesStore = getMockPreferencesStore(),
networkController = getMockNetworkController(),
getCurrentChainId = () => FAKE_CHAIN_ID,
onNetworkDidChange = () => {
// do nothing
},
segmentInstance,
} = {}) {
return new MetaMetricsController({
segment: segmentInstance || segment,
getCurrentChainId: () =>
networkController.store.getState().providerConfig.chainId,
onNetworkDidChange:
networkController.onNetworkDidChange.bind(networkController),
getCurrentChainId,
onNetworkDidChange,
preferencesStore,
version: '0.0.1',
environment: 'test',
@ -166,6 +141,7 @@ function getMetaMetricsController({
extension: MOCK_EXTENSION,
});
}
describe('MetaMetricsController', function () {
const now = new Date();
let clock;
@ -213,17 +189,20 @@ describe('MetaMetricsController', function () {
});
it('should update when network changes', function () {
const networkController = getMockNetworkController();
let chainId = '0x111';
let networkDidChangeListener;
const onNetworkDidChange = (listener) => {
networkDidChangeListener = listener;
};
const metaMetricsController = getMetaMetricsController({
networkController,
getCurrentChainId: () => chainId,
onNetworkDidChange,
});
networkController.store.updateState({
providerConfig: {
type: 'NEW_NETWORK',
chainId: '0xaab',
},
});
assert.strictEqual(metaMetricsController.chainId, '0xaab');
chainId = '0x222';
networkDidChangeListener();
assert.strictEqual(metaMetricsController.chainId, '0x222');
});
it('should update when preferences changes', function () {

View File

@ -549,22 +549,25 @@ export default class MMIController extends EventEmitter {
this.preferencesController.setSelectedAddress(address);
}
const selectedChainId = parseInt(
this.networkController.getCurrentChainId(),
this.networkController.state.providerConfig.chainId,
16,
);
if (selectedChainId !== chainId && chainId === 1) {
this.networkController.setProviderType('mainnet');
await this.networkController.setProviderType('mainnet');
} else if (selectedChainId !== chainId) {
const network = this.preferencesController
.getFrequentRpcListDetail()
.find((item) => parseInt(item.chainId, 16) === chainId);
this.networkController.setRpcTarget(
network.rpcUrl,
network.chainId,
network.ticker,
network.nickname,
);
const foundNetworkConfiguration = Object.values(
this.networkController.state.networkConfigurations,
).find((networkConfiguration) => {
return parseInt(networkConfiguration.chainId, 16) === chainId;
});
if (foundNetworkConfiguration !== undefined) {
await this.networkConfiguration.setActiveNetwork(
foundNetworkConfiguration.id,
);
}
}
getPermissionBackgroundApiMethods(
this.permissionController,
).addPermittedAccount(origin, address);

View File

@ -1,7 +0,0 @@
import { NetworkClientType } from './create-network-client';
import { testsForProviderType } from './provider-api-tests/shared-tests';
describe('createNetworkClient', () => {
testsForProviderType(NetworkClientType.Infura);
testsForProviderType(NetworkClientType.Custom);
});

View File

@ -1,191 +0,0 @@
import {
createAsyncMiddleware,
createScaffoldMiddleware,
JsonRpcEngine,
mergeMiddleware,
JsonRpcMiddleware,
} from 'json-rpc-engine';
import {
createBlockCacheMiddleware,
createBlockRefMiddleware,
createBlockRefRewriteMiddleware,
createBlockTrackerInspectorMiddleware,
createInflightCacheMiddleware,
createFetchMiddleware,
createRetryOnEmptyMiddleware,
} from '@metamask/eth-json-rpc-middleware';
import {
providerFromEngine,
providerFromMiddleware,
SafeEventEmitterProvider,
} from '@metamask/eth-json-rpc-provider';
import { createInfuraMiddleware } from '@metamask/eth-json-rpc-infura';
import type { Hex } from '@metamask/utils/dist';
import { PollingBlockTracker } from 'eth-block-tracker/dist';
import { SECOND } from '../../../../shared/constants/time';
import {
BUILT_IN_INFURA_NETWORKS,
BuiltInInfuraNetwork,
} from '../../../../shared/constants/network';
export enum NetworkClientType {
Custom = 'custom',
Infura = 'infura',
}
type CustomNetworkConfiguration = {
chainId: Hex;
rpcUrl: string;
type: NetworkClientType.Custom;
};
type InfuraNetworkConfiguration = {
network: BuiltInInfuraNetwork;
infuraProjectId: string;
type: NetworkClientType.Infura;
};
/**
* Create a JSON RPC network client for a specific network.
*
* @param networkConfig - The network configuration.
* @returns
*/
export function createNetworkClient(
networkConfig: CustomNetworkConfiguration | InfuraNetworkConfiguration,
): { provider: SafeEventEmitterProvider; blockTracker: PollingBlockTracker } {
const rpcApiMiddleware =
networkConfig.type === NetworkClientType.Infura
? createInfuraMiddleware({
network: networkConfig.network,
projectId: networkConfig.infuraProjectId,
maxAttempts: 5,
source: 'metamask',
})
: createFetchMiddleware({
btoa: global.btoa,
fetch: global.fetch,
rpcUrl: networkConfig.rpcUrl,
});
const rpcProvider = providerFromMiddleware(rpcApiMiddleware);
const blockTrackerOpts =
process.env.IN_TEST && networkConfig.type === 'custom'
? { pollingInterval: SECOND }
: {};
const blockTracker = new PollingBlockTracker({
...blockTrackerOpts,
provider: rpcProvider,
});
const networkMiddleware =
networkConfig.type === NetworkClientType.Infura
? createInfuraNetworkMiddleware({
blockTracker,
network: networkConfig.network,
rpcProvider,
rpcApiMiddleware,
})
: createCustomNetworkMiddleware({
blockTracker,
chainId: networkConfig.chainId,
rpcApiMiddleware,
});
const engine = new JsonRpcEngine();
engine.push(networkMiddleware);
const provider = providerFromEngine(engine);
return { provider, blockTracker };
}
function createInfuraNetworkMiddleware({
blockTracker,
network,
rpcProvider,
rpcApiMiddleware,
}: {
blockTracker: PollingBlockTracker;
network: BuiltInInfuraNetwork;
rpcProvider: SafeEventEmitterProvider;
rpcApiMiddleware: JsonRpcMiddleware<unknown, unknown>;
}) {
return mergeMiddleware([
createNetworkAndChainIdMiddleware({ network }),
createBlockCacheMiddleware({ blockTracker }),
createInflightCacheMiddleware(),
createBlockRefMiddleware({ blockTracker, provider: rpcProvider }),
createRetryOnEmptyMiddleware({ blockTracker, provider: rpcProvider }),
createBlockTrackerInspectorMiddleware({ blockTracker }),
rpcApiMiddleware,
]);
}
function createNetworkAndChainIdMiddleware({
network,
}: {
network: BuiltInInfuraNetwork;
}) {
if (!BUILT_IN_INFURA_NETWORKS[network]) {
throw new Error(`createInfuraClient - unknown network "${network}"`);
}
const { chainId, networkId } = BUILT_IN_INFURA_NETWORKS[network];
return createScaffoldMiddleware({
eth_chainId: chainId,
net_version: networkId,
});
}
const createChainIdMiddleware = (
chainId: string,
): JsonRpcMiddleware<unknown, unknown> => {
return (req, res, next, end) => {
if (req.method === 'eth_chainId') {
res.result = chainId;
return end();
}
return next();
};
};
function createCustomNetworkMiddleware({
blockTracker,
chainId,
rpcApiMiddleware,
}: {
blockTracker: PollingBlockTracker;
chainId: string;
rpcApiMiddleware: any;
}) {
const testMiddlewares = process.env.IN_TEST
? [createEstimateGasDelayTestMiddleware()]
: [];
return mergeMiddleware([
...testMiddlewares,
createChainIdMiddleware(chainId),
createBlockRefRewriteMiddleware({ blockTracker }),
createBlockCacheMiddleware({ blockTracker }),
createInflightCacheMiddleware(),
createBlockTrackerInspectorMiddleware({ blockTracker }),
rpcApiMiddleware,
]);
}
/**
* For use in tests only.
* Adds a delay to `eth_estimateGas` calls.
*/
function createEstimateGasDelayTestMiddleware() {
return createAsyncMiddleware(async (req, _, next) => {
if (req.method === 'eth_estimateGas') {
await new Promise((resolve) => setTimeout(resolve, SECOND * 2));
}
return next();
});
}

View File

@ -1 +0,0 @@
export * from './network-controller';

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,284 +0,0 @@
/* eslint-disable jest/require-top-level-describe, jest/no-export */
import {
ProviderType,
withMockedCommunications,
withNetworkClient,
} from './helpers';
type TestsForRpcMethodThatCheckForBlockHashInResponseOptions = {
providerType: ProviderType;
numberOfParameters: number;
};
/**
* Defines tests which exercise the behavior exhibited by an RPC method that
* use `blockHash` in the response data to determine whether the response is
* cacheable.
*
* @param method - The name of the RPC method under test.
* @param additionalArgs - Additional arguments.
* @param additionalArgs.numberOfParameters - The number of parameters supported
* by the method under test.
* @param additionalArgs.providerType - The type of provider being tested;
* either `infura` or `custom` (default: "infura").
*/
export function testsForRpcMethodsThatCheckForBlockHashInResponse(
method: string,
{
numberOfParameters,
providerType,
}: TestsForRpcMethodThatCheckForBlockHashInResponseOptions,
) {
if (providerType !== 'infura' && providerType !== 'custom') {
throw new Error(
`providerType must be either "infura" or "custom", was "${providerType}" instead`,
);
}
it('does not hit the RPC endpoint more than once for identical requests and it has a valid blockHash', async () => {
const requests = [{ method }, { method }];
const mockResult = { blockHash: '0x1' };
await withMockedCommunications({ providerType }, async (comms) => {
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request: requests[0],
response: { result: mockResult },
});
const results = await withNetworkClient(
{ providerType },
({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests),
);
expect(results).toStrictEqual([mockResult, mockResult]);
});
});
it('hits the RPC endpoint and does not reuse the result of a previous request if the latest block number was updated since', async () => {
const requests = [{ method }, { method }];
const mockResults = [{ blockHash: '0x100' }, { blockHash: '0x200' }];
await withMockedCommunications({ providerType }, async (comms) => {
// Note that we have to mock these requests in a specific order. The
// first block tracker request occurs because of the first RPC
// request. The second block tracker request, however, does not occur
// because of the second RPC request, but rather because we call
// `clock.runAll()` below.
comms.mockNextBlockTrackerRequest({ blockNumber: '0x1' });
comms.mockRpcCall({
request: requests[0],
response: { result: mockResults[0] },
});
comms.mockNextBlockTrackerRequest({ blockNumber: '0x2' });
comms.mockRpcCall({
request: requests[1],
response: { result: mockResults[1] },
});
const results = await withNetworkClient(
{ providerType },
async (client) => {
const firstResult = await client.makeRpcCall(requests[0]);
// Proceed to the next iteration of the block tracker so that a new
// block is fetched and the current block is updated.
client.clock.runAll();
const secondResult = await client.makeRpcCall(requests[1]);
return [firstResult, secondResult];
},
);
expect(results).toStrictEqual(mockResults);
});
});
it('does not reuse the result of a previous request if result.blockHash was null', async () => {
const requests = [{ method }, { method }];
const mockResults = [
{ blockHash: null, extra: 'some value' },
{ blockHash: '0x100', extra: 'some other value' },
];
await withMockedCommunications({ providerType }, async (comms) => {
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request: requests[0],
response: { result: mockResults[0] },
});
comms.mockRpcCall({
request: requests[1],
response: { result: mockResults[1] },
});
const results = await withNetworkClient(
{ providerType },
({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests),
);
expect(results).toStrictEqual(mockResults);
});
});
it('does not reuse the result of a previous request if result.blockHash was undefined', async () => {
const requests = [{ method }, { method }];
const mockResults = [
{ extra: 'some value' },
{ blockHash: '0x100', extra: 'some other value' },
];
await withMockedCommunications({ providerType }, async (comms) => {
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request: requests[0],
response: { result: mockResults[0] },
});
comms.mockRpcCall({
request: requests[1],
response: { result: mockResults[1] },
});
const results = await withNetworkClient(
{ providerType },
({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests),
);
expect(results).toStrictEqual(mockResults);
});
});
it('does not reuse the result of a previous request if result.blockHash was "0x0000000000000000000000000000000000000000000000000000000000000000"', async () => {
const requests = [{ method }, { method }];
const mockResults = [
{
blockHash:
'0x0000000000000000000000000000000000000000000000000000000000000000',
extra: 'some value',
},
{ blockHash: '0x100', extra: 'some other value' },
];
await withMockedCommunications({ providerType }, async (comms) => {
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request: requests[0],
response: { result: mockResults[0] },
});
comms.mockRpcCall({
request: requests[1],
response: { result: mockResults[1] },
});
const results = await withNetworkClient(
{ providerType },
({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests),
);
expect(results).toStrictEqual(mockResults);
});
});
for (const emptyValue of [null, undefined, '\u003cnil\u003e']) {
it(`does not retry an empty response of "${emptyValue}"`, async () => {
const request = { method };
const mockResult = emptyValue;
await withMockedCommunications({ providerType }, async (comms) => {
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
response: { result: mockResult },
});
const result = await withNetworkClient(
{ providerType },
({ makeRpcCall }) => makeRpcCall(request),
);
expect(result).toStrictEqual(mockResult);
});
});
it(`does not reuse the result of a previous request if it was "${emptyValue}"`, async () => {
const requests = [{ method }, { method }];
const mockResults = [emptyValue, { blockHash: '0x100' }];
await withMockedCommunications({ providerType }, async (comms) => {
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request: requests[0],
response: { result: mockResults[0] },
});
comms.mockRpcCall({
request: requests[1],
response: { result: mockResults[1] },
});
const results = await withNetworkClient(
{ providerType },
({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests),
);
expect(results).toStrictEqual(mockResults);
});
});
}
for (const paramIndex of [...Array(numberOfParameters).keys()]) {
it(`does not reuse the result of a previous request with a valid blockHash if parameter at index "${paramIndex}" differs`, async () => {
const firstMockParams = [
...new Array(numberOfParameters).fill('some value'),
];
const secondMockParams = firstMockParams.slice();
secondMockParams[paramIndex] = 'another value';
const requests = [
{
method,
params: firstMockParams,
},
{ method, params: secondMockParams },
];
const mockResults = [{ blockHash: '0x100' }, { blockHash: '0x200' }];
await withMockedCommunications({ providerType }, async (comms) => {
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request: requests[0],
response: { result: mockResults[0] },
});
comms.mockRpcCall({
request: requests[1],
response: { result: mockResults[1] },
});
const results = await withNetworkClient(
{ providerType },
({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests),
);
expect(results).toStrictEqual([mockResults[0], mockResults[1]]);
});
});
}
}

View File

@ -1,543 +0,0 @@
import nock, { Scope as NockScope } from 'nock';
import sinon from 'sinon';
import type { JSONRPCResponse } from '@json-rpc-specification/meta-schema';
import EthQuery from 'eth-query';
import { Hex } from '@metamask/utils';
import { BuiltInInfuraNetwork } from '../../../../../shared/constants/network';
import {
createNetworkClient,
NetworkClientType,
} from '../create-network-client';
/**
* A dummy value for the `infuraProjectId` 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 MOCK_INFURA_PROJECT_ID = 'abc123';
/**
* A dummy value for the `rpcUrl` option that `createJsonRpcClient` needs. (This
* should not be hit during tests, but just in case, this should also not refer
* to a real Infura URL.)
*/
const MOCK_RPC_URL = 'http://foo.com';
/**
* A default value for the `eth_blockNumber` request that the block tracker
* makes.
*/
const DEFAULT_LATEST_BLOCK_NUMBER = '0x42';
/**
* A reference to the original `setTimeout` function so that we can use it even
* when using fake timers.
*/
const originalSetTimeout = setTimeout;
/**
* If you're having trouble writing a test and you're wondering why the test
* keeps failing, you can set `process.env.DEBUG_PROVIDER_TESTS` to `1`. This
* will turn on some extra logging.
*
* @param args - The arguments that `console.log` takes.
*/
function debug(...args: any) {
if (process.env.DEBUG_PROVIDER_TESTS === '1') {
console.log(...args);
}
}
/**
* Builds a Nock scope object for mocking provider requests.
*
* @param rpcUrl - The URL of the RPC endpoint.
* @returns The nock scope.
*/
function buildScopeForMockingRequests(rpcUrl: string): NockScope {
return nock(rpcUrl).filteringRequestBody((body) => {
debug('Nock Received Request: ', body);
return body;
});
}
type Request = { method: string; params?: any[] };
type Response = {
id?: number | string;
jsonrpc?: '2.0';
error?: any;
result?: any;
httpStatus?: number;
};
type ResponseBody = { body: JSONRPCResponse };
type BodyOrResponse = ResponseBody | Response;
type CurriedMockRpcCallOptions = {
request: Request;
// The response data.
response?: BodyOrResponse;
/**
* An error to throw while making the request.
* Takes precedence over `response`.
*/
error?: Error | string;
/**
* The amount of time that should pass before the
* request resolves with the response.
*/
delay?: number;
/**
* The number of times that the request is
* expected to be made.
*/
times?: number;
};
type MockRpcCallOptions = {
// A nock scope (a set of mocked requests scoped to a certain base URL).
nockScope: nock.Scope;
} & CurriedMockRpcCallOptions;
type MockRpcCallResult = nock.Interceptor | nock.Scope;
/**
* Mocks a JSON-RPC request sent to the provider with the given response.
* Provider type is inferred from the base url set on the nockScope.
*
* @param args - The arguments.
* @param args.nockScope - A nock scope (a set of mocked requests scoped to a
* certain base URL).
* @param args.request - The request data.
* @param args.response - Information concerning the response that the request
* should have. If a `body` property is present, this is taken as the complete
* response body. If an `httpStatus` property is present, then it is taken as
* the HTTP status code to respond with. Properties other than these two are
* used to build a complete response body (including `id` and `jsonrpc`
* properties).
* @param args.error - An error to throw while making the request. Takes
* precedence over `response`.
* @param args.delay - The amount of time that should pass before the request
* resolves with the response.
* @param args.times - The number of times that the request is expected to be
* made.
* @returns The nock scope.
*/
function mockRpcCall({
nockScope,
request,
response,
error,
delay,
times,
}: MockRpcCallOptions): MockRpcCallResult {
// eth-query always passes `params`, so even if we don't supply this property,
// for consistency with makeRpcCall, assume that the `body` contains it
const { method, params = [], ...rest } = request;
let httpStatus = 200;
let completeResponse: JSONRPCResponse = { id: 2, jsonrpc: '2.0' };
if (response !== undefined) {
if ('body' in response) {
completeResponse = response.body;
} else {
if (response.error) {
completeResponse.error = response.error;
} else {
completeResponse.result = response.result;
}
if (response.httpStatus) {
httpStatus = response.httpStatus;
}
}
}
/* @ts-expect-error The types for Nock do not include `basePath` in the interface for Nock.Scope. */
const url = new URL(nockScope.basePath).hostname.match(/(\.|^)infura.io$/u)
? `/v3/${MOCK_INFURA_PROJECT_ID}`
: '/';
debug('Mocking request:', {
url,
method,
params,
response,
error,
...rest,
times,
});
let nockRequest = nockScope.post(url, {
id: /\d*/u,
jsonrpc: '2.0',
method,
params,
...rest,
});
if (delay !== undefined) {
nockRequest = nockRequest.delay(delay);
}
if (times !== undefined) {
nockRequest = nockRequest.times(times);
}
if (error !== undefined) {
return nockRequest.replyWithError(error);
} else if (completeResponse !== undefined) {
return nockRequest.reply(httpStatus, (_, requestBody: any) => {
if (response !== undefined && !('body' in response)) {
if (response.id === undefined) {
completeResponse.id = requestBody.id;
} else {
completeResponse.id = response.id;
}
}
debug('Nock returning Response', completeResponse);
return completeResponse;
});
}
return nockRequest;
}
type MockBlockTrackerRequestOptions = {
/**
* A nock scope (a set of mocked requests scoped to a certain base url).
*/
nockScope: NockScope;
/**
* The block number that the block tracker should report, as a 0x-prefixed hex
* string.
*/
blockNumber: string;
};
/**
* Mocks the next request for the latest block that the block tracker will make.
*
* @param args - The arguments.
* @param args.nockScope - A nock scope (a set of mocked requests scoped to a
* certain base URL).
* @param args.blockNumber - The block number that the block tracker should
* report, as a 0x-prefixed hex string.
*/
function mockNextBlockTrackerRequest({
nockScope,
blockNumber = DEFAULT_LATEST_BLOCK_NUMBER,
}: MockBlockTrackerRequestOptions) {
mockRpcCall({
nockScope,
request: { method: 'eth_blockNumber', params: [] },
response: { result: blockNumber },
});
}
/**
* Mocks all requests for the latest block that the block tracker will make.
*
* @param args - The arguments.
* @param args.nockScope - A nock scope (a set of mocked requests scoped to a
* certain base URL).
* @param args.blockNumber - The block number that the block tracker should
* report, as a 0x-prefixed hex string.
*/
async function mockAllBlockTrackerRequests({
nockScope,
blockNumber = DEFAULT_LATEST_BLOCK_NUMBER,
}: MockBlockTrackerRequestOptions) {
const result = await mockRpcCall({
nockScope,
request: { method: 'eth_blockNumber', params: [] },
response: { result: blockNumber },
});
if ('persist' in result) {
result.persist();
}
}
/**
* Makes a JSON-RPC call through the given eth-query object.
*
* @param ethQuery - The eth-query object.
* @param request - The request data.
* @returns A promise that either resolves with the result from the JSON-RPC
* response if it is successful or rejects with the error from the JSON-RPC
* response otherwise.
*/
function makeRpcCall(ethQuery: EthQuery, request: Request) {
return new Promise((resolve, reject) => {
debug('[makeRpcCall] making request', request);
ethQuery.sendAsync(request, (error, result) => {
debug('[makeRpcCall > ethQuery handler] error', error, 'result', result);
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
export type ProviderType = 'infura' | 'custom';
export type MockOptions = {
infuraNetwork?: BuiltInInfuraNetwork;
providerType: ProviderType;
customRpcUrl?: string;
customChainId?: Hex;
};
export type MockCommunications = {
mockNextBlockTrackerRequest: (options?: any) => void;
mockAllBlockTrackerRequests: (options?: any) => void;
mockRpcCall: (options: CurriedMockRpcCallOptions) => MockRpcCallResult;
rpcUrl: string;
infuraNetwork: BuiltInInfuraNetwork;
};
/**
* Sets up request mocks for requests to the provider.
*
* @param options - An options bag.
* @param options.providerType - The type of network client being tested.
* @param options.infuraNetwork - The name of the Infura network being tested,
* assuming that `providerType` is "infura" (default: "mainnet").
* @param options.customRpcUrl - The URL of the custom RPC endpoint, assuming
* that `providerType` is "custom".
* @param fn - A function which will be called with an object that allows
* interaction with the network client.
* @returns The return value of the given function.
*/
export async function withMockedCommunications(
{
providerType,
infuraNetwork = 'mainnet',
customRpcUrl = MOCK_RPC_URL,
}: MockOptions,
fn: (comms: MockCommunications) => Promise<void>,
) {
if (providerType !== 'infura' && providerType !== 'custom') {
throw new Error(
`providerType must be either "infura" or "custom", was "${providerType}" instead`,
);
}
const rpcUrl =
providerType === 'infura'
? `https://${infuraNetwork}.infura.io`
: customRpcUrl;
const nockScope = buildScopeForMockingRequests(rpcUrl);
const curriedMockNextBlockTrackerRequest = (localOptions: any) =>
mockNextBlockTrackerRequest({ nockScope, ...localOptions });
const curriedMockAllBlockTrackerRequests = (localOptions: any) =>
mockAllBlockTrackerRequests({ nockScope, ...localOptions });
const curriedMockRpcCall = (localOptions: any) =>
mockRpcCall({ nockScope, ...localOptions });
const comms = {
mockNextBlockTrackerRequest: curriedMockNextBlockTrackerRequest,
mockAllBlockTrackerRequests: curriedMockAllBlockTrackerRequests,
mockRpcCall: curriedMockRpcCall,
rpcUrl,
infuraNetwork,
};
try {
return await fn(comms);
} finally {
nock.isDone();
nock.cleanAll();
}
}
type MockNetworkClient = {
blockTracker: any;
clock: sinon.SinonFakeTimers;
makeRpcCall: (request: Request) => Promise<any>;
makeRpcCallsInSeries: (requests: Request[]) => Promise<any[]>;
};
/**
* Some middleware contain logic which retries the request if some condition
* applies. This retrying always happens out of band via `setTimeout`, and
* because we are stubbing time via Jest's fake timers, we have to manually
* advance the clock so that the `setTimeout` handlers get fired. We don't know
* when these timers will get created, however, so we have to keep advancing
* timers until the request has been made an appropriate number of times.
* Unfortunately we don't have a good way to know how many times a request has
* been retried, but the good news is that the middleware won't end, and thus
* the promise which the RPC call returns won't get fulfilled, until all retries
* have been made.
*
* @param promise - The promise which is returned by the RPC call.
* @param clock - A Sinon clock object which can be used to advance to the next
* `setTimeout` handler.
*/
export async function waitForPromiseToBeFulfilledAfterRunningAllTimers(
promise: any,
clock: any,
) {
let hasPromiseBeenFulfilled = false;
let numTimesClockHasBeenAdvanced = 0;
promise
.catch((error: any) => {
// This is used to silence Node.js warnings about the rejection
// being handled asynchronously. The error is handled later when
// `promise` is awaited, but we log it here anyway in case it gets
// swallowed.
debug(error);
})
.finally(() => {
hasPromiseBeenFulfilled = true;
});
// `hasPromiseBeenFulfilled` is modified asynchronously.
/* eslint-disable-next-line no-unmodified-loop-condition */
while (!hasPromiseBeenFulfilled && numTimesClockHasBeenAdvanced < 15) {
clock.runAll();
await new Promise((resolve) => originalSetTimeout(resolve, 10));
numTimesClockHasBeenAdvanced += 1;
}
return promise;
}
/**
* Builds a provider from the middleware (for the provider type) along with a
* block tracker, runs the given function with those two things, and then
* ensures the block tracker is stopped at the end.
*
* @param options - An options bag.
* @param options.providerType - The type of network client being tested.
* @param options.infuraNetwork - The name of the Infura network being tested,
* assuming that `providerType` is "infura" (default: "mainnet").
* @param options.customRpcUrl - The URL of the custom RPC endpoint, assuming
* that `providerType` is "custom".
* @param options.customChainId - The chain id belonging to the custom RPC
* endpoint, assuming that `providerType` is "custom" (default: "0x1").
* @param fn - A function which will be called with an object that allows
* interaction with the network client.
* @returns The return value of the given function.
*/
export async function withNetworkClient(
{
providerType,
infuraNetwork = 'mainnet',
customRpcUrl = MOCK_RPC_URL,
customChainId = '0x1',
}: MockOptions,
fn: (client: MockNetworkClient) => Promise<any>,
) {
if (providerType !== 'infura' && providerType !== 'custom') {
throw new Error(
`providerType must be either "infura" or "custom", was "${providerType}" instead`,
);
}
// Faking timers ends up doing two things:
// 1. Halting the block tracker (which depends on `setTimeout` to periodically
// request the latest block) set up in `eth-json-rpc-middleware`
// 2. Halting the retry logic in `@metamask/eth-json-rpc-infura` (which also
// depends on `setTimeout`)
const clock = sinon.useFakeTimers();
// The JSON-RPC client wraps `eth_estimateGas` so that it takes 2 seconds longer
// than it usually would to complete. Or at least it should — this doesn't
// appear to be working correctly. Unset `IN_TEST` on `process.env` to prevent
// this behavior.
const inTest = process.env.IN_TEST;
delete process.env.IN_TEST;
const clientUnderTest =
providerType === 'infura'
? createNetworkClient({
network: infuraNetwork,
infuraProjectId: MOCK_INFURA_PROJECT_ID,
type: NetworkClientType.Infura,
})
: createNetworkClient({
chainId: customChainId,
rpcUrl: customRpcUrl,
type: NetworkClientType.Custom,
});
process.env.IN_TEST = inTest;
const { provider, blockTracker } = clientUnderTest;
const ethQuery = new EthQuery(provider);
const curriedMakeRpcCall = (request: Request) =>
makeRpcCall(ethQuery, request);
const makeRpcCallsInSeries = async (requests: Request[]) => {
const responses = [];
for (const request of requests) {
responses.push(await curriedMakeRpcCall(request));
}
return responses;
};
const client = {
blockTracker,
clock,
makeRpcCall: curriedMakeRpcCall,
makeRpcCallsInSeries,
};
try {
return await fn(client);
} finally {
await blockTracker.destroy();
clock.restore();
}
}
type BuildMockParamsOptions = {
// The block parameter value to set.
blockParam: any;
// The index of the block parameter.
blockParamIndex: number;
};
/**
* Build mock parameters for a JSON-RPC call.
*
* The string 'some value' is used as the default value for each entry. The
* block parameter index determines the number of parameters to generate.
*
* The block parameter can be set to a custom value. If no value is given, it
* is set as undefined.
*
* @param args - Arguments.
* @param args.blockParamIndex - The index of the block parameter.
* @param args.blockParam - The block parameter value to set.
* @returns The mock params.
*/
export function buildMockParams({
blockParam,
blockParamIndex,
}: BuildMockParamsOptions) {
const params = new Array(blockParamIndex).fill('some value');
params[blockParamIndex] = blockParam;
return params;
}
/**
* Returns a partial JSON-RPC request object, with the "block" param replaced
* with the given value.
*
* @param request - The request object.
* @param request.method - The request method.
* @param request.params - The request params.
* @param blockParamIndex - The index within the `params` array of the block
* param.
* @param blockParam - The desired block param value.
* @returns The updated request object.
*/
export function buildRequestWithReplacedBlockParam(
{ method, params = [] }: Request,
blockParamIndex: number,
blockParam: any,
) {
const updatedParams = params.slice();
updatedParams[blockParamIndex] = blockParam;
return { method, params: updatedParams };
}

View File

@ -1,977 +0,0 @@
/* eslint-disable jest/require-top-level-describe, jest/no-export */
import {
ProviderType,
waitForPromiseToBeFulfilledAfterRunningAllTimers,
withMockedCommunications,
withNetworkClient,
} from './helpers';
import {
buildFetchFailedErrorMessage,
buildInfuraClientRetriesExhaustedErrorMessage,
buildJsonRpcEngineEmptyResponseErrorMessage,
} from './shared-tests';
type TestsForRpcMethodAssumingNoBlockParamOptions = {
providerType: ProviderType;
numberOfParameters: number;
};
/**
* Defines tests which exercise the behavior exhibited by an RPC method which is
* assumed to not take a block parameter. Even if it does, the value of this
* parameter will not be used in determining how to cache the method.
*
* @param method - The name of the RPC method under test.
* @param additionalArgs - Additional arguments.
* @param additionalArgs.numberOfParameters - The number of parameters supported by the method under test.
* @param additionalArgs.providerType - The type of provider being tested;
* either `infura` or `custom` (default: "infura").
*/
export function testsForRpcMethodAssumingNoBlockParam(
method: string,
{
numberOfParameters,
providerType,
}: TestsForRpcMethodAssumingNoBlockParamOptions,
) {
if (providerType !== 'infura' && providerType !== 'custom') {
throw new Error(
`providerType must be either "infura" or "custom", was "${providerType}" instead`,
);
}
it('does not hit the RPC endpoint more than once for identical requests', async () => {
const requests = [{ method }, { method }];
const mockResults = ['first result', 'second result'];
await withMockedCommunications({ providerType }, async (comms) => {
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request: requests[0],
response: { result: mockResults[0] },
});
const results = await withNetworkClient(
{ providerType },
({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests),
);
expect(results).toStrictEqual([mockResults[0], mockResults[0]]);
});
});
for (const paramIndex of [...Array(numberOfParameters).keys()]) {
it(`does not reuse the result of a previous request if parameter at index "${paramIndex}" differs`, async () => {
const firstMockParams = [
...new Array(numberOfParameters).fill('some value'),
];
const secondMockParams = firstMockParams.slice();
secondMockParams[paramIndex] = 'another value';
const requests = [
{
method,
params: firstMockParams,
},
{ method, params: secondMockParams },
];
const mockResults = ['some result', 'another result'];
await withMockedCommunications({ providerType }, async (comms) => {
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request: requests[0],
response: { result: mockResults[0] },
});
comms.mockRpcCall({
request: requests[1],
response: { result: mockResults[1] },
});
const results = await withNetworkClient(
{ providerType },
({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests),
);
expect(results).toStrictEqual([mockResults[0], mockResults[1]]);
});
});
}
it('hits the RPC endpoint and does not reuse the result of a previous request if the latest block number was updated since', async () => {
const requests = [{ method }, { method }];
const mockResults = ['first result', 'second result'];
await withMockedCommunications({ providerType }, async (comms) => {
// Note that we have to mock these requests in a specific order. The
// first block tracker request occurs because of the first RPC request.
// The second block tracker request, however, does not occur because of
// the second RPC request, but rather because we call `clock.runAll()`
// below.
comms.mockNextBlockTrackerRequest({ blockNumber: '0x1' });
comms.mockRpcCall({
request: requests[0],
response: { result: mockResults[0] },
});
comms.mockNextBlockTrackerRequest({ blockNumber: '0x2' });
comms.mockRpcCall({
request: requests[1],
response: { result: mockResults[1] },
});
const results = await withNetworkClient(
{ providerType },
async (client) => {
const firstResult = await client.makeRpcCall(requests[0]);
// Proceed to the next iteration of the block tracker so that a new
// block is fetched and the current block is updated.
client.clock.runAll();
const secondResult = await client.makeRpcCall(requests[1]);
return [firstResult, secondResult];
},
);
expect(results).toStrictEqual(mockResults);
});
});
for (const emptyValue of [null, undefined, '\u003cnil\u003e']) {
it(`does not retry an empty response of "${emptyValue}"`, async () => {
const request = { method };
const mockResult = emptyValue;
await withMockedCommunications({ providerType }, async (comms) => {
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
response: { result: mockResult },
});
const result = await withNetworkClient(
{ providerType },
({ makeRpcCall }) => makeRpcCall(request),
);
expect(result).toStrictEqual(mockResult);
});
});
it(`does not reuse the result of a previous request if it was "${emptyValue}"`, async () => {
const requests = [{ method }, { method }];
const mockResults = [emptyValue, 'some result'];
await withMockedCommunications({ providerType }, async (comms) => {
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request: requests[0],
response: { result: mockResults[0] },
});
comms.mockRpcCall({
request: requests[1],
response: { result: mockResults[1] },
});
const results = await withNetworkClient(
{ providerType },
({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests),
);
expect(results).toStrictEqual(mockResults);
});
});
}
it('queues requests while a previous identical call is still pending, then runs the queue when it finishes, reusing the result from the first request', async () => {
const requests = [{ method }, { method }, { method }];
const mockResults = ['first result', 'second result', 'third result'];
await withMockedCommunications({ providerType }, async (comms) => {
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request: requests[0],
response: { result: mockResults[0] },
delay: 100,
});
comms.mockRpcCall({
request: requests[1],
response: { result: mockResults[1] },
});
comms.mockRpcCall({
request: requests[2],
response: { result: mockResults[2] },
});
const results = await withNetworkClient(
{ providerType },
async (client) => {
const resultPromises = [
client.makeRpcCall(requests[0]),
client.makeRpcCall(requests[1]),
client.makeRpcCall(requests[2]),
];
const firstResult = await resultPromises[0];
// The inflight cache middleware uses setTimeout to run the handlers,
// so run them now
client.clock.runAll();
const remainingResults = await Promise.all(resultPromises.slice(1));
return [firstResult, ...remainingResults];
},
);
expect(results).toStrictEqual([
mockResults[0],
mockResults[0],
mockResults[0],
]);
});
});
it('throws a custom error if the request to the RPC endpoint returns a 405 response', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
response: {
httpStatus: 405,
},
});
const promiseForResult = withNetworkClient(
{ providerType },
async ({ makeRpcCall }) => makeRpcCall(request),
);
await expect(promiseForResult).rejects.toThrow(
'The method does not exist / is not available',
);
});
});
// There is a difference in how we are testing the Infura middleware vs. the
// custom RPC middleware (or, more specifically, the fetch middleware) because
// of what both middleware treat as rate limiting errors. In this case, the
// fetch middleware treats a 418 response from the RPC endpoint as such an
// error, whereas to the Infura middleware, it is a 429 response.
if (providerType === 'infura') {
it('throws an undescriptive error if the request to the RPC endpoint returns a 418 response', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { id: 123, method };
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
response: {
httpStatus: 418,
},
});
const promiseForResult = withNetworkClient(
{ providerType },
async ({ makeRpcCall }) => makeRpcCall(request),
);
await expect(promiseForResult).rejects.toThrow(
'{"id":123,"jsonrpc":"2.0"}',
);
});
});
it('throws an error with a custom message if the request to the RPC endpoint returns a 429 response', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
response: {
httpStatus: 429,
},
});
const promiseForResult = withNetworkClient(
{ providerType },
async ({ makeRpcCall }) => makeRpcCall(request),
);
await expect(promiseForResult).rejects.toThrow(
'Request is being rate limited',
);
});
});
} else {
it('throws a custom error if the request to the RPC endpoint returns a 418 response', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
response: {
httpStatus: 418,
},
});
const promiseForResult = withNetworkClient(
{ providerType },
async ({ makeRpcCall }) => makeRpcCall(request),
);
await expect(promiseForResult).rejects.toThrow(
'Request is being rate limited.',
);
});
});
it('throws an undescriptive error if the request to the RPC endpoint returns a 429 response', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
response: {
httpStatus: 429,
},
});
const promiseForResult = withNetworkClient(
{ providerType },
async ({ makeRpcCall }) => makeRpcCall(request),
);
await expect(promiseForResult).rejects.toThrow(
"Non-200 status code: '429'",
);
});
});
}
it('throws a generic, undescriptive error if the request to the RPC endpoint returns a response that is not 405, 418, 429, 503, or 504', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
response: {
id: 12345,
jsonrpc: '2.0',
error: 'some error',
httpStatus: 420,
},
});
const promiseForResult = withNetworkClient(
{ providerType },
async ({ makeRpcCall }) => makeRpcCall(request),
);
const errorMessage =
providerType === 'infura'
? '{"id":12345,"jsonrpc":"2.0","error":"some error"}'
: "Non-200 status code: '420'";
await expect(promiseForResult).rejects.toThrow(errorMessage);
});
});
[503, 504].forEach((httpStatus) => {
it(`retries the request to the RPC endpoint up to 5 times if it returns a ${httpStatus} response, returning the successful result if there is one on the 5th try`, async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
// Here we have the request fail for the first 4 tries, then succeed
// on the 5th try.
comms.mockRpcCall({
request,
response: {
error: 'Some error',
httpStatus,
},
times: 4,
});
comms.mockRpcCall({
request,
response: {
result: 'the result',
httpStatus: 200,
},
});
const result = await withNetworkClient(
{ providerType },
async ({ makeRpcCall, clock }) => {
return await waitForPromiseToBeFulfilledAfterRunningAllTimers(
makeRpcCall(request),
clock,
);
},
);
expect(result).toStrictEqual('the result');
});
});
it(`causes a request to fail with a custom error if the request to the RPC endpoint returns a ${httpStatus} response 5 times in a row`, async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
response: {
error: 'Some error',
httpStatus,
},
times: 5,
});
comms.mockNextBlockTrackerRequest();
const promiseForResult = withNetworkClient(
{ providerType },
async ({ makeRpcCall, clock }) => {
return await waitForPromiseToBeFulfilledAfterRunningAllTimers(
makeRpcCall(request),
clock,
);
},
);
const err =
providerType === 'infura'
? buildInfuraClientRetriesExhaustedErrorMessage('Gateway timeout')
: buildJsonRpcEngineEmptyResponseErrorMessage(method);
await expect(promiseForResult).rejects.toThrow(err);
});
});
});
it('retries the request to the RPC endpoint up to 5 times if an "ETIMEDOUT" error is thrown while making the request, returning the successful result if there is one on the 5th try', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
// Here we have the request fail for the first 4 tries, then succeed
// on the 5th try.
comms.mockRpcCall({
request,
error: 'ETIMEDOUT: Some message',
times: 4,
});
comms.mockRpcCall({
request,
response: {
result: 'the result',
httpStatus: 200,
},
});
const result = await withNetworkClient(
{ providerType },
async ({ makeRpcCall, clock }) => {
return await waitForPromiseToBeFulfilledAfterRunningAllTimers(
makeRpcCall(request),
clock,
);
},
);
expect(result).toStrictEqual('the result');
});
});
// Both the Infura and fetch middleware detect ETIMEDOUT errors and will
// automatically retry the request to the RPC endpoint in question, but both
// produce a different error if the number of retries is exhausted.
if (providerType === 'infura') {
it('causes a request to fail with a custom error if an "ETIMEDOUT" error is thrown while making the request to the RPC endpoint 5 times in a row', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
const errorMessage = 'ETIMEDOUT: Some message';
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
error: errorMessage,
times: 5,
});
const promiseForResult = withNetworkClient(
{ providerType },
async ({ makeRpcCall, clock }) => {
return await waitForPromiseToBeFulfilledAfterRunningAllTimers(
makeRpcCall(request),
clock,
);
},
);
await expect(promiseForResult).rejects.toThrow(
buildInfuraClientRetriesExhaustedErrorMessage(errorMessage),
);
});
});
} else {
it('returns an empty response if an "ETIMEDOUT" error is thrown while making the request to the RPC endpoint 5 times in a row', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
const errorMessage = 'ETIMEDOUT: Some message';
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
error: errorMessage,
times: 5,
});
const promiseForResult = withNetworkClient(
{ providerType },
async ({ makeRpcCall, clock }) => {
return await waitForPromiseToBeFulfilledAfterRunningAllTimers(
makeRpcCall(request),
clock,
);
},
);
await expect(promiseForResult).rejects.toThrow(
buildJsonRpcEngineEmptyResponseErrorMessage(method),
);
});
});
}
// The Infura middleware treats a response that contains an ECONNRESET message
// as an innocuous error that is likely to disappear on a retry. The custom
// RPC middleware, on the other hand, does not specially handle this error.
if (providerType === 'infura') {
it('retries the request to the RPC endpoint up to 5 times if an "ECONNRESET" error is thrown while making the request, returning the successful result if there is one on the 5th try', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
// Here we have the request fail for the first 4 tries, then succeed
// on the 5th try.
comms.mockRpcCall({
request,
error: 'ECONNRESET: Some message',
times: 4,
});
comms.mockRpcCall({
request,
response: {
result: 'the result',
httpStatus: 200,
},
});
const result = await withNetworkClient(
{ providerType },
async ({ makeRpcCall, clock }) => {
return await waitForPromiseToBeFulfilledAfterRunningAllTimers(
makeRpcCall(request),
clock,
);
},
);
expect(result).toStrictEqual('the result');
});
});
it('causes a request to fail with a custom error if an "ECONNRESET" error is thrown while making the request to the RPC endpoint 5 times in a row', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
const errorMessage = 'ECONNRESET: Some message';
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
error: errorMessage,
times: 5,
});
const promiseForResult = withNetworkClient(
{ providerType },
async ({ makeRpcCall, clock }) => {
return await waitForPromiseToBeFulfilledAfterRunningAllTimers(
makeRpcCall(request),
clock,
);
},
);
await expect(promiseForResult).rejects.toThrow(
buildInfuraClientRetriesExhaustedErrorMessage(errorMessage),
);
});
});
} else {
it('does not retry the request to the RPC endpoint, but throws immediately, if an "ECONNRESET" error is thrown while making the request', async () => {
const customRpcUrl = 'http://example.com';
await withMockedCommunications(
{ providerType, customRpcUrl },
async (comms) => {
const request = { method };
const errorMessage = 'ECONNRESET: Some message';
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
error: errorMessage,
});
const promiseForResult = withNetworkClient(
{ providerType, customRpcUrl },
async ({ makeRpcCall }) => makeRpcCall(request),
);
await expect(promiseForResult).rejects.toThrow(
buildFetchFailedErrorMessage(customRpcUrl, errorMessage),
);
},
);
});
}
// Both the Infura and fetch middleware will attempt to parse the response
// body as JSON, and if this step produces an error, both middleware will also
// attempt to retry the request. However, this error handling code is slightly
// different between the two. As the error in this case is a SyntaxError, the
// Infura middleware will catch it immediately, whereas the custom RPC
// middleware will catch it and re-throw a separate error, which it then
// catches later.
if (providerType === 'infura') {
it('retries the request to the RPC endpoint up to 5 times if an "SyntaxError" error is thrown while making the request, returning the successful result if there is one on the 5th try', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
// Here we have the request fail for the first 4 tries, then succeed
// on the 5th try.
comms.mockRpcCall({
request,
error: 'SyntaxError: Some message',
times: 4,
});
comms.mockRpcCall({
request,
response: {
result: 'the result',
httpStatus: 200,
},
});
const result = await withNetworkClient(
{ providerType },
async ({ makeRpcCall, clock }) => {
return await waitForPromiseToBeFulfilledAfterRunningAllTimers(
makeRpcCall(request),
clock,
);
},
);
expect(result).toStrictEqual('the result');
});
});
it('causes a request to fail with a custom error if an "SyntaxError" error is thrown while making the request to the RPC endpoint 5 times in a row', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
const errorMessage = 'SyntaxError: Some message';
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
error: errorMessage,
times: 5,
});
const promiseForResult = withNetworkClient(
{ providerType },
async ({ makeRpcCall, clock }) => {
return await waitForPromiseToBeFulfilledAfterRunningAllTimers(
makeRpcCall(request),
clock,
);
},
);
await expect(promiseForResult).rejects.toThrow(
buildInfuraClientRetriesExhaustedErrorMessage(errorMessage),
);
});
});
it('does not retry the request to the RPC endpoint, but throws immediately, if a "failed to parse response body" error is thrown while making the request', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
const errorMessage = 'failed to parse response body: some message';
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
error: errorMessage,
});
const promiseForResult = withNetworkClient(
{ providerType, infuraNetwork: comms.infuraNetwork },
async ({ makeRpcCall }) => makeRpcCall(request),
);
await expect(promiseForResult).rejects.toThrow(
buildFetchFailedErrorMessage(comms.rpcUrl, errorMessage),
);
});
});
} else {
it('does not retry the request to the RPC endpoint, but throws immediately, if a "SyntaxError" error is thrown while making the request', async () => {
const customRpcUrl = 'http://example.com';
await withMockedCommunications(
{ providerType, customRpcUrl },
async (comms) => {
const request = { method };
const errorMessage = 'SyntaxError: Some message';
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
error: errorMessage,
});
const promiseForResult = withNetworkClient(
{ providerType, customRpcUrl },
async ({ makeRpcCall }) => makeRpcCall(request),
);
await expect(promiseForResult).rejects.toThrow(
buildFetchFailedErrorMessage(customRpcUrl, errorMessage),
);
},
);
});
it('retries the request to the RPC endpoint up to 5 times if a "failed to parse response body" error is thrown while making the request, returning the successful result if there is one on the 5th try', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
// Here we have the request fail for the first 4 tries, then succeed
// on the 5th try.
comms.mockRpcCall({
request,
error: 'failed to parse response body: some message',
times: 4,
});
comms.mockRpcCall({
request,
response: {
result: 'the result',
httpStatus: 200,
},
});
const result = await withNetworkClient(
{ providerType },
async ({ makeRpcCall, clock }) => {
return await waitForPromiseToBeFulfilledAfterRunningAllTimers(
makeRpcCall(request),
clock,
);
},
);
expect(result).toStrictEqual('the result');
});
});
it('returns an empty response if a "failed to parse response body" error is thrown while making the request to the RPC endpoint 5 times in a row', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
const errorMessage = 'failed to parse response body: some message';
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
error: errorMessage,
times: 5,
});
const promiseForResult = withNetworkClient(
{ providerType },
async ({ makeRpcCall, clock }) => {
return await waitForPromiseToBeFulfilledAfterRunningAllTimers(
makeRpcCall(request),
clock,
);
},
);
await expect(promiseForResult).rejects.toThrow(
buildJsonRpcEngineEmptyResponseErrorMessage(method),
);
});
});
}
// Only the custom RPC middleware will detect a "Failed to fetch" error and
// attempt to retry the request to the RPC endpoint; the Infura middleware
// does not.
if (providerType === 'infura') {
it('does not retry the request to the RPC endpoint, but throws immediately, if a "Failed to fetch" error is thrown while making the request', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
const errorMessage = 'Failed to fetch: some message';
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
error: errorMessage,
});
const promiseForResult = withNetworkClient(
{ providerType, infuraNetwork: comms.infuraNetwork },
async ({ makeRpcCall }) => makeRpcCall(request),
);
await expect(promiseForResult).rejects.toThrow(
buildFetchFailedErrorMessage(comms.rpcUrl, errorMessage),
);
});
});
} else {
it('retries the request to the RPC endpoint up to 5 times if a "Failed to fetch" error is thrown while making the request, returning the successful result if there is one on the 5th try', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
// Here we have the request fail for the first 4 tries, then succeed
// on the 5th try.
comms.mockRpcCall({
request,
error: 'Failed to fetch: some message',
times: 4,
});
comms.mockRpcCall({
request,
response: {
result: 'the result',
httpStatus: 200,
},
});
const result = await withNetworkClient(
{ providerType },
async ({ makeRpcCall, clock }) => {
return await waitForPromiseToBeFulfilledAfterRunningAllTimers(
makeRpcCall(request),
clock,
);
},
);
expect(result).toStrictEqual('the result');
});
});
it('returns an empty response if a "Failed to fetch" error is thrown while making the request to the RPC endpoint 5 times in a row', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
const errorMessage = 'Failed to fetch: some message';
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
error: errorMessage,
times: 5,
});
const promiseForResult = withNetworkClient(
{ providerType },
async ({ makeRpcCall, clock }) => {
return await waitForPromiseToBeFulfilledAfterRunningAllTimers(
makeRpcCall(request),
clock,
);
},
);
await expect(promiseForResult).rejects.toThrow(
buildJsonRpcEngineEmptyResponseErrorMessage(method),
);
});
});
}
}

View File

@ -1,63 +0,0 @@
/* eslint-disable jest/require-top-level-describe, jest/no-export */
import { fill } from 'lodash';
import {
ProviderType,
withMockedCommunications,
withNetworkClient,
} from './helpers';
type TestsForRpcMethodNotHandledByMiddlewareOptions = {
providerType: ProviderType;
numberOfParameters: number;
};
/**
* Defines tests which exercise the behavior exhibited by an RPC method that
* is not handled specially by the network client middleware.
*
* @param method - The name of the RPC method under test.
* @param additionalArgs - Additional arguments.
* @param additionalArgs.providerType - The type of provider being tested;
* either `infura` or `custom`.
* @param additionalArgs.numberOfParameters - The number of parameters that this
* RPC method takes.
*/
export function testsForRpcMethodNotHandledByMiddleware(
method: string,
{
providerType,
numberOfParameters,
}: TestsForRpcMethodNotHandledByMiddlewareOptions,
) {
if (providerType !== 'infura' && providerType !== 'custom') {
throw new Error(
`providerType must be either "infura" or "custom", was "${providerType}" instead`,
);
}
it('attempts to pass the request off to the RPC endpoint', async () => {
const request = {
method,
params: fill(Array(numberOfParameters), 'some value'),
};
const expectedResult = 'the result';
await withMockedCommunications({ providerType }, async (comms) => {
// The first time a block-cacheable request is made, the latest block
// number is retrieved through the block tracker first. It doesn't
// matter what this is — it's just used as a cache key.
comms.mockNextBlockTrackerRequest();
comms.mockRpcCall({
request,
response: { result: expectedResult },
});
const actualResult = await withNetworkClient(
{ providerType },
({ makeRpcCall }) => makeRpcCall(request),
);
expect(actualResult).toStrictEqual(expectedResult);
});
});
}

View File

@ -1,372 +0,0 @@
/* eslint-disable jest/require-top-level-describe, jest/no-export, jest/no-identical-title */
import { testsForRpcMethodsThatCheckForBlockHashInResponse } from './block-hash-in-response';
import { testsForRpcMethodSupportingBlockParam } from './block-param';
import {
ProviderType,
withMockedCommunications,
withNetworkClient,
} from './helpers';
import { testsForRpcMethodAssumingNoBlockParam } from './no-block-param';
import { testsForRpcMethodNotHandledByMiddleware } from './not-handled-by-middleware';
/**
* Constructs an error message that the Infura client would produce in the event
* that it has attempted to retry the request to Infura and has failed.
*
* @param reason - The exact reason for failure.
* @returns The error message.
*/
export function buildInfuraClientRetriesExhaustedErrorMessage(reason: string) {
return new RegExp(
`^InfuraProvider - cannot complete request. All retries exhausted\\..+${reason}`,
'us',
);
}
/**
* Constructs an error message that JsonRpcEngine would produce in the event
* that the response object is empty as it leaves the middleware.
*
* @param method - The RPC method.
* @returns The error message.
*/
export function buildJsonRpcEngineEmptyResponseErrorMessage(method: string) {
return new RegExp(
`^JsonRpcEngine: Response has no error or result for request:.+"method": "${method}"`,
'us',
);
}
/**
* Constructs an error message that `fetch` with throw if it cannot make a
* request.
*
* @param url - The URL being fetched
* @param reason - The reason.
* @returns The error message.
*/
export function buildFetchFailedErrorMessage(url: string, reason: string) {
return new RegExp(
`^request to ${url}(/[^/ ]*)+ failed, reason: ${reason}`,
'us',
);
}
/**
* Defines tests that are common to both the Infura and JSON-RPC network client.
*
* @param providerType - The type of provider being tested, which determines
* which suite of middleware is being tested. If `infura`, then the middleware
* exposed by `createInfuraClient` is tested; if `custom`, then the middleware
* exposed by `createJsonRpcClient` will be tested.
*/
export function testsForProviderType(providerType: ProviderType) {
// Ethereum JSON-RPC spec: <https://ethereum.github.io/execution-apis/api-documentation/>
// Infura documentation: <https://docs.infura.io/infura/networks/ethereum/json-rpc-methods>
describe('methods included in the Ethereum JSON-RPC spec', () => {
describe('methods not handled by middleware', () => {
const notHandledByMiddleware = [
// This list is presented in the same order as in the network client
// tests on the core side.
{ name: 'eth_newFilter', numberOfParameters: 1 },
{ name: 'eth_getFilterChanges', numberOfParameters: 1 },
{ name: 'eth_newBlockFilter', numberOfParameters: 0 },
{ name: 'eth_newPendingTransactionFilter', numberOfParameters: 0 },
{ name: 'eth_uninstallFilter', numberOfParameters: 1 },
{ name: 'eth_sendRawTransaction', numberOfParameters: 1 },
{ name: 'eth_sendTransaction', numberOfParameters: 1 },
{ name: 'eth_sign', numberOfParameters: 2 },
{ name: 'eth_createAccessList', numberOfParameters: 2 },
{ name: 'eth_getLogs', numberOfParameters: 1 },
{ name: 'eth_getProof', numberOfParameters: 3 },
{ name: 'eth_getWork', numberOfParameters: 0 },
{ name: 'eth_maxPriorityFeePerGas', numberOfParameters: 0 },
{ name: 'eth_submitHashRate', numberOfParameters: 2 },
{ name: 'eth_submitWork', numberOfParameters: 3 },
{ name: 'eth_syncing', numberOfParameters: 0 },
{ name: 'eth_feeHistory', numberOfParameters: 3 },
{ name: 'debug_getRawHeader', numberOfParameters: 1 },
{ name: 'debug_getRawBlock', numberOfParameters: 1 },
{ name: 'debug_getRawTransaction', numberOfParameters: 1 },
{ name: 'debug_getRawReceipts', numberOfParameters: 1 },
{ name: 'debug_getBadBlocks', numberOfParameters: 0 },
{ name: 'eth_accounts', numberOfParameters: 0 },
{ name: 'eth_coinbase', numberOfParameters: 0 },
{ name: 'eth_hashrate', numberOfParameters: 0 },
{ name: 'eth_mining', numberOfParameters: 0 },
{ name: 'eth_signTransaction', numberOfParameters: 1 },
];
notHandledByMiddleware.forEach(({ name, numberOfParameters }) => {
describe(`method name: ${name}`, () => {
testsForRpcMethodNotHandledByMiddleware(name, {
providerType,
numberOfParameters,
});
});
});
});
describe('methods with block hashes in their result', () => {
const methodsWithBlockHashInResponse = [
{ name: 'eth_getTransactionByHash', numberOfParameters: 1 },
{ name: 'eth_getTransactionReceipt', numberOfParameters: 1 },
];
methodsWithBlockHashInResponse.forEach(({ name, numberOfParameters }) => {
describe(`method name: ${name}`, () => {
testsForRpcMethodsThatCheckForBlockHashInResponse(name, {
numberOfParameters,
providerType,
});
});
});
});
describe('methods that assume there is no block param', () => {
const assumingNoBlockParam = [
{ name: 'eth_getFilterLogs', numberOfParameters: 1 },
{ name: 'eth_blockNumber', numberOfParameters: 0 },
{ name: 'eth_estimateGas', numberOfParameters: 2 },
{ name: 'eth_gasPrice', numberOfParameters: 0 },
{ name: 'eth_getBlockByHash', numberOfParameters: 2 },
{
name: 'eth_getBlockTransactionCountByHash',
numberOfParameters: 1,
},
{
name: 'eth_getTransactionByBlockHashAndIndex',
numberOfParameters: 2,
},
{ name: 'eth_getUncleByBlockHashAndIndex', numberOfParameters: 2 },
{ name: 'eth_getUncleCountByBlockHash', numberOfParameters: 1 },
];
const blockParamIgnored = [
{ name: 'eth_getUncleCountByBlockNumber', numberOfParameters: 1 },
{ name: 'eth_getUncleByBlockNumberAndIndex', numberOfParameters: 2 },
{
name: 'eth_getTransactionByBlockNumberAndIndex',
numberOfParameters: 2,
},
{
name: 'eth_getBlockTransactionCountByNumber',
numberOfParameters: 1,
},
];
assumingNoBlockParam
.concat(blockParamIgnored)
.forEach(({ name, numberOfParameters }) =>
describe(`method name: ${name}`, () => {
testsForRpcMethodAssumingNoBlockParam(name, {
providerType,
numberOfParameters,
});
}),
);
});
describe('methods that have a param to specify the block', () => {
const supportingBlockParam = [
{
name: 'eth_call',
blockParamIndex: 1,
numberOfParameters: 2,
},
{
name: 'eth_getBalance',
blockParamIndex: 1,
numberOfParameters: 2,
},
{
name: 'eth_getBlockByNumber',
blockParamIndex: 0,
numberOfParameters: 2,
},
{ name: 'eth_getCode', blockParamIndex: 1, numberOfParameters: 2 },
{
name: 'eth_getStorageAt',
blockParamIndex: 2,
numberOfParameters: 3,
},
{
name: 'eth_getTransactionCount',
blockParamIndex: 1,
numberOfParameters: 2,
},
];
supportingBlockParam.forEach(
({ name, blockParamIndex, numberOfParameters }) => {
describe(`method name: ${name}`, () => {
testsForRpcMethodSupportingBlockParam(name, {
providerType,
blockParamIndex,
numberOfParameters,
});
});
},
);
});
describe('other methods', () => {
describe('eth_getTransactionByHash', () => {
it("refreshes the block tracker's current block if it is less than the block number that comes back in the response", async () => {
const method = 'eth_getTransactionByHash';
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' });
// This is our request.
comms.mockRpcCall({
request,
response: {
result: {
blockNumber: '0x200',
},
},
});
comms.mockNextBlockTrackerRequest({ blockNumber: '0x300' });
await withNetworkClient(
{ providerType },
async ({ makeRpcCall, blockTracker }) => {
await makeRpcCall(request);
expect(blockTracker.getCurrentBlock()).toStrictEqual('0x300');
},
);
});
});
});
describe('eth_getTransactionReceipt', () => {
it("refreshes the block tracker's current block if it is less than the block number that comes back in the response", async () => {
const method = 'eth_getTransactionReceipt';
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' });
// This is our request.
comms.mockRpcCall({
request,
response: {
result: {
blockNumber: '0x200',
},
},
});
comms.mockNextBlockTrackerRequest({ blockNumber: '0x300' });
await withNetworkClient(
{ providerType },
async ({ makeRpcCall, blockTracker }) => {
await makeRpcCall(request);
expect(blockTracker.getCurrentBlock()).toStrictEqual('0x300');
},
);
});
});
});
describe('eth_chainId', () => {
it('does not hit the RPC endpoint, instead returning the configured chain id', async () => {
const networkId = await withNetworkClient(
{ providerType: 'custom', customChainId: '0x1' },
({ makeRpcCall }) => {
return makeRpcCall({ method: 'eth_chainId' });
},
);
expect(networkId).toStrictEqual('0x1');
});
});
});
});
describe('methods not included in the Ethereum JSON-RPC spec', () => {
describe('methods not handled by middleware', () => {
const notHandledByMiddleware = [
// This list is presented in the same order as in the network client
// tests on the core side.
{ name: 'net_listening', numberOfParameters: 0 },
{ name: 'eth_subscribe', numberOfParameters: 1 },
{ name: 'eth_unsubscribe', numberOfParameters: 1 },
{ name: 'custom_rpc_method', numberOfParameters: 1 },
{ name: 'net_peerCount', numberOfParameters: 0 },
{ name: 'parity_nextNonce', numberOfParameters: 1 },
];
notHandledByMiddleware.forEach(({ name, numberOfParameters }) => {
describe(`method name: ${name}`, () => {
testsForRpcMethodNotHandledByMiddleware(name, {
providerType,
numberOfParameters,
});
});
});
});
describe('methods that assume there is no block param', () => {
const assumingNoBlockParam = [
{ name: 'web3_clientVersion', numberOfParameters: 0 },
{ name: 'eth_protocolVersion', numberOfParameters: 0 },
];
assumingNoBlockParam.forEach(({ name, numberOfParameters }) =>
describe(`method name: ${name}`, () => {
testsForRpcMethodAssumingNoBlockParam(name, {
providerType,
numberOfParameters,
});
}),
);
});
describe('other methods', () => {
describe('net_version', () => {
// The Infura middleware includes `net_version` in its scaffold
// middleware, whereas the custom RPC middleware does not.
if (providerType === 'infura') {
it('does not hit Infura, instead returning the network ID that maps to the Infura network, as a decimal string', async () => {
const networkId = await withNetworkClient(
{ providerType: 'infura', infuraNetwork: 'goerli' },
({ makeRpcCall }) => {
return makeRpcCall({
method: 'net_version',
});
},
);
expect(networkId).toStrictEqual('5');
});
} else {
it('hits the RPC endpoint', async () => {
await withMockedCommunications(
{ providerType: 'custom' },
async (comms) => {
comms.mockRpcCall({
request: { method: 'net_version' },
response: { result: '1' },
});
const networkId = await withNetworkClient(
{ providerType: 'custom' },
({ makeRpcCall }) => {
return makeRpcCall({
method: 'net_version',
});
},
);
expect(networkId).toStrictEqual('1');
},
);
});
}
});
});
});
}

View File

@ -1,11 +0,0 @@
import { PollingBlockTracker } from 'eth-block-tracker';
/**
* Acts like a PollingBlockTracker, but doesn't start the polling loop or
* make any requests.
*/
export class FakeBlockTracker extends PollingBlockTracker {
async _start() {
// do nothing
}
}

View File

@ -1,206 +0,0 @@
import { inspect, isDeepStrictEqual } from 'util';
import {
JsonRpcEngine,
JsonRpcRequest,
JsonRpcResponse,
} from 'json-rpc-engine';
import { SafeEventEmitterProvider } from '@metamask/eth-json-rpc-provider/dist/safe-event-emitter-provider';
// Store this in case it gets stubbed later
const originalSetTimeout = global.setTimeout;
/**
* An object that allows specifying the behavior of a specific invocation of
* `sendAsync`. The `method` always identifies the stub, but the behavior
* may be specified multiple ways: `sendAsync` can either return a promise or
* throw an error, and if it returns a promise, that promise can either be
* resolved with a response object or reject with an error.
*
* @property request - Looks for a request matching these specifications.
* @property request.method - The RPC method to which this stub will be matched.
* @property request.params - The params to which this stub will be matched.
* @property response - Instructs `sendAsync` to return a promise that resolves
* with a response object.
* @property response.result - Specifies a successful response, with this as the
* `result`.
* @property response.error - Specifies an error response, with this as the
* `error`.
* @property error - Instructs `sendAsync` to return a promise that rejects with
* this error.
* @property implementation - Allows overriding `sendAsync` entirely. Useful if
* you want it to throw an error.
* @property delay - The amount of time that will pass after the callback is
* called with the response.
* @property discardAfterMatching - Usually after the stub matches a request, it
* is discarded, but setting this to true prevents that from happening. True by
* default.
* @property beforeCompleting - Sometimes it is useful to do something after the
* request is kicked off but before it ends (or, in terms of a `fetch` promise,
* when the promise is initiated but before it is resolved). You can pass an
* (async) function for this option to do this.
*/
export type FakeProviderStub = {
request: {
method: string;
params?: any[];
};
delay?: number;
discardAfterMatching?: boolean;
beforeCompleting?: () => void | Promise<void>;
} & (
| {
response: { result: any } | { error: string };
}
| {
error: unknown;
}
| {
implementation: () => void;
}
);
/**
* The set of options that the FakeProviderEngine constructor takes.
*
* @property stubs - A set of objects that allow specifying the behavior
* of specific invocations of `sendAsync` matching a `method`.
*/
interface FakeProviderEngineOptions {
stubs?: FakeProviderStub[];
}
/**
* FakeProviderEngine is an implementation of the provider that
* NetworkController exposes, which is actually an instance of
* Web3ProviderEngine (from the `web3-provider-engine` package). Hence it
* supports the same interface as Web3ProviderEngine, except that fake responses
* for any RPC methods that are accessed can be supplied via an API that is more
* succinct than using Jest's mocking API.
*/
// NOTE: We shouldn't need to extend from the "real" provider here, but
// we'd need a `SafeEventEmitterProvider` _interface_ and that doesn't exist (at
// least not yet).
export class FakeProvider extends SafeEventEmitterProvider {
calledStubs: FakeProviderStub[];
#originalStubs: FakeProviderStub[];
#stubs: FakeProviderStub[];
/**
* Makes a new instance of the fake provider.
*
* @param options - The options.
* @param options.stubs - A set of objects that allow specifying the behavior
* of specific invocations of `sendAsync` matching a `method`.
*/
constructor({ stubs = [] }: FakeProviderEngineOptions) {
super({ engine: new JsonRpcEngine() });
this.#originalStubs = stubs;
this.#stubs = this.#originalStubs.slice();
this.calledStubs = [];
}
send = (
payload: JsonRpcRequest<any>,
callback: (error: unknown, response?: JsonRpcResponse<any>) => void,
) => {
return this.#handleSend(payload, callback);
};
sendAsync = (
payload: JsonRpcRequest<any>,
callback: (error: unknown, response?: JsonRpcResponse<any>) => void,
) => {
return this.#handleSend(payload, callback);
};
#handleSend(
payload: JsonRpcRequest<any>,
callback: (error: unknown, response?: JsonRpcResponse<any>) => void,
) {
if (Array.isArray(payload)) {
throw new Error("Arrays aren't supported");
}
const index = this.#stubs.findIndex((stub) => {
return (
stub.request.method === payload.method &&
(!('params' in stub.request) ||
isDeepStrictEqual(stub.request.params, payload.params))
);
});
if (index === -1) {
const matchingCalledStubs = this.calledStubs.filter((stub) => {
return (
stub.request.method === payload.method &&
(!('params' in stub.request) ||
isDeepStrictEqual(stub.request.params, payload.params))
);
});
let message = `Could not find any stubs matching: ${inspect(payload, {
depth: null,
})}`;
if (matchingCalledStubs.length > 0) {
message += `\n\nIt appears the following stubs were defined, but have been called already:\n\n${inspect(
matchingCalledStubs,
{ depth: null },
)}`;
}
throw new Error(message);
} else {
const stub = this.#stubs[index];
if (stub.discardAfterMatching !== false) {
this.#stubs.splice(index, 1);
}
if (stub.delay) {
originalSetTimeout(() => {
this.#handleRequest(stub, callback);
}, stub.delay);
} else {
this.#handleRequest(stub, callback);
}
this.calledStubs.push({ ...stub });
}
}
async #handleRequest(
stub: FakeProviderStub,
callback: (error: unknown, response?: JsonRpcResponse<any>) => void,
) {
if (stub.beforeCompleting) {
await stub.beforeCompleting();
}
if ('implementation' in stub) {
stub.implementation();
return;
}
if ('response' in stub) {
if ('result' in stub.response) {
callback(null, {
jsonrpc: '2.0',
id: 1,
result: stub.response.result,
});
} else if ('error' in stub.response) {
callback(null, {
jsonrpc: '2.0',
id: 1,
error: {
code: -999,
message: stub.response.error,
},
});
}
} else if ('error' in stub) {
callback(stub.error);
}
}
}

View File

@ -147,9 +147,9 @@ export default class SwapsController {
this.indexOfNewestCallInFlight = 0;
this.ethersProvider = new Web3Provider(provider);
this._currentNetworkId = networkController.store.getState().networkId;
this._currentNetworkId = networkController.state.networkId;
onNetworkStateChange(() => {
const { networkId, networkStatus } = networkController.store.getState();
const { networkId, networkStatus } = networkController.state;
if (
networkStatus === NetworkStatus.Available &&
networkId !== this._currentNetworkId

View File

@ -100,11 +100,9 @@ const MOCK_GET_BUFFERED_GAS_LIMIT = async () => ({
function getMockNetworkController() {
return {
store: {
getState: sinon.stub().returns({
networkId: NETWORK_IDS.GOERLI,
networkStatus: NetworkStatus.Available,
}),
state: {
networkId: NETWORK_IDS.GOERLI,
networkStatus: NetworkStatus.Available,
},
};
}
@ -208,7 +206,10 @@ describe('SwapsController', function () {
it('should replace ethers instance when network changes', function () {
const networkController = getMockNetworkController();
const onNetworkStateChange = sinon.stub();
let networkStateChangeListener;
const onNetworkStateChange = (listener) => {
networkStateChangeListener = listener;
};
const swapsController = new SwapsController({
getBufferedGasLimit: MOCK_GET_BUFFERED_GAS_LIMIT,
networkController,
@ -220,13 +221,12 @@ describe('SwapsController', function () {
getCurrentChainId: getCurrentChainIdStub,
});
const currentEthersInstance = swapsController.ethersProvider;
const changeNetwork = onNetworkStateChange.getCall(0).args[0];
networkController.store.getState.returns({
networkController.state = {
networkId: NETWORK_IDS.MAINNET,
networkStatus: NetworkStatus.Available,
});
changeNetwork(NETWORK_IDS.MAINNET);
};
networkStateChangeListener();
const newEthersInstance = swapsController.ethersProvider;
assert.notStrictEqual(
@ -238,7 +238,10 @@ describe('SwapsController', function () {
it('should not replace ethers instance when network changes to loading', function () {
const networkController = getMockNetworkController();
const onNetworkStateChange = sinon.stub();
let networkStateChangeListener;
const onNetworkStateChange = (listener) => {
networkStateChangeListener = listener;
};
const swapsController = new SwapsController({
getBufferedGasLimit: MOCK_GET_BUFFERED_GAS_LIMIT,
networkController,
@ -250,13 +253,12 @@ describe('SwapsController', function () {
getCurrentChainId: getCurrentChainIdStub,
});
const currentEthersInstance = swapsController.ethersProvider;
const changeNetwork = onNetworkStateChange.getCall(0).args[0];
networkController.store.getState.returns({
networkController.state = {
networkId: null,
networkStatus: NetworkStatus.Unknown,
});
changeNetwork('loading');
};
networkStateChangeListener();
const newEthersInstance = swapsController.ethersProvider;
assert.strictEqual(
@ -268,7 +270,10 @@ describe('SwapsController', function () {
it('should not replace ethers instance when network changes to the same network', function () {
const networkController = getMockNetworkController();
const onNetworkStateChange = sinon.stub();
let networkStateChangeListener;
const onNetworkStateChange = (listener) => {
networkStateChangeListener = listener;
};
const swapsController = new SwapsController({
getBufferedGasLimit: MOCK_GET_BUFFERED_GAS_LIMIT,
networkController,
@ -280,13 +285,12 @@ describe('SwapsController', function () {
getCurrentChainId: getCurrentChainIdStub,
});
const currentEthersInstance = swapsController.ethersProvider;
const changeNetwork = onNetworkStateChange.getCall(0).args[0];
networkController.store.getState.returns({
networkController.state = {
networkId: NETWORK_IDS.GOERLI,
networkStatus: NetworkStatus.Available,
});
changeNetwork(NETWORK_IDS.GOERLI);
};
networkStateChangeListener();
const newEthersInstance = swapsController.ethersProvider;
assert.strictEqual(

View File

@ -39,6 +39,7 @@ import {
} from '@metamask/assets-controllers';
import { PhishingController } from '@metamask/phishing-controller';
import { AnnouncementController } from '@metamask/announcement-controller';
import { NetworkController } from '@metamask/network-controller';
import { GasFeeController } from '@metamask/gas-fee-controller';
import {
PermissionController,
@ -105,6 +106,7 @@ import {
import {
CHAIN_IDS,
NETWORK_TYPES,
TEST_NETWORK_TICKER_MAP,
NetworkStatus,
} from '../../shared/constants/network';
import { HardwareDeviceNames } from '../../shared/constants/hardware-wallets';
@ -167,7 +169,6 @@ import createTabIdMiddleware from './lib/createTabIdMiddleware';
import createOnboardingMiddleware from './lib/createOnboardingMiddleware';
import { setupMultiplex } from './lib/stream-utils';
import EnsController from './controllers/ens';
import { NetworkController } from './controllers/network';
import PreferencesController from './controllers/preferences';
import AppStateController from './controllers/app-state';
import CachedBalancesController from './controllers/cached-balances';
@ -302,6 +303,7 @@ export default class MetamaskController extends EventEmitter {
const networkControllerMessenger = this.controllerMessenger.getRestricted({
name: 'NetworkController',
allowedEvents: [
'NetworkController:stateChange',
'NetworkController:networkWillChange',
'NetworkController:networkDidChange',
'NetworkController:infuraIsBlocked',
@ -309,9 +311,34 @@ export default class MetamaskController extends EventEmitter {
],
});
let initialProviderConfig;
if (process.env.IN_TEST) {
initialProviderConfig = {
type: NETWORK_TYPES.RPC,
rpcUrl: 'http://localhost:8545',
chainId: '0x539',
nickname: 'Localhost 8545',
ticker: 'ETH',
};
} else if (
process.env.METAMASK_DEBUG ||
process.env.METAMASK_ENVIRONMENT === 'test'
) {
initialProviderConfig = {
type: NETWORK_TYPES.GOERLI,
chainId: CHAIN_IDS.GOERLI,
ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.GOERLI],
};
}
const initialNetworkControllerState = initialProviderConfig
? {
providerConfig: initialProviderConfig,
...initState.NetworkController,
}
: initState.NetworkController;
this.networkController = new NetworkController({
messenger: networkControllerMessenger,
state: initState.NetworkController,
state: initialNetworkControllerState,
infuraProjectId: opts.infuraProjectId,
trackMetaMetricsEvent: (...args) =>
this.metaMetricsController.trackEvent(...args),
@ -331,13 +358,10 @@ export default class MetamaskController extends EventEmitter {
});
this.tokenListController = new TokenListController({
chainId: this.networkController.store.getState().providerConfig.chainId,
chainId: this.networkController.state.providerConfig.chainId,
preventPollingOnNetworkRestart: initState.TokenListController
? initState.TokenListController.preventPollingOnNetworkRestart
: true,
onNetworkStateChange: this.networkController.store.subscribe.bind(
this.networkController.store,
),
messenger: tokenListMessenger,
state: initState.TokenListController,
});
@ -365,25 +389,28 @@ export default class MetamaskController extends EventEmitter {
await updateCurrentLocale(currentLocale);
});
const tokensControllerMessenger = this.controllerMessenger.getRestricted({
name: 'TokensController',
allowedActions: ['ApprovalController:addRequest'],
allowedEvents: ['NetworkController:stateChange'],
});
this.tokensController = new TokensController({
chainId: this.networkController.store.getState().providerConfig.chainId,
messenger: tokensControllerMessenger,
chainId: this.networkController.state.providerConfig.chainId,
onPreferencesStateChange: this.preferencesController.store.subscribe.bind(
this.preferencesController.store,
),
onNetworkStateChange: this.networkController.store.subscribe.bind(
this.networkController.store,
onNetworkStateChange: networkControllerMessenger.subscribe.bind(
networkControllerMessenger,
'NetworkController:stateChange',
),
config: { provider: this.provider },
state: initState.TokensController,
messenger: this.controllerMessenger.getRestricted({
name: 'TokensController',
allowedActions: [`${this.approvalController.name}:addRequest`],
}),
});
this.assetsContractController = new AssetsContractController(
{
chainId: this.networkController.store.getState().providerConfig.chainId,
chainId: this.networkController.state.providerConfig.chainId,
onPreferencesStateChange: (listener) =>
this.preferencesController.store.subscribe(listener),
// This handler is misnamed, and is a known issue that will be resolved
@ -398,7 +425,7 @@ export default class MetamaskController extends EventEmitter {
networkControllerMessenger.subscribe(
'NetworkController:networkDidChange',
() => {
const networkState = this.networkController.store.getState();
const networkState = this.networkController.state;
return cb(networkState);
},
),
@ -416,13 +443,14 @@ export default class MetamaskController extends EventEmitter {
this.nftController = new NftController(
{
messenger: nftControllerMessenger,
chainId: this.networkController.store.getState().providerConfig.chainId,
chainId: this.networkController.state.providerConfig.chainId,
onPreferencesStateChange:
this.preferencesController.store.subscribe.bind(
this.preferencesController.store,
),
onNetworkStateChange: this.networkController.store.subscribe.bind(
this.networkController.store,
onNetworkStateChange: networkControllerMessenger.subscribe.bind(
networkControllerMessenger,
'NetworkController:stateChange',
),
getERC721AssetName:
this.assetsContractController.getERC721AssetName.bind(
@ -469,13 +497,14 @@ export default class MetamaskController extends EventEmitter {
this.nftController.setApiKey(process.env.OPENSEA_KEY);
this.nftDetectionController = new NftDetectionController({
chainId: this.networkController.store.getState().providerConfig.chainId,
chainId: this.networkController.state.providerConfig.chainId,
onNftsStateChange: (listener) => this.nftController.subscribe(listener),
onPreferencesStateChange: this.preferencesController.store.subscribe.bind(
this.preferencesController.store,
),
onNetworkStateChange: this.networkController.store.subscribe.bind(
this.networkController.store,
onNetworkStateChange: networkControllerMessenger.subscribe.bind(
networkControllerMessenger,
'NetworkController:stateChange',
),
getOpenSeaApiKey: () => this.nftController.openSeaApiKey,
getBalancesInSingleCall:
@ -494,12 +523,11 @@ export default class MetamaskController extends EventEmitter {
'NetworkController:networkDidChange',
),
getNetworkIdentifier: () => {
const { type, rpcUrl } =
this.networkController.store.getState().providerConfig;
const { type, rpcUrl } = this.networkController.state.providerConfig;
return type === NETWORK_TYPES.RPC ? rpcUrl : type;
},
getCurrentChainId: () =>
this.networkController.store.getState().providerConfig.chainId,
this.networkController.state.providerConfig.chainId,
version: this.platform.getVersion(),
environment: process.env.METAMASK_ENVIRONMENT,
extension: this.extension,
@ -531,7 +559,7 @@ export default class MetamaskController extends EventEmitter {
onNetworkStateChange: (eventHandler) => {
networkControllerMessenger.subscribe(
'NetworkController:networkDidChange',
() => eventHandler(this.networkController.store.getState()),
() => eventHandler(this.networkController.state),
);
},
getCurrentNetworkEIP1559Compatibility:
@ -543,12 +571,10 @@ export default class MetamaskController extends EventEmitter {
legacyAPIEndpoint: `${gasApiBaseUrl}/networks/<chain_id>/gasPrices`,
EIP1559APIEndpoint: `${gasApiBaseUrl}/networks/<chain_id>/suggestedGasFees`,
getCurrentNetworkLegacyGasAPICompatibility: () => {
const { chainId } =
this.networkController.store.getState().providerConfig;
const { chainId } = this.networkController.state.providerConfig;
return process.env.IN_TEST || chainId === CHAIN_IDS.MAINNET;
},
getChainId: () =>
this.networkController.store.getState().providerConfig.chainId,
getChainId: () => this.networkController.state.providerConfig.chainId,
});
this.qrHardwareKeyring = new QRHardwareKeyring();
@ -577,8 +603,7 @@ export default class MetamaskController extends EventEmitter {
messenger: currencyRateMessenger,
state: {
...initState.CurrencyController,
nativeCurrency:
this.networkController.store.getState().providerConfig.ticker,
nativeCurrency: this.networkController.state.providerConfig.ticker,
},
});
@ -606,7 +631,7 @@ export default class MetamaskController extends EventEmitter {
// token exchange rate tracker
this.tokenRatesController = new TokenRatesController(
{
chainId: this.networkController.store.getState().providerConfig.chainId,
chainId: this.networkController.state.providerConfig.chainId,
onTokensStateChange: (listener) =>
this.tokensController.subscribe(listener),
onCurrencyRateStateChange: (listener) =>
@ -614,8 +639,9 @@ export default class MetamaskController extends EventEmitter {
`${this.currencyRateController.name}:stateChange`,
listener,
),
onNetworkStateChange: this.networkController.store.subscribe.bind(
this.networkController.store,
onNetworkStateChange: networkControllerMessenger.subscribe.bind(
networkControllerMessenger,
'NetworkController:stateChange',
),
},
{
@ -645,7 +671,7 @@ export default class MetamaskController extends EventEmitter {
this.ensController = new EnsController({
provider: this.provider,
getCurrentChainId: () =>
this.networkController.store.getState().providerConfig.chainId,
this.networkController.state.providerConfig.chainId,
onNetworkDidChange: networkControllerMessenger.subscribe.bind(
networkControllerMessenger,
'NetworkController:networkDidChange',
@ -663,7 +689,7 @@ export default class MetamaskController extends EventEmitter {
'NetworkController:networkDidChange',
),
getCurrentChainId: () =>
this.networkController.store.getState().providerConfig.chainId,
this.networkController.state.providerConfig.chainId,
preferencesController: this.preferencesController,
onboardingController: this.onboardingController,
initState: initState.IncomingTransactionsController,
@ -674,10 +700,9 @@ export default class MetamaskController extends EventEmitter {
provider: this.provider,
blockTracker: this.blockTracker,
getCurrentChainId: () =>
this.networkController.store.getState().providerConfig.chainId,
this.networkController.state.providerConfig.chainId,
getNetworkIdentifier: () => {
const { type, rpcUrl } =
this.networkController.store.getState().providerConfig;
const { type, rpcUrl } = this.networkController.state.providerConfig;
return type === NETWORK_TYPES.RPC ? rpcUrl : type;
},
preferencesController: this.preferencesController,
@ -714,7 +739,7 @@ export default class MetamaskController extends EventEmitter {
this.cachedBalancesController = new CachedBalancesController({
accountTracker: this.accountTracker,
getCurrentChainId: () =>
this.networkController.store.getState().providerConfig.chainId,
this.networkController.state.providerConfig.chainId,
initState: initState.CachedBalancesController,
});
@ -988,7 +1013,13 @@ export default class MetamaskController extends EventEmitter {
});
///: END:ONLY_INCLUDE_IN
const detectTokensControllerMessenger =
this.controllerMessenger.getRestricted({
name: 'DetectTokensController',
allowedEvents: ['NetworkController:stateChange'],
});
this.detectTokensController = new DetectTokensController({
messenger: detectTokensControllerMessenger,
preferences: this.preferencesController,
tokensController: this.tokensController,
assetsContractController: this.assetsContractController,
@ -1039,29 +1070,24 @@ export default class MetamaskController extends EventEmitter {
initState:
initState.TransactionController || initState.TransactionManager,
getPermittedAccounts: this.getPermittedAccounts.bind(this),
getProviderConfig: () =>
this.networkController.store.getState().providerConfig,
getProviderConfig: () => this.networkController.state.providerConfig,
getCurrentNetworkEIP1559Compatibility:
this.networkController.getEIP1559Compatibility.bind(
this.networkController,
),
getCurrentAccountEIP1559Compatibility:
this.getCurrentAccountEIP1559Compatibility.bind(this),
getNetworkId: () => this.networkController.store.getState().networkId,
getNetworkStatus: () =>
this.networkController.store.getState().networkStatus,
getNetworkId: () => this.networkController.state.networkId,
getNetworkStatus: () => this.networkController.state.networkStatus,
onNetworkStateChange: (listener) => {
let previousNetworkId =
this.networkController.store.getState().networkId;
this.networkController.store.subscribe((state) => {
if (previousNetworkId !== state.networkId) {
listener();
previousNetworkId = state.networkId;
}
});
networkControllerMessenger.subscribe(
'NetworkController:stateChange',
() => listener(),
({ networkId }) => networkId,
);
},
getCurrentChainId: () =>
this.networkController.store.getState().providerConfig.chainId,
this.networkController.state.providerConfig.chainId,
preferencesStore: this.preferencesController.store,
txHistoryLimit: 60,
signTransaction: this.keyringController.signTransaction.bind(
@ -1139,8 +1165,7 @@ export default class MetamaskController extends EventEmitter {
const txMeta = this.txController.txStateManager.getTransaction(txId);
let rpcPrefs = {};
if (txMeta.chainId) {
const { networkConfigurations } =
this.networkController.store.getState();
const { networkConfigurations } = this.networkController.state;
const matchingNetworkConfig = Object.values(
networkConfigurations,
).find(
@ -1221,8 +1246,7 @@ export default class MetamaskController extends EventEmitter {
networkControllerMessenger.subscribe(
'NetworkController:networkDidChange',
async () => {
const { ticker } =
this.networkController.store.getState().providerConfig;
const { ticker } = this.networkController.state.providerConfig;
try {
await this.currencyRateController.setNativeCurrency(ticker);
} catch (error) {
@ -1277,7 +1301,7 @@ export default class MetamaskController extends EventEmitter {
getAllState: this.getState.bind(this),
securityProviderRequest: this.securityProviderRequest.bind(this),
getCurrentChainId: () =>
this.networkController.store.getState().providerConfig.chainId,
this.networkController.state.providerConfig.chainId,
});
this.signatureController.hub.on(
@ -1301,14 +1325,15 @@ export default class MetamaskController extends EventEmitter {
this.txController.txGasUtil,
),
networkController: this.networkController,
onNetworkStateChange: (listener) =>
this.networkController.store.subscribe(listener),
onNetworkStateChange: networkControllerMessenger.subscribe.bind(
networkControllerMessenger,
'NetworkController:stateChange',
),
provider: this.provider,
getProviderConfig: () =>
this.networkController.store.getState().providerConfig,
getProviderConfig: () => this.networkController.state.providerConfig,
getTokenRatesState: () => this.tokenRatesController.state,
getCurrentChainId: () =>
this.networkController.store.getState().providerConfig.chainId,
this.networkController.state.providerConfig.chainId,
getEIP1559GasFeeEstimates:
this.gasFeeController.fetchGasFeeEstimates.bind(
this.gasFeeController,
@ -1318,19 +1343,11 @@ export default class MetamaskController extends EventEmitter {
);
this.smartTransactionsController = new SmartTransactionsController(
{
onNetworkStateChange: (cb) => {
this.networkController.store.subscribe((networkState) => {
const modifiedNetworkState = {
...networkState,
providerConfig: {
...networkState.providerConfig,
},
};
return cb(modifiedNetworkState);
});
},
getNetwork: () =>
this.networkController.store.getState().networkId ?? 'loading',
onNetworkStateChange: networkControllerMessenger.subscribe.bind(
networkControllerMessenger,
'NetworkController:stateChange',
),
getNetwork: () => this.networkController.state.networkId ?? 'loading',
getNonceLock: this.txController.nonceTracker.getNonceLock.bind(
this.txController.nonceTracker,
),
@ -1482,7 +1499,7 @@ export default class MetamaskController extends EventEmitter {
MetaMetricsController: this.metaMetricsController.store,
AddressBookController: this.addressBookController,
CurrencyController: this.currencyRateController,
NetworkController: this.networkController.store,
NetworkController: this.networkController,
CachedBalancesController: this.cachedBalancesController.store,
AlertController: this.alertController.store,
OnboardingController: this.onboardingController.store,
@ -1520,7 +1537,7 @@ export default class MetamaskController extends EventEmitter {
this.memStore = new ComposableObservableStore({
config: {
AppStateController: this.appStateController.store,
NetworkController: this.networkController.store,
NetworkController: this.networkController,
CachedBalancesController: this.cachedBalancesController.store,
KeyringController: this.keyringController.memStore,
PreferencesController: this.preferencesController.store,
@ -1906,7 +1923,7 @@ export default class MetamaskController extends EventEmitter {
updatePublicConfigStore(this.getState());
function updatePublicConfigStore(memState) {
const { chainId } = networkController.store.getState().providerConfig;
const { chainId } = networkController.state.providerConfig;
if (memState.networkStatus === NetworkStatus.Available) {
publicConfigStore.putState(selectPublicState(chainId, memState));
}
@ -1947,7 +1964,7 @@ export default class MetamaskController extends EventEmitter {
getProviderNetworkState(memState) {
const { networkId } = memState || this.getState();
return {
chainId: this.networkController.store.getState().providerConfig.chainId,
chainId: this.networkController.state.providerConfig.chainId,
networkVersion: networkId ?? 'loading',
};
}
@ -2975,8 +2992,7 @@ export default class MetamaskController extends EventEmitter {
this.appStateController.setTrezorModel(model);
}
keyring.network =
this.networkController.store.getState().providerConfig.type;
keyring.network = this.networkController.state.providerConfig.type;
return keyring;
}
@ -3862,12 +3878,12 @@ export default class MetamaskController extends EventEmitter {
),
getCurrentChainId: () =>
this.networkController.store.getState().providerConfig.chainId,
this.networkController.state.providerConfig.chainId,
getCurrentRpcUrl: () =>
this.networkController.store.getState().providerConfig.rpcUrl,
this.networkController.state.providerConfig.rpcUrl,
// network configuration-related
getNetworkConfigurations: () =>
this.networkController.store.getState().networkConfigurations,
this.networkController.state.networkConfigurations,
upsertNetworkConfiguration:
this.networkController.upsertNetworkConfiguration.bind(
this.networkController,
@ -4286,7 +4302,7 @@ export default class MetamaskController extends EventEmitter {
* @returns {object} rpcInfo found in the network configurations list
*/
findNetworkConfigurationBy(rpcInfo) {
const { networkConfigurations } = this.networkController.store.getState();
const { networkConfigurations } = this.networkController.state;
const networkConfiguration = Object.values(networkConfigurations).find(
(configuration) => {
return Object.keys(rpcInfo).some((key) => {
@ -4502,9 +4518,7 @@ export default class MetamaskController extends EventEmitter {
if (transactionSecurityCheckEnabled) {
const chainId = Number(
hexToDecimal(
this.networkController.store.getState().providerConfig.chainId,
),
hexToDecimal(this.networkController.state.providerConfig.chainId),
);
try {

View File

@ -1,9 +1,6 @@
module.exports = {
collectCoverageFrom: [
'<rootDir>/app/scripts/constants/error-utils.js',
'<rootDir>/app/scripts/controllers/network/**/*.js',
'<rootDir>/app/scripts/controllers/network/**/*.ts',
'!<rootDir>/app/scripts/controllers/network/**/test/*.ts',
'<rootDir>/app/scripts/controllers/permissions/**/*.js',
'<rootDir>/app/scripts/controllers/sign.ts',
'<rootDir>/app/scripts/controllers/decrypt-message.ts',
@ -40,8 +37,6 @@ module.exports = {
'<rootDir>/app/scripts/constants/error-utils.test.js',
'<rootDir>/app/scripts/controllers/app-state.test.js',
'<rootDir>/app/scripts/controllers/mmi-controller.test.js',
'<rootDir>/app/scripts/controllers/network/**/*.test.js',
'<rootDir>/app/scripts/controllers/network/**/*.test.ts',
'<rootDir>/app/scripts/controllers/permissions/**/*.test.js',
'<rootDir>/app/scripts/controllers/sign.test.ts',
'<rootDir>/app/scripts/controllers/decrypt-message.test.ts',

View File

@ -905,50 +905,6 @@
"fetch": true
}
},
"@metamask/eth-json-rpc-infura": {
"globals": {
"setTimeout": true
},
"packages": {
"@metamask/eth-json-rpc-infura>@metamask/utils": true,
"@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": true,
"eth-rpc-errors": true,
"json-rpc-engine": true,
"node-fetch": true
}
},
"@metamask/eth-json-rpc-infura>@metamask/utils": {
"globals": {
"TextDecoder": true,
"TextEncoder": true
},
"packages": {
"browserify>buffer": true,
"nock>debug": true,
"semver": true,
"superstruct": true
}
},
"@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": {
"globals": {
"URL": true,
"btoa": true,
"console.error": true,
"fetch": true,
"setTimeout": true
},
"packages": {
"@metamask/eth-json-rpc-infura>@metamask/utils": true,
"@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true,
"@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true,
"@metamask/safe-event-emitter": true,
"browserify>browser-resolve": true,
"eth-rpc-errors": true,
"json-rpc-engine": true,
"lavamoat>json-stable-stringify": true,
"vinyl>clone": true
}
},
"@metamask/eth-json-rpc-middleware": {
"globals": {
"URL": true,
@ -977,12 +933,6 @@
"superstruct": true
}
},
"@metamask/eth-json-rpc-provider": {
"packages": {
"@metamask/safe-event-emitter": true,
"json-rpc-engine": true
}
},
"@metamask/eth-keyring-controller": {
"packages": {
"@metamask/browser-passworder": true,
@ -1588,6 +1538,100 @@
"browserify>url": true
}
},
"@metamask/network-controller": {
"globals": {
"URL": true,
"btoa": true,
"fetch": true,
"setTimeout": true
},
"packages": {
"@metamask/base-controller": true,
"@metamask/controller-utils": true,
"@metamask/eth-json-rpc-middleware": true,
"@metamask/network-controller>@metamask/eth-json-rpc-infura": true,
"@metamask/network-controller>@metamask/eth-json-rpc-provider": true,
"@metamask/network-controller>@metamask/swappable-obj-proxy": true,
"@metamask/network-controller>eth-block-tracker": true,
"@metamask/utils": true,
"browserify>assert": true,
"eth-query": true,
"eth-rpc-errors": true,
"json-rpc-engine": true,
"uuid": true
}
},
"@metamask/network-controller>@metamask/eth-json-rpc-infura": {
"globals": {
"setTimeout": true
},
"packages": {
"@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true,
"@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": true,
"eth-rpc-errors": true,
"json-rpc-engine": true,
"node-fetch": true
}
},
"@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": {
"globals": {
"TextDecoder": true,
"TextEncoder": true
},
"packages": {
"browserify>buffer": true,
"nock>debug": true,
"semver": true,
"superstruct": true
}
},
"@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": {
"globals": {
"URL": true,
"btoa": true,
"console.error": true,
"fetch": true,
"setTimeout": true
},
"packages": {
"@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true,
"@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true,
"@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true,
"@metamask/safe-event-emitter": true,
"browserify>browser-resolve": true,
"eth-rpc-errors": true,
"json-rpc-engine": true,
"lavamoat>json-stable-stringify": true,
"vinyl>clone": true
}
},
"@metamask/network-controller>@metamask/eth-json-rpc-provider": {
"packages": {
"@metamask/safe-event-emitter": true,
"json-rpc-engine": true
}
},
"@metamask/network-controller>eth-block-tracker": {
"globals": {
"clearTimeout": true,
"console.error": true,
"setTimeout": true
},
"packages": {
"@metamask/network-controller>eth-block-tracker>@metamask/safe-event-emitter": true,
"@metamask/network-controller>eth-block-tracker>pify": true,
"@metamask/utils": true,
"eth-query>json-rpc-random-id": true
}
},
"@metamask/network-controller>eth-block-tracker>@metamask/safe-event-emitter": {
"globals": {
"setTimeout": true
},
"packages": {
"browserify>events": true
}
},
"@metamask/notification-controller>nanoid": {
"globals": {
"crypto.getRandomValues": true
@ -2972,27 +3016,6 @@
"postMessage": true
}
},
"eth-block-tracker": {
"globals": {
"clearTimeout": true,
"console.error": true,
"setTimeout": true
},
"packages": {
"@metamask/utils": true,
"eth-block-tracker>@metamask/safe-event-emitter": true,
"eth-block-tracker>pify": true,
"eth-query>json-rpc-random-id": true
}
},
"eth-block-tracker>@metamask/safe-event-emitter": {
"globals": {
"setTimeout": true
},
"packages": {
"browserify>events": true
}
},
"eth-ens-namehash": {
"globals": {
"name": "write"

View File

@ -976,50 +976,6 @@
"define": true
}
},
"@metamask/eth-json-rpc-infura": {
"globals": {
"setTimeout": true
},
"packages": {
"@metamask/eth-json-rpc-infura>@metamask/utils": true,
"@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": true,
"eth-rpc-errors": true,
"json-rpc-engine": true,
"node-fetch": true
}
},
"@metamask/eth-json-rpc-infura>@metamask/utils": {
"globals": {
"TextDecoder": true,
"TextEncoder": true
},
"packages": {
"browserify>buffer": true,
"nock>debug": true,
"semver": true,
"superstruct": true
}
},
"@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": {
"globals": {
"URL": true,
"btoa": true,
"console.error": true,
"fetch": true,
"setTimeout": true
},
"packages": {
"@metamask/eth-json-rpc-infura>@metamask/utils": true,
"@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true,
"@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true,
"@metamask/safe-event-emitter": true,
"browserify>browser-resolve": true,
"eth-rpc-errors": true,
"json-rpc-engine": true,
"lavamoat>json-stable-stringify": true,
"vinyl>clone": true
}
},
"@metamask/eth-json-rpc-middleware": {
"globals": {
"URL": true,
@ -1048,12 +1004,6 @@
"superstruct": true
}
},
"@metamask/eth-json-rpc-provider": {
"packages": {
"@metamask/safe-event-emitter": true,
"json-rpc-engine": true
}
},
"@metamask/eth-keyring-controller": {
"packages": {
"@metamask/browser-passworder": true,
@ -1659,6 +1609,100 @@
"browserify>url": true
}
},
"@metamask/network-controller": {
"globals": {
"URL": true,
"btoa": true,
"fetch": true,
"setTimeout": true
},
"packages": {
"@metamask/base-controller": true,
"@metamask/controller-utils": true,
"@metamask/eth-json-rpc-middleware": true,
"@metamask/network-controller>@metamask/eth-json-rpc-infura": true,
"@metamask/network-controller>@metamask/eth-json-rpc-provider": true,
"@metamask/network-controller>@metamask/swappable-obj-proxy": true,
"@metamask/network-controller>eth-block-tracker": true,
"@metamask/utils": true,
"browserify>assert": true,
"eth-query": true,
"eth-rpc-errors": true,
"json-rpc-engine": true,
"uuid": true
}
},
"@metamask/network-controller>@metamask/eth-json-rpc-infura": {
"globals": {
"setTimeout": true
},
"packages": {
"@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true,
"@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": true,
"eth-rpc-errors": true,
"json-rpc-engine": true,
"node-fetch": true
}
},
"@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": {
"globals": {
"TextDecoder": true,
"TextEncoder": true
},
"packages": {
"browserify>buffer": true,
"nock>debug": true,
"semver": true,
"superstruct": true
}
},
"@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": {
"globals": {
"URL": true,
"btoa": true,
"console.error": true,
"fetch": true,
"setTimeout": true
},
"packages": {
"@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true,
"@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true,
"@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true,
"@metamask/safe-event-emitter": true,
"browserify>browser-resolve": true,
"eth-rpc-errors": true,
"json-rpc-engine": true,
"lavamoat>json-stable-stringify": true,
"vinyl>clone": true
}
},
"@metamask/network-controller>@metamask/eth-json-rpc-provider": {
"packages": {
"@metamask/safe-event-emitter": true,
"json-rpc-engine": true
}
},
"@metamask/network-controller>eth-block-tracker": {
"globals": {
"clearTimeout": true,
"console.error": true,
"setTimeout": true
},
"packages": {
"@metamask/network-controller>eth-block-tracker>@metamask/safe-event-emitter": true,
"@metamask/network-controller>eth-block-tracker>pify": true,
"@metamask/utils": true,
"eth-query>json-rpc-random-id": true
}
},
"@metamask/network-controller>eth-block-tracker>@metamask/safe-event-emitter": {
"globals": {
"setTimeout": true
},
"packages": {
"browserify>events": true
}
},
"@metamask/notification-controller": {
"packages": {
"@metamask/base-controller": true,
@ -3498,27 +3542,6 @@
"postMessage": true
}
},
"eth-block-tracker": {
"globals": {
"clearTimeout": true,
"console.error": true,
"setTimeout": true
},
"packages": {
"@metamask/utils": true,
"eth-block-tracker>@metamask/safe-event-emitter": true,
"eth-block-tracker>pify": true,
"eth-query>json-rpc-random-id": true
}
},
"eth-block-tracker>@metamask/safe-event-emitter": {
"globals": {
"setTimeout": true
},
"packages": {
"browserify>events": true
}
},
"eth-ens-namehash": {
"globals": {
"name": "write"

View File

@ -976,50 +976,6 @@
"define": true
}
},
"@metamask/eth-json-rpc-infura": {
"globals": {
"setTimeout": true
},
"packages": {
"@metamask/eth-json-rpc-infura>@metamask/utils": true,
"@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": true,
"eth-rpc-errors": true,
"json-rpc-engine": true,
"node-fetch": true
}
},
"@metamask/eth-json-rpc-infura>@metamask/utils": {
"globals": {
"TextDecoder": true,
"TextEncoder": true
},
"packages": {
"browserify>buffer": true,
"nock>debug": true,
"semver": true,
"superstruct": true
}
},
"@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": {
"globals": {
"URL": true,
"btoa": true,
"console.error": true,
"fetch": true,
"setTimeout": true
},
"packages": {
"@metamask/eth-json-rpc-infura>@metamask/utils": true,
"@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true,
"@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true,
"@metamask/safe-event-emitter": true,
"browserify>browser-resolve": true,
"eth-rpc-errors": true,
"json-rpc-engine": true,
"lavamoat>json-stable-stringify": true,
"vinyl>clone": true
}
},
"@metamask/eth-json-rpc-middleware": {
"globals": {
"URL": true,
@ -1048,12 +1004,6 @@
"superstruct": true
}
},
"@metamask/eth-json-rpc-provider": {
"packages": {
"@metamask/safe-event-emitter": true,
"json-rpc-engine": true
}
},
"@metamask/eth-keyring-controller": {
"packages": {
"@metamask/browser-passworder": true,
@ -1659,6 +1609,100 @@
"browserify>url": true
}
},
"@metamask/network-controller": {
"globals": {
"URL": true,
"btoa": true,
"fetch": true,
"setTimeout": true
},
"packages": {
"@metamask/base-controller": true,
"@metamask/controller-utils": true,
"@metamask/eth-json-rpc-middleware": true,
"@metamask/network-controller>@metamask/eth-json-rpc-infura": true,
"@metamask/network-controller>@metamask/eth-json-rpc-provider": true,
"@metamask/network-controller>@metamask/swappable-obj-proxy": true,
"@metamask/network-controller>eth-block-tracker": true,
"@metamask/utils": true,
"browserify>assert": true,
"eth-query": true,
"eth-rpc-errors": true,
"json-rpc-engine": true,
"uuid": true
}
},
"@metamask/network-controller>@metamask/eth-json-rpc-infura": {
"globals": {
"setTimeout": true
},
"packages": {
"@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true,
"@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": true,
"eth-rpc-errors": true,
"json-rpc-engine": true,
"node-fetch": true
}
},
"@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": {
"globals": {
"TextDecoder": true,
"TextEncoder": true
},
"packages": {
"browserify>buffer": true,
"nock>debug": true,
"semver": true,
"superstruct": true
}
},
"@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": {
"globals": {
"URL": true,
"btoa": true,
"console.error": true,
"fetch": true,
"setTimeout": true
},
"packages": {
"@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true,
"@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true,
"@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true,
"@metamask/safe-event-emitter": true,
"browserify>browser-resolve": true,
"eth-rpc-errors": true,
"json-rpc-engine": true,
"lavamoat>json-stable-stringify": true,
"vinyl>clone": true
}
},
"@metamask/network-controller>@metamask/eth-json-rpc-provider": {
"packages": {
"@metamask/safe-event-emitter": true,
"json-rpc-engine": true
}
},
"@metamask/network-controller>eth-block-tracker": {
"globals": {
"clearTimeout": true,
"console.error": true,
"setTimeout": true
},
"packages": {
"@metamask/network-controller>eth-block-tracker>@metamask/safe-event-emitter": true,
"@metamask/network-controller>eth-block-tracker>pify": true,
"@metamask/utils": true,
"eth-query>json-rpc-random-id": true
}
},
"@metamask/network-controller>eth-block-tracker>@metamask/safe-event-emitter": {
"globals": {
"setTimeout": true
},
"packages": {
"browserify>events": true
}
},
"@metamask/notification-controller": {
"packages": {
"@metamask/base-controller": true,
@ -3498,27 +3542,6 @@
"postMessage": true
}
},
"eth-block-tracker": {
"globals": {
"clearTimeout": true,
"console.error": true,
"setTimeout": true
},
"packages": {
"@metamask/utils": true,
"eth-block-tracker>@metamask/safe-event-emitter": true,
"eth-block-tracker>pify": true,
"eth-query>json-rpc-random-id": true
}
},
"eth-block-tracker>@metamask/safe-event-emitter": {
"globals": {
"setTimeout": true
},
"packages": {
"browserify>events": true
}
},
"eth-ens-namehash": {
"globals": {
"name": "write"

View File

@ -905,50 +905,6 @@
"fetch": true
}
},
"@metamask/eth-json-rpc-infura": {
"globals": {
"setTimeout": true
},
"packages": {
"@metamask/eth-json-rpc-infura>@metamask/utils": true,
"@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": true,
"eth-rpc-errors": true,
"json-rpc-engine": true,
"node-fetch": true
}
},
"@metamask/eth-json-rpc-infura>@metamask/utils": {
"globals": {
"TextDecoder": true,
"TextEncoder": true
},
"packages": {
"browserify>buffer": true,
"nock>debug": true,
"semver": true,
"superstruct": true
}
},
"@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": {
"globals": {
"URL": true,
"btoa": true,
"console.error": true,
"fetch": true,
"setTimeout": true
},
"packages": {
"@metamask/eth-json-rpc-infura>@metamask/utils": true,
"@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true,
"@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true,
"@metamask/safe-event-emitter": true,
"browserify>browser-resolve": true,
"eth-rpc-errors": true,
"json-rpc-engine": true,
"lavamoat>json-stable-stringify": true,
"vinyl>clone": true
}
},
"@metamask/eth-json-rpc-middleware": {
"globals": {
"URL": true,
@ -977,12 +933,6 @@
"superstruct": true
}
},
"@metamask/eth-json-rpc-provider": {
"packages": {
"@metamask/safe-event-emitter": true,
"json-rpc-engine": true
}
},
"@metamask/eth-keyring-controller": {
"packages": {
"@metamask/browser-passworder": true,
@ -1588,6 +1538,100 @@
"browserify>url": true
}
},
"@metamask/network-controller": {
"globals": {
"URL": true,
"btoa": true,
"fetch": true,
"setTimeout": true
},
"packages": {
"@metamask/base-controller": true,
"@metamask/controller-utils": true,
"@metamask/eth-json-rpc-middleware": true,
"@metamask/network-controller>@metamask/eth-json-rpc-infura": true,
"@metamask/network-controller>@metamask/eth-json-rpc-provider": true,
"@metamask/network-controller>@metamask/swappable-obj-proxy": true,
"@metamask/network-controller>eth-block-tracker": true,
"@metamask/utils": true,
"browserify>assert": true,
"eth-query": true,
"eth-rpc-errors": true,
"json-rpc-engine": true,
"uuid": true
}
},
"@metamask/network-controller>@metamask/eth-json-rpc-infura": {
"globals": {
"setTimeout": true
},
"packages": {
"@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true,
"@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": true,
"eth-rpc-errors": true,
"json-rpc-engine": true,
"node-fetch": true
}
},
"@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": {
"globals": {
"TextDecoder": true,
"TextEncoder": true
},
"packages": {
"browserify>buffer": true,
"nock>debug": true,
"semver": true,
"superstruct": true
}
},
"@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": {
"globals": {
"URL": true,
"btoa": true,
"console.error": true,
"fetch": true,
"setTimeout": true
},
"packages": {
"@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true,
"@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true,
"@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true,
"@metamask/safe-event-emitter": true,
"browserify>browser-resolve": true,
"eth-rpc-errors": true,
"json-rpc-engine": true,
"lavamoat>json-stable-stringify": true,
"vinyl>clone": true
}
},
"@metamask/network-controller>@metamask/eth-json-rpc-provider": {
"packages": {
"@metamask/safe-event-emitter": true,
"json-rpc-engine": true
}
},
"@metamask/network-controller>eth-block-tracker": {
"globals": {
"clearTimeout": true,
"console.error": true,
"setTimeout": true
},
"packages": {
"@metamask/network-controller>eth-block-tracker>@metamask/safe-event-emitter": true,
"@metamask/network-controller>eth-block-tracker>pify": true,
"@metamask/utils": true,
"eth-query>json-rpc-random-id": true
}
},
"@metamask/network-controller>eth-block-tracker>@metamask/safe-event-emitter": {
"globals": {
"setTimeout": true
},
"packages": {
"browserify>events": true
}
},
"@metamask/notification-controller>nanoid": {
"globals": {
"crypto.getRandomValues": true
@ -2972,27 +3016,6 @@
"postMessage": true
}
},
"eth-block-tracker": {
"globals": {
"clearTimeout": true,
"console.error": true,
"setTimeout": true
},
"packages": {
"@metamask/utils": true,
"eth-block-tracker>@metamask/safe-event-emitter": true,
"eth-block-tracker>pify": true,
"eth-query>json-rpc-random-id": true
}
},
"eth-block-tracker>@metamask/safe-event-emitter": {
"globals": {
"setTimeout": true
},
"packages": {
"browserify>events": true
}
},
"eth-ens-namehash": {
"globals": {
"name": "write"

View File

@ -1126,50 +1126,6 @@
"fetch": true
}
},
"@metamask/eth-json-rpc-infura": {
"globals": {
"setTimeout": true
},
"packages": {
"@metamask/eth-json-rpc-infura>@metamask/utils": true,
"@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": true,
"eth-rpc-errors": true,
"json-rpc-engine": true,
"node-fetch": true
}
},
"@metamask/eth-json-rpc-infura>@metamask/utils": {
"globals": {
"TextDecoder": true,
"TextEncoder": true
},
"packages": {
"browserify>buffer": true,
"nock>debug": true,
"semver": true,
"superstruct": true
}
},
"@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": {
"globals": {
"URL": true,
"btoa": true,
"console.error": true,
"fetch": true,
"setTimeout": true
},
"packages": {
"@metamask/eth-json-rpc-infura>@metamask/utils": true,
"@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true,
"@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true,
"@metamask/safe-event-emitter": true,
"browserify>browser-resolve": true,
"eth-rpc-errors": true,
"json-rpc-engine": true,
"lavamoat>json-stable-stringify": true,
"vinyl>clone": true
}
},
"@metamask/eth-json-rpc-middleware": {
"globals": {
"URL": true,
@ -1198,12 +1154,6 @@
"superstruct": true
}
},
"@metamask/eth-json-rpc-provider": {
"packages": {
"@metamask/safe-event-emitter": true,
"json-rpc-engine": true
}
},
"@metamask/eth-keyring-controller": {
"packages": {
"@metamask/browser-passworder": true,
@ -1809,6 +1759,100 @@
"browserify>url": true
}
},
"@metamask/network-controller": {
"globals": {
"URL": true,
"btoa": true,
"fetch": true,
"setTimeout": true
},
"packages": {
"@metamask/base-controller": true,
"@metamask/controller-utils": true,
"@metamask/eth-json-rpc-middleware": true,
"@metamask/network-controller>@metamask/eth-json-rpc-infura": true,
"@metamask/network-controller>@metamask/eth-json-rpc-provider": true,
"@metamask/network-controller>@metamask/swappable-obj-proxy": true,
"@metamask/network-controller>eth-block-tracker": true,
"@metamask/utils": true,
"browserify>assert": true,
"eth-query": true,
"eth-rpc-errors": true,
"json-rpc-engine": true,
"uuid": true
}
},
"@metamask/network-controller>@metamask/eth-json-rpc-infura": {
"globals": {
"setTimeout": true
},
"packages": {
"@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true,
"@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": true,
"eth-rpc-errors": true,
"json-rpc-engine": true,
"node-fetch": true
}
},
"@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": {
"globals": {
"TextDecoder": true,
"TextEncoder": true
},
"packages": {
"browserify>buffer": true,
"nock>debug": true,
"semver": true,
"superstruct": true
}
},
"@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": {
"globals": {
"URL": true,
"btoa": true,
"console.error": true,
"fetch": true,
"setTimeout": true
},
"packages": {
"@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true,
"@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true,
"@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true,
"@metamask/safe-event-emitter": true,
"browserify>browser-resolve": true,
"eth-rpc-errors": true,
"json-rpc-engine": true,
"lavamoat>json-stable-stringify": true,
"vinyl>clone": true
}
},
"@metamask/network-controller>@metamask/eth-json-rpc-provider": {
"packages": {
"@metamask/safe-event-emitter": true,
"json-rpc-engine": true
}
},
"@metamask/network-controller>eth-block-tracker": {
"globals": {
"clearTimeout": true,
"console.error": true,
"setTimeout": true
},
"packages": {
"@metamask/network-controller>eth-block-tracker>@metamask/safe-event-emitter": true,
"@metamask/network-controller>eth-block-tracker>pify": true,
"@metamask/utils": true,
"eth-query>json-rpc-random-id": true
}
},
"@metamask/network-controller>eth-block-tracker>@metamask/safe-event-emitter": {
"globals": {
"setTimeout": true
},
"packages": {
"browserify>events": true
}
},
"@metamask/notification-controller>nanoid": {
"globals": {
"crypto.getRandomValues": true
@ -3193,27 +3237,6 @@
"postMessage": true
}
},
"eth-block-tracker": {
"globals": {
"clearTimeout": true,
"console.error": true,
"setTimeout": true
},
"packages": {
"@metamask/utils": true,
"eth-block-tracker>@metamask/safe-event-emitter": true,
"eth-block-tracker>pify": true,
"eth-query>json-rpc-random-id": true
}
},
"eth-block-tracker>@metamask/safe-event-emitter": {
"globals": {
"setTimeout": true
},
"packages": {
"browserify>events": true
}
},
"eth-ens-namehash": {
"globals": {
"name": "write"

View File

@ -234,9 +234,7 @@
"@metamask/controller-utils": "^4.0.0",
"@metamask/design-tokens": "^1.9.0",
"@metamask/desktop": "^0.3.0",
"@metamask/eth-json-rpc-infura": "^8.1.0",
"@metamask/eth-json-rpc-middleware": "^11.0.0",
"@metamask/eth-json-rpc-provider": "^1.0.0",
"@metamask/eth-keyring-controller": "^10.0.1",
"@metamask/eth-ledger-bridge-keyring": "^0.15.0",
"@metamask/eth-token-tracker": "^4.0.0",
@ -248,6 +246,7 @@
"@metamask/logo": "^3.1.1",
"@metamask/message-manager": "^7.0.0",
"@metamask/metamask-eth-abis": "^3.0.0",
"@metamask/network-controller": "^10.1.0",
"@metamask/notification-controller": "^3.0.0",
"@metamask/obs-store": "^8.1.0",
"@metamask/permission-controller": "^4.0.0",
@ -269,7 +268,6 @@
"@metamask/snaps-utils": "^0.32.2",
"@metamask/snaps-utils-flask": "npm:@metamask/snaps-utils@0.34.0-flask.1",
"@metamask/subject-metadata-controller": "^2.0.0",
"@metamask/swappable-obj-proxy": "^2.1.0",
"@metamask/utils": "^5.0.0",
"@ngraveio/bc-ur": "^1.1.6",
"@popperjs/core": "^2.4.0",
@ -295,7 +293,6 @@
"debounce-stream": "^2.0.0",
"deep-freeze-strict": "1.1.1",
"end-of-stream": "^1.4.4",
"eth-block-tracker": "^7.0.0",
"eth-ens-namehash": "^2.0.8",
"eth-json-rpc-filters": "^6.0.0",
"eth-lattice-keyring": "^0.12.4",
@ -374,7 +371,6 @@
"@babel/preset-typescript": "^7.16.7",
"@babel/register": "^7.5.5",
"@ethersproject/bignumber": "^5.7.0",
"@json-rpc-specification/meta-schema": "^1.0.6",
"@lavamoat/allow-scripts": "^2.0.3",
"@lavamoat/lavapack": "^5.0.0",
"@metamask/auto-changelog": "^2.1.0",
@ -417,7 +413,6 @@
"@types/gulp-sass": "^5.0.0",
"@types/gulp-sourcemaps": "^0.0.35",
"@types/jest": "^29.1.2",
"@types/jest-when": "^3.5.2",
"@types/madge": "^5.0.0",
"@types/node": "^17.0.21",
"@types/pify": "^5.0.1",
@ -496,7 +491,6 @@
"jest": "^29.1.2",
"jest-canvas-mock": "^2.3.1",
"jest-environment-jsdom": "^29.1.2",
"jest-when": "^3.5.2",
"js-yaml": "^4.1.0",
"jsdom": "^11.2.0",
"junit-report-merger": "^4.0.0",

View File

@ -3450,13 +3450,6 @@ __metadata:
languageName: node
linkType: hard
"@json-rpc-specification/meta-schema@npm:^1.0.6":
version: 1.0.6
resolution: "@json-rpc-specification/meta-schema@npm:1.0.6"
checksum: 2eb9c6c6c73bb38350c7180d1ad3c5b8462406926cae753741895b457d7b1b9f0b74148daf3462bb167cef39efdd1d9090308edf4d4938956863acb643c146eb
languageName: node
linkType: hard
"@juggle/resize-observer@npm:^3.3.1":
version: 3.4.0
resolution: "@juggle/resize-observer@npm:3.4.0"
@ -4142,7 +4135,7 @@ __metadata:
languageName: node
linkType: hard
"@metamask/eth-json-rpc-infura@npm:^8.0.0, @metamask/eth-json-rpc-infura@npm:^8.1.0":
"@metamask/eth-json-rpc-infura@npm:^8.0.0":
version: 8.1.0
resolution: "@metamask/eth-json-rpc-infura@npm:8.1.0"
dependencies:
@ -7740,15 +7733,6 @@ __metadata:
languageName: node
linkType: hard
"@types/jest-when@npm:^3.5.2":
version: 3.5.2
resolution: "@types/jest-when@npm:3.5.2"
dependencies:
"@types/jest": "*"
checksum: 106230dd71ee266bbd7620ab339a7305054e56ba7638f0eff9f222e67a959df0ad68a7f0108156c50f7005881bae59cd5c38b3760c101d060cdb3dac9cd77ee2
languageName: node
linkType: hard
"@types/jest@npm:*, @types/jest@npm:^29.1.2":
version: 29.1.2
resolution: "@types/jest@npm:29.1.2"
@ -21963,15 +21947,6 @@ __metadata:
languageName: node
linkType: hard
"jest-when@npm:^3.5.2":
version: 3.5.2
resolution: "jest-when@npm:3.5.2"
peerDependencies:
jest: ">= 25"
checksum: 9ad95552d377ef4d517c96a14c38bd8626d6856f518e7efc8a01d76a45a39d3c52281392c98da781c302114c78cd1ea17556c7f638d49d15971fcce9d58306e8
languageName: node
linkType: hard
"jest-worker@npm:^27.4.5":
version: 27.5.1
resolution: "jest-worker@npm:27.5.1"
@ -24309,7 +24284,6 @@ __metadata:
"@ethersproject/providers": ^5.7.2
"@formatjs/intl-relativetimeformat": ^5.2.6
"@fortawesome/fontawesome-free": ^5.13.0
"@json-rpc-specification/meta-schema": ^1.0.6
"@keystonehq/bc-ur-registry-eth": ^0.19.1
"@keystonehq/metamask-airgapped-keyring": ^0.13.1
"@lavamoat/allow-scripts": ^2.0.3
@ -24340,9 +24314,7 @@ __metadata:
"@metamask/eslint-config-mocha": ^9.0.0
"@metamask/eslint-config-nodejs": ^9.0.0
"@metamask/eslint-config-typescript": ^9.0.1
"@metamask/eth-json-rpc-infura": ^8.1.0
"@metamask/eth-json-rpc-middleware": ^11.0.0
"@metamask/eth-json-rpc-provider": ^1.0.0
"@metamask/eth-keyring-controller": ^10.0.1
"@metamask/eth-ledger-bridge-keyring": ^0.15.0
"@metamask/eth-token-tracker": ^4.0.0
@ -24355,6 +24327,7 @@ __metadata:
"@metamask/logo": ^3.1.1
"@metamask/message-manager": ^7.0.0
"@metamask/metamask-eth-abis": ^3.0.0
"@metamask/network-controller": ^10.1.0
"@metamask/notification-controller": ^3.0.0
"@metamask/obs-store": ^8.1.0
"@metamask/permission-controller": ^4.0.0
@ -24377,7 +24350,6 @@ __metadata:
"@metamask/snaps-utils": ^0.32.2
"@metamask/snaps-utils-flask": "npm:@metamask/snaps-utils@0.34.0-flask.1"
"@metamask/subject-metadata-controller": ^2.0.0
"@metamask/swappable-obj-proxy": ^2.1.0
"@metamask/test-dapp": ^7.0.0
"@metamask/utils": ^5.0.0
"@ngraveio/bc-ur": ^1.1.6
@ -24421,7 +24393,6 @@ __metadata:
"@types/gulp-sass": ^5.0.0
"@types/gulp-sourcemaps": ^0.0.35
"@types/jest": ^29.1.2
"@types/jest-when": ^3.5.2
"@types/madge": ^5.0.0
"@types/node": ^17.0.21
"@types/pify": ^5.0.1
@ -24486,7 +24457,6 @@ __metadata:
eslint-plugin-react: ^7.23.1
eslint-plugin-react-hooks: ^4.2.0
eslint-plugin-storybook: ^0.6.12
eth-block-tracker: ^7.0.0
eth-ens-namehash: ^2.0.8
eth-json-rpc-filters: ^6.0.0
eth-lattice-keyring: ^0.12.4
@ -24537,7 +24507,6 @@ __metadata:
jest-canvas-mock: ^2.3.1
jest-environment-jsdom: ^29.1.2
jest-junit: ^14.0.1
jest-when: ^3.5.2
js-yaml: ^4.1.0
jsdom: ^11.2.0
json-rpc-engine: ^6.1.0