diff --git a/jest.config.js b/jest.config.js index 432cbfa..153f417 100644 --- a/jest.config.js +++ b/jest.config.js @@ -6,10 +6,10 @@ module.exports = { coverageReporters: ['text', 'html'], coverageThreshold: { global: { - branches: 77, - functions: 89, - lines: 92, - statements: 91, + branches: 76.5, + functions: 92.5, + lines: 93.35, + statements: 93.35, }, }, moduleFileExtensions: ['js', 'json', 'jsx', 'ts', 'tsx', 'node'], diff --git a/package.json b/package.json index 21c2336..6f7b981 100644 --- a/package.json +++ b/package.json @@ -25,12 +25,12 @@ "test:watch": "jest --watchAll" }, "dependencies": { - "@ethersproject/bignumber": "^5.7.0", "@ethersproject/bytes": "^5.7.0", - "@ethersproject/providers": "^5.7.0", "@metamask/base-controller": "^3.2.1", "@metamask/controller-utils": "^5.0.0", + "@metamask/eth-query": "^3.0.1", "@metamask/network-controller": "^15.0.0", + "@metamask/polling-controller": "^0.2.0", "bignumber.js": "^9.0.1", "fast-json-patch": "^3.1.0", "lodash": "^4.17.21" diff --git a/src/SmartTransactionsController.test.ts b/src/SmartTransactionsController.test.ts index 4ac7adb..aa22f87 100644 --- a/src/SmartTransactionsController.test.ts +++ b/src/SmartTransactionsController.test.ts @@ -1,36 +1,67 @@ import nock from 'nock'; import { NetworkState } from '@metamask/network-controller'; +import { convertHexToDecimal } from '@metamask/controller-utils'; import SmartTransactionsController, { DEFAULT_INTERVAL, } from './SmartTransactionsController'; import { API_BASE_URL, CHAIN_IDS } from './constants'; import { SmartTransaction, SmartTransactionStatuses } from './types'; - -const confirmExternalMock = jest.fn(); +import * as utils from './utils'; + +/** + * Resolve all pending promises. + * This method is used for async tests that use fake timers. + * See https://stackoverflow.com/a/58716087 and https://jestjs.io/docs/timer-mocks. + */ +function flushPromises(): Promise { + return new Promise(jest.requireActual('timers').setImmediate); +} +// const confirmExternalMock = jest.fn(); jest.mock('@ethersproject/bytes', () => ({ ...jest.requireActual('@ethersproject/bytes'), hexlify: (str: string) => `0x${str}`, })); -jest.mock('@ethersproject/providers', () => ({ - Web3Provider: class Web3Provider { - getBalance = () => ({ toHexString: () => '0x1000' }); - - getTransactionReceipt = jest.fn(() => ({ blockNumber: '123' })); - - getTransaction = jest.fn(() => ({ - maxFeePerGas: { toHexString: () => '0x123' }, - maxPriorityFeePerGas: { toHexString: () => '0x123' }, - })); - - getBlock = jest.fn(); - }, -})); +jest.mock('@metamask/eth-query', () => { + const EthQuery = jest.requireActual('@metamask/eth-query'); + return class FakeEthQuery extends EthQuery { + sendAsync = jest.fn(({ method }, callback) => { + switch (method) { + case 'eth_getBalance': { + callback(null, '0x1000'); + break; + } + + case 'eth_getTransactionReceipt': { + callback(null, { blockNumber: '123' }); + break; + } + + case 'eth_getBlockByNumber': { + callback(null, { baseFeePerGas: '0x123' }); + break; + } + + case 'eth_getTransactionByHash': { + callback(null, { + maxFeePerGas: '0x123', + maxPriorityFeePerGas: '0x123', + }); + break; + } + + default: { + throw new Error('Invalid method'); + } + } + }); + }; +}); const addressFrom = '0x268392a24B6b093127E8581eAfbD1DA228bAdAe3'; -const createUnsignedTransaction = () => { +const createUnsignedTransaction = (chainId: number) => { return { from: addressFrom, to: '0x0000000000000000000000000000000000000000', @@ -38,7 +69,7 @@ const createUnsignedTransaction = () => { data: '0x', nonce: 0, type: 2, - chainId: 4, + chainId, }; }; @@ -248,13 +279,40 @@ const testHistory = [ ]; const ethereumChainIdDec = parseInt(CHAIN_IDS.ETHEREUM, 16); +const goerliChainIdDec = parseInt(CHAIN_IDS.GOERLI, 16); const trackMetaMetricsEventSpy = jest.fn(); +const defaultState = { + smartTransactionsState: { + smartTransactions: { + [CHAIN_IDS.ETHEREUM]: [], + }, + userOptIn: undefined, + fees: { + approvalTxFees: undefined, + tradeTxFees: undefined, + }, + feesByChainId: { + [CHAIN_IDS.ETHEREUM]: { + approvalTxFees: undefined, + tradeTxFees: undefined, + }, + [CHAIN_IDS.GOERLI]: { + approvalTxFees: undefined, + tradeTxFees: undefined, + }, + }, + liveness: true, + livenessByChainId: { + [CHAIN_IDS.ETHEREUM]: true, + [CHAIN_IDS.GOERLI]: true, + }, + }, +}; describe('SmartTransactionsController', () => { let smartTransactionsController: SmartTransactionsController; let networkListener: (networkState: NetworkState) => void; - beforeEach(() => { smartTransactionsController = new SmartTransactionsController({ onNetworkStateChange: (listener) => { @@ -267,8 +325,26 @@ describe('SmartTransactionsController', () => { }; }), provider: jest.fn(), - confirmExternalTransaction: confirmExternalMock, + confirmExternalTransaction: jest.fn(), trackMetaMetricsEvent: trackMetaMetricsEventSpy, + getNetworkClientById: jest.fn().mockImplementation((networkClientId) => { + switch (networkClientId) { + case 'mainnet': + return { + configuration: { + chainId: CHAIN_IDS.ETHEREUM, + }, + }; + case 'goerli': + return { + configuration: { + chainId: CHAIN_IDS.GOERLI, + }, + }; + default: + throw new Error('Invalid network client id'); + } + }), }); // eslint-disable-next-line jest/prefer-spy-on smartTransactionsController.subscribe = jest.fn(); @@ -283,37 +359,35 @@ describe('SmartTransactionsController', () => { it('initializes with default config', () => { expect(smartTransactionsController.config).toStrictEqual({ interval: DEFAULT_INTERVAL, - supportedChainIds: [CHAIN_IDS.ETHEREUM, CHAIN_IDS.RINKEBY], + supportedChainIds: [CHAIN_IDS.ETHEREUM, CHAIN_IDS.GOERLI], chainId: CHAIN_IDS.ETHEREUM, clientId: 'default', }); }); it('initializes with default state', () => { - expect(smartTransactionsController.state).toStrictEqual({ - smartTransactionsState: { - smartTransactions: { - [CHAIN_IDS.ETHEREUM]: [], - }, - userOptIn: undefined, - fees: { - approvalTxFees: undefined, - tradeTxFees: undefined, - }, - liveness: true, - }, - }); + expect(smartTransactionsController.state).toStrictEqual(defaultState); }); describe('onNetworkChange', () => { it('is triggered', () => { - networkListener({ providerConfig: { chainId: '52' } } as NetworkState); - expect(smartTransactionsController.config.chainId).toBe('52'); + networkListener({ + providerConfig: { chainId: '0x32', type: 'rpc', ticker: 'CET' }, + selectedNetworkClientId: 'networkClientId', + networkConfigurations: {}, + networksMetadata: {}, + } as NetworkState); + expect(smartTransactionsController.config.chainId).toBe('0x32'); }); it('calls poll', () => { const checkPollSpy = jest.spyOn(smartTransactionsController, 'checkPoll'); - networkListener({ providerConfig: { chainId: '2' } } as NetworkState); + networkListener({ + providerConfig: { chainId: '0x32', type: 'rpc', ticker: 'CET' }, + selectedNetworkClientId: 'networkClientId', + networkConfigurations: {}, + networksMetadata: {}, + } as NetworkState); expect(checkPollSpy).toHaveBeenCalled(); }); }); @@ -340,7 +414,7 @@ describe('SmartTransactionsController', () => { it('calls stop if there is a timeoutHandle and no pending transactions', () => { const stopSpy = jest.spyOn(smartTransactionsController, 'stop'); - smartTransactionsController.timeoutHandle = setInterval(() => ({})); + smartTransactionsController.timeoutHandle = setTimeout(() => ({})); smartTransactionsController.checkPoll(smartTransactionsController.state); expect(stopSpy).toHaveBeenCalled(); clearInterval(smartTransactionsController.timeoutHandle); @@ -354,12 +428,19 @@ describe('SmartTransactionsController', () => { 'updateSmartTransactions', ); expect(updateSmartTransactionsSpy).not.toHaveBeenCalled(); - networkListener({ providerConfig: { chainId: '56' } } as NetworkState); + networkListener({ + providerConfig: { chainId: '0x32', type: 'rpc', ticker: 'CET' }, + selectedNetworkClientId: 'networkClientId', + networkConfigurations: {}, + networksMetadata: {}, + } as NetworkState); expect(updateSmartTransactionsSpy).not.toHaveBeenCalled(); }); }); describe('updateSmartTransactions', () => { + // TODO rewrite this test... updateSmartTransactions is getting called via the checkPoll method which is called whenever state is updated. + // this test should be more isolated to the updateSmartTransactions method. it('calls fetchSmartTransactionsStatus if there are pending transactions', () => { const fetchSmartTransactionsStatusSpy = jest .spyOn(smartTransactionsController, 'fetchSmartTransactionsStatus') @@ -431,8 +512,8 @@ describe('SmartTransactionsController', () => { describe('getFees', () => { it('gets unsigned transactions and estimates based on an unsigned transaction', async () => { - const tradeTx = createUnsignedTransaction(); - const approvalTx = createUnsignedTransaction(); + const tradeTx = createUnsignedTransaction(ethereumChainIdDec); + const approvalTx = createUnsignedTransaction(ethereumChainIdDec); const getFeesApiResponse = createGetFeesApiResponse(); nock(API_BASE_URL) .post(`/networks/${ethereumChainIdDec}/getFees`) @@ -446,6 +527,55 @@ describe('SmartTransactionsController', () => { tradeTxFees: getFeesApiResponse.txs[1], }); }); + + it('should add fee data to feesByChainId state using the networkClientId passed in to identify the appropriate chain', async () => { + // getNetworkClientByIdSpy.mockImplementation((networkClientId) => { + // switch (networkClientId) { + // case 'mainnet': + // return { + // configuration: { + // chainId: CHAIN_IDS.ETHEREUM, + // }, + // }; + // case 'goerli': + // return { + // configuration: { + // chainId: CHAIN_IDS.GOERLI, + // }, + // }; + // default: + // throw new Error('Invalid network client id'); + // } + // }); + + const tradeTx = createUnsignedTransaction(goerliChainIdDec); + const approvalTx = createUnsignedTransaction(goerliChainIdDec); + const getFeesApiResponse = createGetFeesApiResponse(); + nock(API_BASE_URL) + .post(`/networks/${goerliChainIdDec}/getFees`) + .reply(200, getFeesApiResponse); + + expect( + smartTransactionsController.state.smartTransactionsState.feesByChainId, + ).toStrictEqual(defaultState.smartTransactionsState.feesByChainId); + + await smartTransactionsController.getFees(tradeTx, approvalTx, { + networkClientId: 'goerli', + }); + + expect( + smartTransactionsController.state.smartTransactionsState.feesByChainId, + ).toMatchObject({ + [CHAIN_IDS.ETHEREUM]: { + approvalTxFees: undefined, + tradeTxFees: undefined, + }, + [CHAIN_IDS.GOERLI]: { + approvalTxFees: getFeesApiResponse.txs[0], + tradeTxFees: getFeesApiResponse.txs[1], + }, + }); + }); }); describe('submitSignedTransactions', () => { @@ -489,7 +619,10 @@ describe('SmartTransactionsController', () => { nock(API_BASE_URL) .get(`/networks/${ethereumChainIdDec}/batchStatus?uuids=uuid1`) .reply(200, pendingBatchStatusApiResponse); - await smartTransactionsController.fetchSmartTransactionsStatus(uuids); + + await smartTransactionsController.fetchSmartTransactionsStatus(uuids, { + networkClientId: 'mainnet', + }); const pendingState = createStateAfterPending()[0]; const pendingTransaction = { ...pendingState, history: [pendingState] }; expect(smartTransactionsController.state).toStrictEqual({ @@ -502,7 +635,21 @@ describe('SmartTransactionsController', () => { approvalTxFees: undefined, tradeTxFees: undefined, }, + feesByChainId: { + [CHAIN_IDS.ETHEREUM]: { + approvalTxFees: undefined, + tradeTxFees: undefined, + }, + [CHAIN_IDS.GOERLI]: { + approvalTxFees: undefined, + tradeTxFees: undefined, + }, + }, liveness: true, + livenessByChainId: { + [CHAIN_IDS.ETHEREUM]: true, + [CHAIN_IDS.GOERLI]: true, + }, }, }); }); @@ -524,7 +671,10 @@ describe('SmartTransactionsController', () => { nock(API_BASE_URL) .get(`/networks/${ethereumChainIdDec}/batchStatus?uuids=uuid2`) .reply(200, successBatchStatusApiResponse); - await smartTransactionsController.fetchSmartTransactionsStatus(uuids); + + await smartTransactionsController.fetchSmartTransactionsStatus(uuids, { + networkClientId: 'mainnet', + }); const successState = createStateAfterSuccess()[0]; const successTransaction = { ...successState, history: [successState] }; expect(smartTransactionsController.state).toStrictEqual({ @@ -541,6 +691,20 @@ describe('SmartTransactionsController', () => { tradeTxFees: undefined, }, liveness: true, + feesByChainId: { + '0x1': { + approvalTxFees: undefined, + tradeTxFees: undefined, + }, + '0x5': { + approvalTxFees: undefined, + tradeTxFees: undefined, + }, + }, + livenessByChainId: { + '0x1': true, + '0x5': true, + }, }, }); }); @@ -555,6 +719,32 @@ describe('SmartTransactionsController', () => { const liveness = await smartTransactionsController.fetchLiveness(); expect(liveness).toBe(true); }); + + it('fetches liveness and sets in feesByChainId state for the Smart Transactions API for the chainId of the networkClientId passed in', async () => { + nock(API_BASE_URL) + .get(`/networks/${goerliChainIdDec}/health`) + .replyWithError('random error'); + + expect( + smartTransactionsController.state.smartTransactionsState + .livenessByChainId, + ).toStrictEqual({ + [CHAIN_IDS.ETHEREUM]: true, + [CHAIN_IDS.GOERLI]: true, + }); + + await smartTransactionsController.fetchLiveness({ + networkClientId: 'goerli', + }); + + expect( + smartTransactionsController.state.smartTransactionsState + .livenessByChainId, + ).toStrictEqual({ + [CHAIN_IDS.ETHEREUM]: true, + [CHAIN_IDS.GOERLI]: false, + }); + }); }); describe('updateSmartTransaction', () => { @@ -583,6 +773,9 @@ describe('SmartTransactionsController', () => { }; smartTransactionsController.updateSmartTransaction( updateTransaction as SmartTransaction, + { + networkClientId: 'mainnet', + }, ); expect( @@ -593,10 +786,6 @@ describe('SmartTransactionsController', () => { it('confirms a smart transaction that has status success', async () => { const { smartTransactionsState } = smartTransactionsController.state; - const confirmSpy = jest.spyOn( - smartTransactionsController, - 'confirmSmartTransaction', - ); const pendingStx = { ...createStateAfterPending()[0], history: testHistory, @@ -611,44 +800,27 @@ describe('SmartTransactionsController', () => { }); const updateTransaction = { ...pendingStx, - status: 'success', + status: SmartTransactionStatuses.SUCCESS, }; + smartTransactionsController.updateSmartTransaction( updateTransaction as SmartTransaction, + { + networkClientId: 'mainnet', + }, ); - expect(confirmSpy).toHaveBeenCalled(); - }); - }); - describe('confirmSmartTransaction', () => { - beforeEach(() => { - // eslint-disable-next-line jest/prefer-spy-on - smartTransactionsController.checkPoll = jest.fn(() => ({})); - }); + await flushPromises(); - it('calls confirm external transaction', async () => { - const successfulStx = { - ...createStateAfterSuccess()[0], - history: testHistory, - }; - await smartTransactionsController.confirmSmartTransaction( - successfulStx as SmartTransaction, - ); - expect(confirmExternalMock).toHaveBeenCalled(); - }); - - it('throws an error if ethersProvider fails', async () => { - smartTransactionsController.ethersProvider.getTransactionReceipt.mockRejectedValueOnce( - 'random error' as never, - ); - const successfulStx = { - ...createStateAfterSuccess()[0], - history: testHistory, - }; - await smartTransactionsController.confirmSmartTransaction( - successfulStx as SmartTransaction, - ); - expect(trackMetaMetricsEventSpy).toHaveBeenCalled(); + expect( + smartTransactionsController.state.smartTransactionsState + .smartTransactions[CHAIN_IDS.ETHEREUM], + ).toStrictEqual([ + { + ...updateTransaction, + confirmed: true, + }, + ]); }); }); @@ -735,4 +907,114 @@ describe('SmartTransactionsController', () => { expect(actual).toBe(false); }); }); + + describe('startPollingByNetworkClientId', () => { + it('starts and stops calling smart transactions batch status api endpoint with the correct chainId at the polling interval', async () => { + // mock this to a noop because it causes an extra fetch call to the API upon state changes + jest + .spyOn(smartTransactionsController, 'checkPoll') + .mockImplementation(() => undefined); + + // pending transactions in state are required to test polling + smartTransactionsController.update({ + smartTransactionsState: { + ...defaultState.smartTransactionsState, + smartTransactions: { + '0x1': [ + { + uuid: 'uuid1', + status: 'pending', + cancellable: true, + chainId: '0x1', + }, + ], + '0x5': [ + { + uuid: 'uuid2', + status: 'pending', + cancellable: true, + chainId: '0x5', + }, + ], + }, + }, + }); + + jest.useFakeTimers(); + const handleFetchSpy = jest.spyOn(utils, 'handleFetch'); + + const mainnetPollingToken = + smartTransactionsController.startPollingByNetworkClientId('mainnet'); + + await Promise.all([ + jest.advanceTimersByTime(DEFAULT_INTERVAL), + flushPromises(), + ]); + + expect(handleFetchSpy.mock.calls[0]).toStrictEqual( + expect.arrayContaining([ + `${API_BASE_URL}/networks/${convertHexToDecimal( + CHAIN_IDS.ETHEREUM, + )}/batchStatus?uuids=uuid1`, + ]), + ); + + smartTransactionsController.startPollingByNetworkClientId('goerli'); + await jest.advanceTimersByTime(DEFAULT_INTERVAL); + + expect( + JSON.stringify(handleFetchSpy.mock.calls.map((arg) => arg[0])), + ).toStrictEqual( + JSON.stringify([ + `${API_BASE_URL}/networks/${convertHexToDecimal( + CHAIN_IDS.ETHEREUM, + )}/batchStatus?uuids=uuid1`, + `${API_BASE_URL}/networks/${convertHexToDecimal( + CHAIN_IDS.ETHEREUM, + )}/batchStatus?uuids=uuid1`, + `${API_BASE_URL}/networks/${convertHexToDecimal( + CHAIN_IDS.GOERLI, + )}/batchStatus?uuids=uuid2`, + ]), + ); + + // stop the mainnet polling + smartTransactionsController.stopPollingByPollingToken( + mainnetPollingToken, + ); + + // cycle two polling intervals + await jest.advanceTimersByTime(DEFAULT_INTERVAL); + await jest.advanceTimersByTime(DEFAULT_INTERVAL); + + // check that the mainnet polling has stopped while the goerli polling continues + expect( + JSON.stringify(handleFetchSpy.mock.calls.map((arg) => arg[0])), + ).toStrictEqual( + JSON.stringify([ + `${API_BASE_URL}/networks/${convertHexToDecimal( + CHAIN_IDS.ETHEREUM, + )}/batchStatus?uuids=uuid1`, + `${API_BASE_URL}/networks/${convertHexToDecimal( + CHAIN_IDS.ETHEREUM, + )}/batchStatus?uuids=uuid1`, + `${API_BASE_URL}/networks/${convertHexToDecimal( + CHAIN_IDS.GOERLI, + )}/batchStatus?uuids=uuid2`, + `${API_BASE_URL}/networks/${convertHexToDecimal( + CHAIN_IDS.GOERLI, + )}/batchStatus?uuids=uuid2`, + `${API_BASE_URL}/networks/${convertHexToDecimal( + CHAIN_IDS.GOERLI, + )}/batchStatus?uuids=uuid2`, + ]), + ); + + // cleanup + smartTransactionsController.update(defaultState); + + smartTransactionsController.stopAllPolling(); + jest.clearAllTimers(); + }); + }); }); diff --git a/src/SmartTransactionsController.ts b/src/SmartTransactionsController.ts index 25d511c..08aacd7 100644 --- a/src/SmartTransactionsController.ts +++ b/src/SmartTransactionsController.ts @@ -1,15 +1,14 @@ +import { BaseConfig, BaseState } from '@metamask/base-controller'; +import { ChainId, safelyExecute, query } from '@metamask/controller-utils'; import { - BaseConfig, - BaseController, - BaseState, -} from '@metamask/base-controller'; -import { safelyExecute } from '@metamask/controller-utils'; -import { NetworkState } from '@metamask/network-controller'; + NetworkState, + NetworkController, + NetworkClientId, +} from '@metamask/network-controller'; +import { PollingControllerV1 } from '@metamask/polling-controller'; import { BigNumber } from 'bignumber.js'; -import { BigNumber as ethersBigNumber } from '@ethersproject/bignumber'; -import { Web3Provider } from '@ethersproject/providers'; +import EthQuery from '@metamask/eth-query'; import { hexlify } from '@ethersproject/bytes'; -import mapValues from 'lodash/mapValues'; import cloneDeep from 'lodash/cloneDeep'; import { APIType, @@ -21,6 +20,7 @@ import { SmartTransactionStatuses, Fees, IndividualTxFees, + Hex, } from './types'; import { getAPIRequestURL, @@ -42,23 +42,27 @@ export const DEFAULT_INTERVAL = SECOND * 5; export type SmartTransactionsControllerConfig = BaseConfig & { interval: number; clientId: string; - chainId: string; + chainId: Hex; supportedChainIds: string[]; }; +type FeeEstimates = { + approvalTxFees: IndividualTxFees | undefined; + tradeTxFees: IndividualTxFees | undefined; +}; + export type SmartTransactionsControllerState = BaseState & { smartTransactionsState: { - smartTransactions: Record; + smartTransactions: Record; userOptIn: boolean | undefined; liveness: boolean | undefined; - fees: { - approvalTxFees: IndividualTxFees | undefined; - tradeTxFees: IndividualTxFees | undefined; - }; + fees: FeeEstimates; + feesByChainId: Record; + livenessByChainId: Record; }; }; -export default class SmartTransactionsController extends BaseController< +export default class SmartTransactionsController extends PollingControllerV1< SmartTransactionsControllerConfig, SmartTransactionsControllerState > { @@ -66,12 +70,14 @@ export default class SmartTransactionsController extends BaseController< private getNonceLock: any; - public ethersProvider: any; + public ethQuery: EthQuery; public confirmExternalTransaction: any; private trackMetaMetricsEvent: any; + private getNetworkClientById: NetworkController['getNetworkClientById']; + /* istanbul ignore next */ private async fetch(request: string, options?: RequestInit) { const { clientId } = this.config; @@ -93,6 +99,7 @@ export default class SmartTransactionsController extends BaseController< provider, confirmExternalTransaction, trackMetaMetricsEvent, + getNetworkClientById, }: { onNetworkStateChange: ( listener: (networkState: NetworkState) => void, @@ -101,6 +108,7 @@ export default class SmartTransactionsController extends BaseController< provider: any; confirmExternalTransaction: any; trackMetaMetricsEvent: any; + getNetworkClientById: NetworkController['getNetworkClientById']; }, config?: Partial, state?: Partial, @@ -109,9 +117,9 @@ export default class SmartTransactionsController extends BaseController< this.defaultConfig = { interval: DEFAULT_INTERVAL, - chainId: CHAIN_IDS.ETHEREUM, + chainId: ChainId.mainnet, clientId: 'default', - supportedChainIds: [CHAIN_IDS.ETHEREUM, CHAIN_IDS.RINKEBY], + supportedChainIds: [CHAIN_IDS.ETHEREUM, CHAIN_IDS.GOERLI], }; this.defaultState = { @@ -123,13 +131,28 @@ export default class SmartTransactionsController extends BaseController< tradeTxFees: undefined, }, liveness: true, + livenessByChainId: { + [CHAIN_IDS.ETHEREUM]: true, + [CHAIN_IDS.GOERLI]: true, + }, + feesByChainId: { + [CHAIN_IDS.ETHEREUM]: { + approvalTxFees: undefined, + tradeTxFees: undefined, + }, + [CHAIN_IDS.GOERLI]: { + approvalTxFees: undefined, + tradeTxFees: undefined, + }, + }, }, }; - + this.setIntervalLength(this.config.interval || DEFAULT_INTERVAL); this.getNonceLock = getNonceLock; - this.ethersProvider = new Web3Provider(provider); + this.ethQuery = new EthQuery(provider); this.confirmExternalTransaction = confirmExternalTransaction; this.trackMetaMetricsEvent = trackMetaMetricsEvent; + this.getNetworkClientById = getNetworkClientById; this.initialize(); this.initializeSmartTransactionsForChainId(); @@ -139,12 +162,24 @@ export default class SmartTransactionsController extends BaseController< this.configure({ chainId }); this.initializeSmartTransactionsForChainId(); this.checkPoll(this.state); - this.ethersProvider = new Web3Provider(provider); + this.ethQuery = new EthQuery(provider); }); this.subscribe((currentState: any) => this.checkPoll(currentState)); } + _executePoll(networkClientId: string): Promise { + // if this is going to be truly UI driven polling we shouldn't really reach here + // with a networkClientId that is not supported, but for now I'll add a check in case + // wondering if we should add some kind of predicate to the polling controller to check whether + // we should poll or not + const chainId = this.getChainId({ networkClientId }); + if (!this.config.supportedChainIds.includes(chainId)) { + return Promise.resolve(); + } + return this.updateSmartTransactions({ networkClientId }); + } + checkPoll(state: any) { const { smartTransactions } = state.smartTransactionsState; const currentSmartTransactions = smartTransactions[this.config.chainId]; @@ -252,8 +287,35 @@ export default class SmartTransactionsController extends BaseController< return currentIndex === -1 || currentIndex === undefined; } - updateSmartTransaction(smartTransaction: SmartTransaction): void { - const { chainId } = this.config; + updateSmartTransaction( + smartTransaction: SmartTransaction, + { networkClientId }: { networkClientId?: NetworkClientId } = {}, + ) { + let { chainId } = this.config; + let { ethQuery } = this; + if (networkClientId) { + const networkClient = this.getNetworkClientById(networkClientId); + chainId = networkClient.configuration.chainId; + // @ts-expect-error TODO: Provider type alignment + ethQuery = new EthQuery(networkClient.provider); + } + + this.#updateSmartTransaction(smartTransaction, { + chainId, + ethQuery, + }); + } + + #updateSmartTransaction( + smartTransaction: SmartTransaction, + { + chainId = this.config.chainId, + ethQuery = this.ethQuery, + }: { + chainId: Hex; + ethQuery: EthQuery; + }, + ): void { const { smartTransactionsState } = this.state; const { smartTransactions } = smartTransactionsState; const currentSmartTransactions = smartTransactions[chainId]; @@ -263,6 +325,7 @@ export default class SmartTransactionsController extends BaseController< const isNewSmartTransaction = this.isNewSmartTransaction( smartTransaction.uuid, ); + this.trackStxStatusChange( smartTransaction, isNewSmartTransaction @@ -272,7 +335,7 @@ export default class SmartTransactionsController extends BaseController< if (isNewSmartTransaction) { // add smart transaction - const cancelledNonceIndex = currentSmartTransactions.findIndex( + const cancelledNonceIndex = currentSmartTransactions?.findIndex( (stx: SmartTransaction) => stx.txParams?.nonce === smartTransaction.txParams?.nonce && stx.status?.startsWith('cancelled'), @@ -286,7 +349,7 @@ export default class SmartTransactionsController extends BaseController< .slice(0, cancelledNonceIndex) .concat(currentSmartTransactions.slice(cancelledNonceIndex + 1)) .concat(historifiedSmartTransaction) - : currentSmartTransactions.concat(historifiedSmartTransaction); + : currentSmartTransactions?.concat(historifiedSmartTransaction); this.update({ smartTransactionsState: { ...smartTransactionsState, @@ -310,7 +373,10 @@ export default class SmartTransactionsController extends BaseController< ...currentSmartTransaction, ...smartTransaction, }; - this.confirmSmartTransaction(nextSmartTransaction); + this.#confirmSmartTransaction(nextSmartTransaction, { + chainId, + ethQuery, + }); } this.update({ @@ -330,42 +396,59 @@ export default class SmartTransactionsController extends BaseController< }); } - async updateSmartTransactions() { + async updateSmartTransactions({ + networkClientId, + }: { + networkClientId?: NetworkClientId; + } = {}): Promise { const { smartTransactions } = this.state.smartTransactionsState; - const { chainId } = this.config; - - const currentSmartTransactions = smartTransactions?.[chainId]; + const chainId = this.getChainId({ networkClientId }); + const smartTransactionsForChainId = smartTransactions?.[chainId]; - const transactionsToUpdate: string[] = currentSmartTransactions + const transactionsToUpdate: string[] = smartTransactionsForChainId .filter(isSmartTransactionPending) .map((smartTransaction) => smartTransaction.uuid); if (transactionsToUpdate.length > 0) { - this.fetchSmartTransactionsStatus(transactionsToUpdate); + this.fetchSmartTransactionsStatus(transactionsToUpdate, { + networkClientId, + }); } } - async confirmSmartTransaction(smartTransaction: SmartTransaction) { + async #confirmSmartTransaction( + smartTransaction: SmartTransaction, + { + chainId, + ethQuery, + }: { + chainId: Hex; + ethQuery: EthQuery; + }, + ) { const txHash = smartTransaction.statusMetadata?.minedHash; try { - const transactionReceipt = - await this.ethersProvider.getTransactionReceipt(txHash); - const transaction = await this.ethersProvider.getTransaction(txHash); - const maxFeePerGas = transaction.maxFeePerGas?.toHexString(); - const maxPriorityFeePerGas = - transaction.maxPriorityFeePerGas?.toHexString(); + const transactionReceipt: { + maxFeePerGas?: string; + maxPriorityFeePerGas?: string; + blockNumber: string; + } | null = await query(ethQuery, 'getTransactionReceipt', [txHash]); + + const transaction: { + maxFeePerGas?: string; + maxPriorityFeePerGas?: string; + } | null = await query(ethQuery, 'getTransactionByHash', [txHash]); + + const maxFeePerGas = transaction?.maxFeePerGas; + const maxPriorityFeePerGas = transaction?.maxPriorityFeePerGas; if (transactionReceipt?.blockNumber) { - const blockData = await this.ethersProvider.getBlock( - transactionReceipt?.blockNumber, - false, + const blockData: { baseFeePerGas?: string } | null = await query( + ethQuery, + 'getBlockByNumber', + [transactionReceipt?.blockNumber, false], ); - const baseFeePerGas = blockData?.baseFeePerGas.toHexString(); - const txReceipt = mapValues(transactionReceipt, (value) => { - if (value instanceof ethersBigNumber) { - return value.toHexString(); - } - return value; - }); + const baseFeePerGas = blockData?.baseFeePerGas; + const txReceipt = Object.values(transactionReceipt); const updatedTxParams = { ...smartTransaction.txParams, maxFeePerGas, @@ -396,6 +479,7 @@ export default class SmartTransactionsController extends BaseController< history: originalTxMeta.history.concat(entry), } : originalTxMeta; + this.confirmExternalTransaction(txMeta, txReceipt, baseFeePerGas); this.trackMetaMetricsEvent({ @@ -403,10 +487,13 @@ export default class SmartTransactionsController extends BaseController< category: 'swaps', }); - this.updateSmartTransaction({ - ...smartTransaction, - confirmed: true, - }); + this.#updateSmartTransaction( + { + ...smartTransaction, + confirmed: true, + }, + { chainId, ethQuery }, + ); } } catch (e) { this.trackMetaMetricsEvent({ @@ -420,29 +507,31 @@ export default class SmartTransactionsController extends BaseController< // ! Ask backend API to accept list of uuids as params async fetchSmartTransactionsStatus( uuids: string[], + { networkClientId }: { networkClientId?: NetworkClientId } = {}, ): Promise { - const { chainId } = this.config; - const params = new URLSearchParams({ uuids: uuids.join(','), }); - + const chainId = this.getChainId({ networkClientId }); + const ethQuery = this.getEthQuery({ networkClientId }); const url = `${getAPIRequestURL( APIType.BATCH_STATUS, chainId, )}?${params.toString()}`; const data = await this.fetch(url); - Object.entries(data).forEach(([uuid, stxStatus]) => { - this.updateSmartTransaction({ - statusMetadata: stxStatus as SmartTransactionsStatus, - status: calculateStatus(stxStatus as SmartTransactionsStatus), - cancellable: isSmartTransactionCancellable( - stxStatus as SmartTransactionsStatus, - ), - uuid, - }); + this.#updateSmartTransaction( + { + statusMetadata: stxStatus as SmartTransactionsStatus, + status: calculateStatus(stxStatus as SmartTransactionsStatus), + cancellable: isSmartTransactionCancellable( + stxStatus as SmartTransactionsStatus, + ), + uuid, + }, + { chainId, ethQuery }, + ); }); return data; @@ -477,8 +566,9 @@ export default class SmartTransactionsController extends BaseController< async getFees( tradeTx: UnsignedTransaction, approvalTx: UnsignedTransaction, + { networkClientId }: { networkClientId?: NetworkClientId } = {}, ): Promise { - const { chainId } = this.config; + const chainId = this.getChainId({ networkClientId }); const transactions = []; let unsignedTradeTransactionWithNonce; if (approvalTx) { @@ -518,8 +608,16 @@ export default class SmartTransactionsController extends BaseController< approvalTxFees, tradeTxFees, }, + feesByChainId: { + ...this.state.smartTransactionsState.feesByChainId, + [chainId]: { + approvalTxFees, + tradeTxFees, + }, + }, }, }); + return { approvalTxFees, tradeTxFees, @@ -532,12 +630,15 @@ export default class SmartTransactionsController extends BaseController< txParams, signedTransactions, signedCanceledTransactions, + networkClientId, }: { signedTransactions: SignedTransaction[]; signedCanceledTransactions: SignedCanceledTransaction[]; txParams?: any; + networkClientId?: NetworkClientId; }) { - const { chainId } = this.config; + const chainId = this.getChainId({ networkClientId }); + const ethQuery = this.getEthQuery({ networkClientId }); const data = await this.fetch( getAPIRequestURL(APIType.SUBMIT_TRANSACTIONS, chainId), { @@ -551,12 +652,12 @@ export default class SmartTransactionsController extends BaseController< const time = Date.now(); let preTxBalance; try { - const preTxBalanceBN = await this.ethersProvider.getBalance( + const preTxBalanceBN = await query(ethQuery, 'getBalance', [ txParams?.from, - ); - preTxBalance = new BigNumber(preTxBalanceBN.toHexString()).toString(16); + ]); + preTxBalance = new BigNumber(preTxBalanceBN).toString(16); } catch (e) { - console.error('ethers error', e); + console.error('provider error', e); } const nonceLock = await this.getNonceLock(txParams?.from); try { @@ -566,16 +667,18 @@ export default class SmartTransactionsController extends BaseController< } const { nonceDetails } = nonceLock; - this.updateSmartTransaction({ - chainId, - nonceDetails, - preTxBalance, - status: SmartTransactionStatuses.PENDING, - time, - txParams, - uuid: data.uuid, - cancellable: true, - }); + this.#updateSmartTransaction( + { + nonceDetails, + preTxBalance, + status: SmartTransactionStatuses.PENDING, + time, + txParams, + uuid: data.uuid, + cancellable: true, + }, + { chainId, ethQuery }, + ); } finally { nonceLock.releaseLock(); } @@ -583,19 +686,49 @@ export default class SmartTransactionsController extends BaseController< return data; } + getChainId({ + networkClientId, + }: { networkClientId?: NetworkClientId } = {}): Hex { + return networkClientId + ? this.getNetworkClientById(networkClientId).configuration.chainId + : this.config.chainId; + } + + getEthQuery({ + networkClientId, + }: { + networkClientId?: NetworkClientId; + }): EthQuery { + return networkClientId + ? // @ts-expect-error TODO: Provider type alignment + new EthQuery(this.getNetworkClientById(networkClientId).provider) + : this.ethQuery; + } + // TODO: This should return if the cancellation was on chain or not (for nonce management) // After this successful call client must update nonce representative // in transaction controller external transactions list - async cancelSmartTransaction(uuid: string): Promise { - const { chainId } = this.config; + async cancelSmartTransaction( + uuid: string, + { + networkClientId, + }: { + networkClientId?: NetworkClientId; + } = {}, + ): Promise { + const chainId = this.getChainId({ networkClientId }); await this.fetch(getAPIRequestURL(APIType.CANCEL, chainId), { method: 'POST', body: JSON.stringify({ uuid }), }); } - async fetchLiveness(): Promise { - const { chainId } = this.config; + async fetchLiveness({ + networkClientId, + }: { + networkClientId?: NetworkClientId; + } = {}): Promise { + const chainId = this.getChainId({ networkClientId }); let liveness = false; try { const response = await this.fetch( @@ -610,8 +743,13 @@ export default class SmartTransactionsController extends BaseController< smartTransactionsState: { ...this.state.smartTransactionsState, liveness, + livenessByChainId: { + ...this.state.smartTransactionsState.livenessByChainId, + [chainId]: liveness, + }, }, }); + return liveness; } diff --git a/src/constants.ts b/src/constants.ts index 070e289..693df74 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,6 +1,7 @@ export const API_BASE_URL = 'https://transaction.metaswap.codefi.network'; export const CHAIN_IDS = { ETHEREUM: '0x1', + GOERLI: '0x5', RINKEBY: '0x4', BSC: '0x38', -}; +} as const; diff --git a/src/index.test.ts b/src/index.test.ts index 411d2c1..678b078 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -10,6 +10,7 @@ describe('default export', () => { provider: jest.fn(), confirmExternalTransaction: jest.fn(), trackMetaMetricsEvent: jest.fn(), + getNetworkClientById: jest.fn(), }); expect(controller).toBeInstanceOf(SmartTransactionsController); jest.clearAllTimers(); diff --git a/src/types.ts b/src/types.ts index 045463f..b1032c1 100644 --- a/src/types.ts +++ b/src/types.ts @@ -117,3 +117,5 @@ export type SignedTransaction = any; // TODO export type SignedCanceledTransaction = any; + +export type Hex = `0x${string}`; diff --git a/yarn.lock b/yarn.lock index 0eda61c..b62c65c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -528,77 +528,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/abstract-provider@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/abstract-provider@npm:5.7.0" - dependencies: - "@ethersproject/bignumber": ^5.7.0 - "@ethersproject/bytes": ^5.7.0 - "@ethersproject/logger": ^5.7.0 - "@ethersproject/networks": ^5.7.0 - "@ethersproject/properties": ^5.7.0 - "@ethersproject/transactions": ^5.7.0 - "@ethersproject/web": ^5.7.0 - checksum: 74cf4696245cf03bb7cc5b6cbf7b4b89dd9a79a1c4688126d214153a938126d4972d42c93182198653ce1de35f2a2cad68be40337d4774b3698a39b28f0228a8 - languageName: node - linkType: hard - -"@ethersproject/abstract-signer@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/abstract-signer@npm:5.7.0" - dependencies: - "@ethersproject/abstract-provider": ^5.7.0 - "@ethersproject/bignumber": ^5.7.0 - "@ethersproject/bytes": ^5.7.0 - "@ethersproject/logger": ^5.7.0 - "@ethersproject/properties": ^5.7.0 - checksum: a823dac9cfb761e009851050ebebd5b229d1b1cc4a75b125c2da130ff37e8218208f7f9d1386f77407705b889b23d4a230ad67185f8872f083143e0073cbfbe3 - languageName: node - linkType: hard - -"@ethersproject/address@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/address@npm:5.7.0" - dependencies: - "@ethersproject/bignumber": ^5.7.0 - "@ethersproject/bytes": ^5.7.0 - "@ethersproject/keccak256": ^5.7.0 - "@ethersproject/logger": ^5.7.0 - "@ethersproject/rlp": ^5.7.0 - checksum: 64ea5ebea9cc0e845c413e6cb1e54e157dd9fc0dffb98e239d3a3efc8177f2ff798cd4e3206cf3660ee8faeb7bef1a47dc0ebef0d7b132c32e61e550c7d4c843 - languageName: node - linkType: hard - -"@ethersproject/base64@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/base64@npm:5.7.0" - dependencies: - "@ethersproject/bytes": ^5.7.0 - checksum: 7dd5d734d623582f08f665434f53685041a3d3b334a0e96c0c8afa8bbcaab934d50e5b6b980e826a8fde8d353e0b18f11e61faf17468177274b8e7c69cd9742b - languageName: node - linkType: hard - -"@ethersproject/basex@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/basex@npm:5.7.0" - dependencies: - "@ethersproject/bytes": ^5.7.0 - "@ethersproject/properties": ^5.7.0 - checksum: 326087b7e1f3787b5fe6cd1cf2b4b5abfafbc355a45e88e22e5e9d6c845b613ffc5301d629b28d5c4d5e2bfe9ec424e6782c804956dff79be05f0098cb5817de - languageName: node - linkType: hard - -"@ethersproject/bignumber@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/bignumber@npm:5.7.0" - dependencies: - "@ethersproject/bytes": ^5.7.0 - "@ethersproject/logger": ^5.7.0 - bn.js: ^5.2.1 - checksum: 8c9a134b76f3feb4ec26a5a27379efb4e156b8fb2de0678a67788a91c7f4e30abe9d948638458e4b20f2e42380da0adacc7c9389d05fce070692edc6ae9b4904 - languageName: node - linkType: hard - "@ethersproject/bytes@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/bytes@npm:5.7.0" @@ -608,42 +537,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/constants@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/constants@npm:5.7.0" - dependencies: - "@ethersproject/bignumber": ^5.7.0 - checksum: 6d4b1355747cce837b3e76ec3bde70e4732736f23b04f196f706ebfa5d4d9c2be50904a390d4d40ce77803b98d03d16a9b6898418e04ba63491933ce08c4ba8a - languageName: node - linkType: hard - -"@ethersproject/hash@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/hash@npm:5.7.0" - dependencies: - "@ethersproject/abstract-signer": ^5.7.0 - "@ethersproject/address": ^5.7.0 - "@ethersproject/base64": ^5.7.0 - "@ethersproject/bignumber": ^5.7.0 - "@ethersproject/bytes": ^5.7.0 - "@ethersproject/keccak256": ^5.7.0 - "@ethersproject/logger": ^5.7.0 - "@ethersproject/properties": ^5.7.0 - "@ethersproject/strings": ^5.7.0 - checksum: 6e9fa8d14eb08171cd32f17f98cc108ec2aeca74a427655f0d689c550fee0b22a83b3b400fad7fb3f41cf14d4111f87f170aa7905bcbcd1173a55f21b06262ef - languageName: node - linkType: hard - -"@ethersproject/keccak256@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/keccak256@npm:5.7.0" - dependencies: - "@ethersproject/bytes": ^5.7.0 - js-sha3: 0.8.0 - checksum: ff70950d82203aab29ccda2553422cbac2e7a0c15c986bd20a69b13606ed8bb6e4fdd7b67b8d3b27d4f841e8222cbaccd33ed34be29f866fec7308f96ed244c6 - languageName: node - linkType: hard - "@ethersproject/logger@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/logger@npm:5.7.0" @@ -651,138 +544,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/networks@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/networks@npm:5.7.0" - dependencies: - "@ethersproject/logger": ^5.7.0 - checksum: 4f4d77e7c59e79cfcba616315a5d0e634a7653acbd11bb06a0028f4bd009b19f9a31556148a1e38f7308f55d1a1d170eb9f065290de9f9cf104b34e91cc348b8 - languageName: node - linkType: hard - -"@ethersproject/properties@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/properties@npm:5.7.0" - dependencies: - "@ethersproject/logger": ^5.7.0 - checksum: 6ab0ccf0c3aadc9221e0cdc5306ce6cd0df7f89f77d77bccdd1277182c9ead0202cd7521329ba3acde130820bf8af299e17cf567d0d497c736ee918207bbf59f - languageName: node - linkType: hard - -"@ethersproject/providers@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/providers@npm:5.7.0" - dependencies: - "@ethersproject/abstract-provider": ^5.7.0 - "@ethersproject/abstract-signer": ^5.7.0 - "@ethersproject/address": ^5.7.0 - "@ethersproject/base64": ^5.7.0 - "@ethersproject/basex": ^5.7.0 - "@ethersproject/bignumber": ^5.7.0 - "@ethersproject/bytes": ^5.7.0 - "@ethersproject/constants": ^5.7.0 - "@ethersproject/hash": ^5.7.0 - "@ethersproject/logger": ^5.7.0 - "@ethersproject/networks": ^5.7.0 - "@ethersproject/properties": ^5.7.0 - "@ethersproject/random": ^5.7.0 - "@ethersproject/rlp": ^5.7.0 - "@ethersproject/sha2": ^5.7.0 - "@ethersproject/strings": ^5.7.0 - "@ethersproject/transactions": ^5.7.0 - "@ethersproject/web": ^5.7.0 - bech32: 1.1.4 - ws: 7.4.6 - checksum: a6f80cea838424ceb367ff8e0f004f9fd6b43a87505da9d6aef33eb2bbc77cdb03ab51709ae83b7aa07d038fadf00634e08d8683fe6ae8b17b9351e3b30b26cb - languageName: node - linkType: hard - -"@ethersproject/random@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/random@npm:5.7.0" - dependencies: - "@ethersproject/bytes": ^5.7.0 - "@ethersproject/logger": ^5.7.0 - checksum: 017829c91cff6c76470852855108115b0b52c611b6be817ed1948d56ba42d6677803ec2012aa5ae298a7660024156a64c11fcf544e235e239ab3f89f0fff7345 - languageName: node - linkType: hard - -"@ethersproject/rlp@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/rlp@npm:5.7.0" - dependencies: - "@ethersproject/bytes": ^5.7.0 - "@ethersproject/logger": ^5.7.0 - checksum: bce165b0f7e68e4d091c9d3cf47b247cac33252df77a095ca4281d32d5eeaaa3695d9bc06b2b057c5015353a68df89f13a4a54a72e888e4beeabbe56b15dda6e - languageName: node - linkType: hard - -"@ethersproject/sha2@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/sha2@npm:5.7.0" - dependencies: - "@ethersproject/bytes": ^5.7.0 - "@ethersproject/logger": ^5.7.0 - hash.js: 1.1.7 - checksum: 09321057c022effbff4cc2d9b9558228690b5dd916329d75c4b1ffe32ba3d24b480a367a7cc92d0f0c0b1c896814d03351ae4630e2f1f7160be2bcfbde435dbc - languageName: node - linkType: hard - -"@ethersproject/signing-key@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/signing-key@npm:5.7.0" - dependencies: - "@ethersproject/bytes": ^5.7.0 - "@ethersproject/logger": ^5.7.0 - "@ethersproject/properties": ^5.7.0 - bn.js: ^5.2.1 - elliptic: 6.5.4 - hash.js: 1.1.7 - checksum: 8f8de09b0aac709683bbb49339bc0a4cd2f95598f3546436c65d6f3c3a847ffa98e06d35e9ed2b17d8030bd2f02db9b7bd2e11c5cf8a71aad4537487ab4cf03a - languageName: node - linkType: hard - -"@ethersproject/strings@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/strings@npm:5.7.0" - dependencies: - "@ethersproject/bytes": ^5.7.0 - "@ethersproject/constants": ^5.7.0 - "@ethersproject/logger": ^5.7.0 - checksum: 5ff78693ae3fdf3cf23e1f6dc047a61e44c8197d2408c42719fef8cb7b7b3613a4eec88ac0ed1f9f5558c74fe0de7ae3195a29ca91a239c74b9f444d8e8b50df - languageName: node - linkType: hard - -"@ethersproject/transactions@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/transactions@npm:5.7.0" - dependencies: - "@ethersproject/address": ^5.7.0 - "@ethersproject/bignumber": ^5.7.0 - "@ethersproject/bytes": ^5.7.0 - "@ethersproject/constants": ^5.7.0 - "@ethersproject/keccak256": ^5.7.0 - "@ethersproject/logger": ^5.7.0 - "@ethersproject/properties": ^5.7.0 - "@ethersproject/rlp": ^5.7.0 - "@ethersproject/signing-key": ^5.7.0 - checksum: a31b71996d2b283f68486241bff0d3ea3f1ba0e8f1322a8fffc239ccc4f4a7eb2ea9994b8fd2f093283fd75f87bae68171e01b6265261f821369aca319884a79 - languageName: node - linkType: hard - -"@ethersproject/web@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/web@npm:5.7.0" - dependencies: - "@ethersproject/base64": ^5.7.0 - "@ethersproject/bytes": ^5.7.0 - "@ethersproject/logger": ^5.7.0 - "@ethersproject/properties": ^5.7.0 - "@ethersproject/strings": ^5.7.0 - checksum: 9d4ca82f8b1295bbc1c59d58cb351641802d2f70f4b7d523fc726f51b0615296da6d6585dee5749b4d5e4a6a9af6d6650d46fe562d5b04f43a0af5c7f7f4a77e - languageName: node - linkType: hard - "@gar/promisify@npm:^1.1.3": version: 1.1.3 resolution: "@gar/promisify@npm:1.1.3" @@ -1303,6 +1064,23 @@ __metadata: languageName: node linkType: hard +"@metamask/polling-controller@npm:^0.2.0": + version: 0.2.0 + resolution: "@metamask/polling-controller@npm:0.2.0" + dependencies: + "@metamask/base-controller": ^3.2.3 + "@metamask/controller-utils": ^5.0.2 + "@metamask/network-controller": ^15.0.0 + "@metamask/utils": ^8.1.0 + "@types/uuid": ^8.3.0 + fast-json-stable-stringify: ^2.1.0 + uuid: ^8.3.2 + peerDependencies: + "@metamask/network-controller": ^15.0.0 + checksum: 4dee3e49b23ba2b92055816dcc68b8e468405d9b00528d14b01c490058dea6e7aae943b19a007adbbbe06aa9a5b61d961211f9de82c8b55c7622599de78eb76e + languageName: node + linkType: hard + "@metamask/rpc-errors@npm:^6.0.0, @metamask/rpc-errors@npm:^6.1.0": version: 6.1.0 resolution: "@metamask/rpc-errors@npm:6.1.0" @@ -1324,9 +1102,7 @@ __metadata: version: 0.0.0-use.local resolution: "@metamask/smart-transactions-controller@workspace:." dependencies: - "@ethersproject/bignumber": ^5.7.0 "@ethersproject/bytes": ^5.7.0 - "@ethersproject/providers": ^5.7.0 "@lavamoat/allow-scripts": ^2.3.1 "@metamask/auto-changelog": ^3.1.0 "@metamask/base-controller": ^3.2.1 @@ -1335,7 +1111,9 @@ __metadata: "@metamask/eslint-config-jest": ^10.0.0 "@metamask/eslint-config-nodejs": ^10.0.0 "@metamask/eslint-config-typescript": ^10.0.0 + "@metamask/eth-query": ^3.0.1 "@metamask/network-controller": ^15.0.0 + "@metamask/polling-controller": ^0.2.0 "@types/jest": ^26.0.24 "@types/lodash": ^4.14.194 "@types/node": ^16.18.31 @@ -1755,6 +1533,13 @@ __metadata: languageName: node linkType: hard +"@types/uuid@npm:^8.3.0": + version: 8.3.4 + resolution: "@types/uuid@npm:8.3.4" + checksum: 6f11f3ff70f30210edaa8071422d405e9c1d4e53abbe50fdce365150d3c698fe7bbff65c1e71ae080cbfb8fded860dbb5e174da96fdbbdfcaa3fb3daa474d20f + languageName: node + linkType: hard + "@types/yargs-parser@npm:*": version: 21.0.0 resolution: "@types/yargs-parser@npm:21.0.0" @@ -2355,13 +2140,6 @@ __metadata: languageName: node linkType: hard -"bech32@npm:1.1.4": - version: 1.1.4 - resolution: "bech32@npm:1.1.4" - checksum: 0e98db619191548390d6f09ff68b0253ba7ae6a55db93dfdbb070ba234c1fd3308c0606fbcc95fad50437227b10011e2698b89f0181f6e7f845c499bd14d0f4b - languageName: node - linkType: hard - "big-integer@npm:^1.6.44": version: 1.6.51 resolution: "big-integer@npm:1.6.51" @@ -2409,7 +2187,7 @@ __metadata: languageName: node linkType: hard -"bn.js@npm:^5.1.2, bn.js@npm:^5.2.1": +"bn.js@npm:^5.1.2": version: 5.2.1 resolution: "bn.js@npm:5.2.1" checksum: 3dd8c8d38055fedfa95c1d5fc3c99f8dd547b36287b37768db0abab3c239711f88ff58d18d155dd8ad902b0b0cee973747b7ae20ea12a09473272b0201c9edd3 @@ -3247,7 +3025,7 @@ __metadata: languageName: node linkType: hard -"elliptic@npm:6.5.4, elliptic@npm:^6.5.2": +"elliptic@npm:^6.5.2": version: 6.5.4 resolution: "elliptic@npm:6.5.4" dependencies: @@ -4027,7 +3805,7 @@ __metadata: languageName: node linkType: hard -"fast-json-stable-stringify@npm:2.x, fast-json-stable-stringify@npm:^2.0.0": +"fast-json-stable-stringify@npm:2.x, fast-json-stable-stringify@npm:^2.0.0, fast-json-stable-stringify@npm:^2.1.0": version: 2.1.0 resolution: "fast-json-stable-stringify@npm:2.1.0" checksum: b191531e36c607977e5b1c47811158733c34ccb3bfde92c44798929e9b4154884378536d26ad90dfecd32e1ffc09c545d23535ad91b3161a27ddbb8ebe0cbecb @@ -4607,7 +4385,7 @@ __metadata: languageName: node linkType: hard -"hash.js@npm:1.1.7, hash.js@npm:^1.0.0, hash.js@npm:^1.0.3, hash.js@npm:^1.1.7": +"hash.js@npm:^1.0.0, hash.js@npm:^1.0.3, hash.js@npm:^1.1.7": version: 1.1.7 resolution: "hash.js@npm:1.1.7" dependencies: @@ -5789,13 +5567,6 @@ __metadata: languageName: node linkType: hard -"js-sha3@npm:0.8.0": - version: 0.8.0 - resolution: "js-sha3@npm:0.8.0" - checksum: 75df77c1fc266973f06cce8309ce010e9e9f07ec35ab12022ed29b7f0d9c8757f5a73e1b35aa24840dced0dea7059085aa143d817aea9e188e2a80d569d9adce - languageName: node - linkType: hard - "js-sha3@npm:^0.5.7": version: 0.5.7 resolution: "js-sha3@npm:0.5.7" @@ -8899,7 +8670,7 @@ __metadata: languageName: node linkType: hard -"ws@npm:7.4.6, ws@npm:^7.4.4": +"ws@npm:^7.4.4": version: 7.4.6 resolution: "ws@npm:7.4.6" peerDependencies: