From dcd86f0d41f052cc3f15b9c5fc9115d57f5bba30 Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Thu, 29 Feb 2024 20:52:20 +0000 Subject: [PATCH 01/12] Remove provider and block tracker constructor options Dynamically retrieve selected network client when required. Throw or ignore in all public methods and helpers. Replace helper options with network client. --- .../src/TransactionController.ts | 271 +++++++++++------- .../src/helpers/GasFeePoller.ts | 15 +- .../src/helpers/IncomingTransactionHelper.ts | 81 ++++-- .../src/helpers/MultichainTrackingHelper.ts | 136 +++++---- .../src/helpers/PendingTransactionTracker.ts | 232 ++++++++++----- .../transaction-controller/src/utils/nonce.ts | 8 +- 6 files changed, 487 insertions(+), 256 deletions(-) diff --git a/packages/transaction-controller/src/TransactionController.ts b/packages/transaction-controller/src/TransactionController.ts index 2b1efd48df..c80e620316 100644 --- a/packages/transaction-controller/src/TransactionController.ts +++ b/packages/transaction-controller/src/TransactionController.ts @@ -15,12 +15,11 @@ import type { import { BaseControllerV1 } from '@metamask/base-controller'; import { query, - NetworkType, ApprovalType, ORIGIN_METAMASK, convertHexToDecimal, } from '@metamask/controller-utils'; -import EthQuery from '@metamask/eth-query'; +import type EthQuery from '@metamask/eth-query'; import type { GasFeeState } from '@metamask/gas-fee-controller'; import type { BlockTracker, @@ -33,6 +32,11 @@ import type { NetworkControllerGetNetworkClientByIdAction, } from '@metamask/network-controller'; import { NetworkClientType } from '@metamask/network-controller'; +import type { AutoManagedNetworkClient } from '@metamask/network-controller/src/create-auto-managed-network-client'; +import type { + CustomNetworkClientConfiguration, + NetworkClientConfiguration, +} from '@metamask/network-controller/src/types'; import { errorCodes, rpcErrors, providerErrors } from '@metamask/rpc-errors'; import type { Hex } from '@metamask/utils'; import { add0x } from '@metamask/utils'; @@ -330,8 +334,6 @@ export class TransactionController extends BaseControllerV1< private readonly inProcessOfSigning: Set = new Set(); - private readonly nonceTracker: NonceTracker; - // TODO: Replace `any` with type // eslint-disable-next-line @typescript-eslint/no-explicit-any private readonly registry: any; @@ -423,8 +425,21 @@ export class TransactionController extends BaseControllerV1< } private async registryLookup(fourBytePrefix: string): Promise { - const registryMethod = await this.registry.lookup(fourBytePrefix); - const parsedRegistryMethod = this.registry.parse(registryMethod); + const selectedNetworkClient = + this.#multichainTrackingHelper.getSelectedNetworkClient(); + + if (!selectedNetworkClient) { + throw providerErrors.disconnected(); + } + + const { provider } = selectedNetworkClient; + + // @ts-expect-error the type in eth-method-registry is inappropriate and should be changed + const registry = new MethodRegistry({ provider }); + + const registryMethod = (await registry.lookup(fourBytePrefix)) as string; + const parsedRegistryMethod = registry.parse(registryMethod); + return { registryMethod, parsedRegistryMethod }; } @@ -451,7 +466,6 @@ export class TransactionController extends BaseControllerV1< constructor( { - blockTracker, disableHistory, disableSendFlowHistory, disableSwaps, @@ -467,7 +481,6 @@ export class TransactionController extends BaseControllerV1< messenger, onNetworkStateChange, pendingTransactions = {}, - provider, securityProviderRequest, getNetworkClientRegistry, isMultichainEnabled = false, @@ -493,8 +506,6 @@ export class TransactionController extends BaseControllerV1< this.isSendFlowHistoryDisabled = disableSendFlowHistory ?? false; this.isHistoryDisabled = disableHistory ?? false; this.isSwapsDisabled = disableSwaps ?? false; - // @ts-expect-error the type in eth-method-registry is inappropriate and should be changed - this.registry = new MethodRegistry({ provider }); this.getSavedGasFees = getSavedGasFees ?? ((_chainId) => undefined); this.getCurrentAccountEIP1559Compatibility = getCurrentAccountEIP1559Compatibility ?? (() => Promise.resolve(true)); @@ -522,15 +533,8 @@ export class TransactionController extends BaseControllerV1< this.publish = hooks?.publish ?? (() => Promise.resolve({ transactionHash: undefined })); - this.nonceTracker = this.#createNonceTracker({ - provider, - blockTracker, - }); - this.#multichainTrackingHelper = new MultichainTrackingHelper({ isMultichainEnabled, - provider, - nonceTracker: this.nonceTracker, incomingTransactionOptions: incomingTransactions, findNetworkClientIdByChainId: (chainId: Hex) => { return this.messagingSystem.call( @@ -545,6 +549,8 @@ export class TransactionController extends BaseControllerV1< ); }) as NetworkController['getNetworkClientById'], getNetworkClientRegistry, + getSelectedNetworkClientId: () => + getNetworkState()?.selectedNetworkClientId, removeIncomingTransactionHelperListeners: this.#removeIncomingTransactionHelperListeners.bind(this), removePendingTransactionTrackerListeners: @@ -568,14 +574,16 @@ export class TransactionController extends BaseControllerV1< includeTokenTransfers: incomingTransactions.includeTokenTransfers, }); + const getSelectedNetworkClient = () => + this.#multichainTrackingHelper.getSelectedNetworkClient(); + this.incomingTransactionHelper = this.#createIncomingTransactionHelper({ - blockTracker, + getNetworkClient: getSelectedNetworkClient, etherscanRemoteTransactionSource, }); this.pendingTransactionTracker = this.#createPendingTransactionTracker({ - provider, - blockTracker, + getNetworkClient: getSelectedNetworkClient, }); this.gasFeeFlows = this.#getGasFeeFlows(); @@ -608,10 +616,8 @@ export class TransactionController extends BaseControllerV1< onNetworkStateChange(() => { log('Detected network change', this.getChainId()); this.pendingTransactionTracker.startIfPendingTransactions(); - this.onBootCleanup(); + this.initApprovedTransactions(); }); - - this.onBootCleanup(); } /** @@ -629,6 +635,7 @@ export class TransactionController extends BaseControllerV1< */ async handleMethodData(fourBytePrefix: string): Promise { const releaseLock = await this.mutex.acquire(); + try { const { methodData } = this.state; const knownMethod = Object.keys(methodData).find( @@ -729,8 +736,9 @@ export class TransactionController extends BaseControllerV1< origin, ); - const chainId = this.getChainId(networkClientId); - const ethQuery = this.#multichainTrackingHelper.getEthQuery({ + const chainId = this.#getChainIdOrThrow(networkClientId); + + const ethQuery = this.#getEthQueryOrThrow({ networkClientId, chainId, }); @@ -759,7 +767,7 @@ export class TransactionController extends BaseControllerV1< networkClientId, }; - await this.updateGasProperties(transactionMeta); + await this.updateGasProperties(transactionMeta, ethQuery); // Checks if a transaction already exists with a given actionId if (!existingTransactionMeta) { @@ -965,10 +973,11 @@ export class TransactionController extends BaseControllerV1< txParams: newTxParams, }); - const ethQuery = this.#multichainTrackingHelper.getEthQuery({ + const ethQuery = this.#getEthQueryOrThrow({ networkClientId: transactionMeta.networkClientId, chainId: transactionMeta.chainId, }); + const hash = await this.publishTransactionForRetry( ethQuery, rawTx, @@ -1133,6 +1142,11 @@ export class TransactionController extends BaseControllerV1< networkClientId: transactionMeta.networkClientId, chainId: transactionMeta.chainId, }); + + if (!ethQuery) { + throw providerErrors.disconnected(); + } + const hash = await this.publishTransactionForRetry( ethQuery, rawTx, @@ -1196,9 +1210,10 @@ export class TransactionController extends BaseControllerV1< transaction: TransactionParams, networkClientId?: NetworkClientId, ) { - const ethQuery = this.#multichainTrackingHelper.getEthQuery({ + const ethQuery = this.#getEthQueryOrThrow({ networkClientId, }); + const { estimatedGas, simulationFails } = await estimateGas( transaction, ethQuery, @@ -1219,9 +1234,10 @@ export class TransactionController extends BaseControllerV1< multiplier: number, networkClientId?: NetworkClientId, ) { - const ethQuery = this.#multichainTrackingHelper.getEthQuery({ + const ethQuery = this.#getEthQueryOrThrow({ networkClientId, }); + const { blockGasLimit, estimatedGas, simulationFails } = await estimateGas( transaction, ethQuery, @@ -1290,7 +1306,9 @@ export class TransactionController extends BaseControllerV1< this.update({ transactions: [] }); return; } - const currentChainId = this.getChainId(); + + const currentChainId = this.#getChainIdOrThrow(); + const newTransactions = this.state.transactions.filter( ({ chainId, txParams }) => { const isMatchingNetwork = ignoreNetwork || chainId === currentChainId; @@ -1561,10 +1579,16 @@ export class TransactionController extends BaseControllerV1< address: string, networkClientId?: NetworkClientId, ): Promise { - return this.#multichainTrackingHelper.getNonceLock( + const nonceLock = await this.#multichainTrackingHelper.getNonceLock( address, networkClientId, ); + + if (!nonceLock) { + throw providerErrors.disconnected(); + } + + return nonceLock; } /** @@ -1623,13 +1647,17 @@ export class TransactionController extends BaseControllerV1< ) as TransactionParams; const updatedTransaction = merge(transactionMeta, editableParams); + + const ethQuery = this.#getEthQueryOrThrow({ + networkClientId: transactionMeta.networkClientId, + chainId: transactionMeta.chainId, + }); + const { type } = await determineTransactionType( updatedTransaction.txParams, - this.#multichainTrackingHelper.getEthQuery({ - networkClientId: transactionMeta.networkClientId, - chainId: transactionMeta.chainId, - }), + ethQuery, ); + updatedTransaction.type = type; this.updateTransaction( @@ -1801,7 +1829,8 @@ export class TransactionController extends BaseControllerV1< * Creates approvals for all unapproved transactions persisted. */ initApprovals() { - const chainId = this.getChainId(); + const chainId = this.#getChainIdOrThrow(); + const unapprovedTxs = this.state.transactions.filter( (transaction) => transaction.status === TransactionStatus.unapproved && @@ -1821,6 +1850,32 @@ export class TransactionController extends BaseControllerV1< } } + /** + * Sign and submit any previously approved transactions. + */ + initApprovedTransactions() { + const approvedTransactions = this.state.transactions.filter( + (transaction) => transaction.status === TransactionStatus.approved, + ); + + if (!approvedTransactions.length) { + return; + } + + log('Processing previously approved transactions', { + count: approvedTransactions.length, + }); + + for (const transactionMeta of approvedTransactions) { + if (this.beforeApproveOnInit(transactionMeta)) { + this.approveTransaction(transactionMeta.id).catch((error) => { + /* istanbul ignore next */ + console.error('Error while submitting persisted transaction', error); + }); + } + } + } + /** * Search transaction metadata for matching entries. * @@ -1844,7 +1899,8 @@ export class TransactionController extends BaseControllerV1< filterToCurrentNetwork?: boolean; limit?: number; } = {}): TransactionMeta[] { - const chainId = this.getChainId(); + const chainId = this.#getChainIdOrThrow(); + // searchCriteria is an object that might have values that aren't predicate // methods. When providing any other value type (string, number, etc), we // consider this shorthand for "check the value at key for strict equality @@ -1996,25 +2052,27 @@ export class TransactionController extends BaseControllerV1< this.update({ transactions: this.trimTransactionsForState(transactions) }); } - private async updateGasProperties(transactionMeta: TransactionMeta) { + private async updateGasProperties( + transactionMeta: TransactionMeta, + ethQuery: EthQuery, + ) { const isEIP1559Compatible = (await this.getEIP1559Compatibility(transactionMeta.networkClientId)) && transactionMeta.txParams.type !== TransactionEnvelopeType.legacy; const { networkClientId, chainId } = transactionMeta; - const isCustomNetwork = networkClientId + const networkType = networkClientId ? this.messagingSystem.call( `NetworkController:getNetworkClientById`, networkClientId, ).configuration.type === NetworkClientType.Custom - : this.getNetworkState().providerConfig.type === NetworkType.rpc; + : this.#getSelectedNetworkClientConfiguration()?.type; + + const isCustomNetwork = networkType === NetworkClientType.Custom; await updateGas({ - ethQuery: this.#multichainTrackingHelper.getEthQuery({ - networkClientId, - chainId, - }), + ethQuery, chainId, isCustomNetwork, txMeta: transactionMeta, @@ -2022,10 +2080,7 @@ export class TransactionController extends BaseControllerV1< await updateGasFees({ eip1559: isEIP1559Compatible, - ethQuery: this.#multichainTrackingHelper.getEthQuery({ - networkClientId, - chainId, - }), + ethQuery, gasFeeFlows: this.gasFeeFlows, getGasFeeEstimates: this.getGasFeeEstimates, getSavedGasFees: this.getSavedGasFees.bind(this), @@ -2033,28 +2088,6 @@ export class TransactionController extends BaseControllerV1< }); } - private onBootCleanup() { - this.submitApprovedTransactions(); - } - - /** - * Force submit approved transactions for all chains. - */ - private submitApprovedTransactions() { - const approvedTransactions = this.state.transactions.filter( - (transaction) => transaction.status === TransactionStatus.approved, - ); - - for (const transactionMeta of approvedTransactions) { - if (this.beforeApproveOnInit(transactionMeta)) { - this.approveTransaction(transactionMeta.id).catch((error) => { - /* istanbul ignore next */ - console.error('Error while submitting persisted transaction', error); - }); - } - } - } - private async processApproval( transactionMeta: TransactionMeta, { @@ -2209,10 +2242,12 @@ export class TransactionController extends BaseControllerV1< return; } + const getNonceLock = (address: string) => + this.#multichainTrackingHelper.getNonceLock(address, networkClientId); + const [nonce, releaseNonce] = await getNextNonce( transactionMeta, - (address: string) => - this.#multichainTrackingHelper.getNonceLock(address, networkClientId), + getNonceLock, ); releaseNonceLock = releaseNonce; @@ -2255,7 +2290,7 @@ export class TransactionController extends BaseControllerV1< return; } - const ethQuery = this.#multichainTrackingHelper.getEthQuery({ + const ethQuery = this.#getEthQueryOrThrow({ networkClientId: transactionMeta.networkClientId, chainId: transactionMeta.chainId, }); @@ -2467,15 +2502,15 @@ export class TransactionController extends BaseControllerV1< return { meta: transaction, isCompleted }; } - private getChainId(networkClientId?: NetworkClientId): Hex { + private getChainId(networkClientId?: NetworkClientId): Hex | undefined { if (networkClientId) { return this.messagingSystem.call( `NetworkController:getNetworkClientById`, networkClientId, ).configuration.chainId; } - const { providerConfig } = this.getNetworkState(); - return providerConfig.chainId; + + return this.#getSelectedNetworkClientConfiguration()?.chainId; } private prepareUnsignedEthTx( @@ -2748,6 +2783,8 @@ export class TransactionController extends BaseControllerV1< this.inProcessOfSigning.add(transactionMeta.id); + log('Signing Test', unsignedEthTx, txParams); + const signedTx = await new Promise((resolve, reject) => { this.sign?.( unsignedEthTx, @@ -2808,7 +2845,7 @@ export class TransactionController extends BaseControllerV1< private getNonceTrackerTransactions( status: TransactionStatus, address: string, - chainId: string = this.getChainId(), + chainId: string, ) { return getAndFormatTransactionsForNonceTracker( chainId, @@ -2839,10 +2876,11 @@ export class TransactionController extends BaseControllerV1< return; } - const ethQuery = this.#multichainTrackingHelper.getEthQuery({ + const ethQuery = this.#getEthQueryOrThrow({ networkClientId: transactionMeta.networkClientId, chainId: transactionMeta.chainId, }); + const { updatedTransactionMeta, approvalTransactionMeta } = await updatePostTransactionBalance(transactionMeta, { ethQuery, @@ -2867,7 +2905,7 @@ export class TransactionController extends BaseControllerV1< }: { provider: Provider; blockTracker: BlockTracker; - chainId?: Hex; + chainId: Hex; }): NonceTracker { return new NonceTracker({ // TODO: Replace `any` with type @@ -2881,24 +2919,24 @@ export class TransactionController extends BaseControllerV1< getConfirmedTransactions: this.getNonceTrackerTransactions.bind( this, TransactionStatus.confirmed, + chainId, ), }); } #createIncomingTransactionHelper({ - blockTracker, + getNetworkClient, etherscanRemoteTransactionSource, - chainId, }: { - blockTracker: BlockTracker; + getNetworkClient: () => + | AutoManagedNetworkClient + | undefined; etherscanRemoteTransactionSource: EtherscanRemoteTransactionSource; - chainId?: Hex; }): IncomingTransactionHelper { const incomingTransactionHelper = new IncomingTransactionHelper({ - blockTracker, getCurrentAccount: this.getSelectedAddress, getLastFetchedBlockNumbers: () => this.state.lastFetchedBlockNumbers, - getChainId: chainId ? () => chainId : this.getChainId.bind(this), + getNetworkClient, isEnabled: this.#incomingTransactionOptions.isEnabled, queryEntireHistory: this.#incomingTransactionOptions.queryEntireHistory, remoteTransactionSource: etherscanRemoteTransactionSource, @@ -2912,27 +2950,20 @@ export class TransactionController extends BaseControllerV1< } #createPendingTransactionTracker({ - provider, - blockTracker, - chainId, + getNetworkClient, }: { - provider: Provider; - blockTracker: BlockTracker; - chainId?: Hex; + getNetworkClient: () => + | AutoManagedNetworkClient + | undefined; }): PendingTransactionTracker { - const ethQuery = new EthQuery(provider); - const getChainId = chainId ? () => chainId : this.getChainId.bind(this); - const pendingTransactionTracker = new PendingTransactionTracker({ approveTransaction: this.approveTransaction.bind(this), - blockTracker, - getChainId, - getEthQuery: () => ethQuery, + getNetworkClient, getTransactions: () => this.state.transactions, isResubmitEnabled: this.#pendingTransactionOptions.isResubmitEnabled, - getGlobalLock: () => + getGlobalLock: (chainId: Hex) => this.#multichainTrackingHelper.acquireNonceLockForChainIdKey({ - chainId: getChainId(), + chainId, }), publishTransaction: this.publishTransaction.bind(this), hooks: { @@ -3021,10 +3052,7 @@ export class TransactionController extends BaseControllerV1< ); } - #getNonceTrackerPendingTransactions( - chainId: string | undefined, - address: string, - ) { + #getNonceTrackerPendingTransactions(chainId: string, address: string) { const standardPendingTransactions = this.getNonceTrackerTransactions( TransactionStatus.submitted, address, @@ -3035,6 +3063,7 @@ export class TransactionController extends BaseControllerV1< address, chainId, ); + return [...standardPendingTransactions, ...externalPendingTransactions]; } @@ -3096,4 +3125,40 @@ export class TransactionController extends BaseControllerV1< this.update({ transactions: this.trimTransactionsForState(transactions) }); } + + #getEthQueryOrThrow({ + networkClientId, + chainId, + }: { + networkClientId?: NetworkClientId; + chainId?: Hex; + }): EthQuery { + const ethQuery = this.#multichainTrackingHelper.getEthQuery({ + networkClientId, + chainId, + }); + + if (!ethQuery) { + throw providerErrors.disconnected(); + } + + return ethQuery; + } + + #getChainIdOrThrow(networkClientId?: NetworkClientId): Hex { + const chainId = this.getChainId(networkClientId); + + if (!chainId) { + throw providerErrors.disconnected(); + } + + return chainId; + } + + #getSelectedNetworkClientConfiguration(): + | CustomNetworkClientConfiguration + | undefined { + return this.#multichainTrackingHelper.getSelectedNetworkClient() + ?.configuration; + } } diff --git a/packages/transaction-controller/src/helpers/GasFeePoller.ts b/packages/transaction-controller/src/helpers/GasFeePoller.ts index 52343389b5..be6ed3b2c4 100644 --- a/packages/transaction-controller/src/helpers/GasFeePoller.ts +++ b/packages/transaction-controller/src/helpers/GasFeePoller.ts @@ -22,7 +22,10 @@ export class GasFeePoller { #gasFeeFlows: GasFeeFlow[]; - #getEthQuery: (chainId: Hex, networkClientId?: NetworkClientId) => EthQuery; + #getEthQuery: ( + chainId: Hex, + networkClientId?: NetworkClientId, + ) => EthQuery | undefined; #getGasFeeControllerEstimates: () => Promise; @@ -49,7 +52,10 @@ export class GasFeePoller { onStateChange, }: { gasFeeFlows: GasFeeFlow[]; - getEthQuery: (chainId: Hex, networkClientId?: NetworkClientId) => EthQuery; + getEthQuery: ( + chainId: Hex, + networkClientId?: NetworkClientId, + ) => EthQuery | undefined; getGasFeeControllerEstimates: () => Promise; getTransactions: () => TransactionMeta[]; onStateChange: (listener: () => void) => void; @@ -134,6 +140,11 @@ export class GasFeePoller { ); } + if (!ethQuery) { + log('Provider not available', transactionMeta.id); + return; + } + const request: GasFeeFlowRequest = { ethQuery, getGasFeeControllerEstimates: this.#getGasFeeControllerEstimates, diff --git a/packages/transaction-controller/src/helpers/IncomingTransactionHelper.ts b/packages/transaction-controller/src/helpers/IncomingTransactionHelper.ts index bd8b66aeaf..7f5d4e47ca 100644 --- a/packages/transaction-controller/src/helpers/IncomingTransactionHelper.ts +++ b/packages/transaction-controller/src/helpers/IncomingTransactionHelper.ts @@ -1,4 +1,8 @@ -import type { BlockTracker } from '@metamask/network-controller'; +import type { + BlockTracker, + NetworkClientConfiguration, +} from '@metamask/network-controller'; +import type { AutoManagedNetworkClient } from '@metamask/network-controller/src/create-auto-managed-network-client'; import type { Hex } from '@metamask/utils'; import { Mutex } from 'async-mutex'; import EventEmitter from 'events'; @@ -33,7 +37,7 @@ export type IncomingTransactionOptions = { export class IncomingTransactionHelper { hub: EventEmitter; - #blockTracker: BlockTracker; + #blockTracker: BlockTracker | undefined; #getCurrentAccount: () => string; @@ -41,7 +45,9 @@ export class IncomingTransactionHelper { #getLocalTransactions: () => TransactionMeta[]; - #getChainId: () => Hex; + #getNetworkClient: () => + | AutoManagedNetworkClient + | undefined; #isEnabled: () => boolean; @@ -60,22 +66,22 @@ export class IncomingTransactionHelper { #updateTransactions: boolean; constructor({ - blockTracker, getCurrentAccount, getLastFetchedBlockNumbers, getLocalTransactions, - getChainId, + getNetworkClient, isEnabled, queryEntireHistory, remoteTransactionSource, transactionLimit, updateTransactions, }: { - blockTracker: BlockTracker; getCurrentAccount: () => string; getLastFetchedBlockNumbers: () => Record; getLocalTransactions?: () => TransactionMeta[]; - getChainId: () => Hex; + getNetworkClient: () => + | AutoManagedNetworkClient + | undefined; isEnabled?: () => boolean; queryEntireHistory?: boolean; remoteTransactionSource: RemoteTransactionSource; @@ -84,11 +90,10 @@ export class IncomingTransactionHelper { }) { this.hub = new EventEmitter(); - this.#blockTracker = blockTracker; this.#getCurrentAccount = getCurrentAccount; this.#getLastFetchedBlockNumbers = getLastFetchedBlockNumbers; this.#getLocalTransactions = getLocalTransactions || (() => []); - this.#getChainId = getChainId; + this.#getNetworkClient = getNetworkClient; this.#isEnabled = isEnabled ?? (() => true); this.#isRunning = false; this.#queryEntireHistory = queryEntireHistory ?? true; @@ -112,16 +117,25 @@ export class IncomingTransactionHelper { return; } - if (!this.#canStart()) { + const networkClient = this.#getNetworkClient(); + + if (!networkClient) { + log('Cannot start as network client is not available'); + return; + } + + if (!this.#canStart(networkClient.configuration.chainId)) { return; } + this.#blockTracker = networkClient.blockTracker; this.#blockTracker.addListener('latest', this.#onLatestBlock); this.#isRunning = true; } stop() { - this.#blockTracker.removeListener('latest', this.#onLatestBlock); + this.#blockTracker?.removeListener('latest', this.#onLatestBlock); + this.#blockTracker = undefined; this.#isRunning = false; } @@ -131,7 +145,14 @@ export class IncomingTransactionHelper { log('Checking for incoming transactions'); try { - if (!this.#canStart()) { + const networkClient = this.#getNetworkClient(); + + if (!networkClient || !this.#blockTracker) { + log('Cannot update as network client is not available'); + return; + } + + if (!this.#canStart(networkClient.configuration.chainId)) { return; } @@ -143,9 +164,13 @@ export class IncomingTransactionHelper { const additionalLastFetchedKeys = this.#remoteTransactionSource.getLastBlockVariations?.() ?? []; - const fromBlock = this.#getFromBlock(latestBlockNumber); + const fromBlock = this.#getFromBlock( + latestBlockNumber, + networkClient.configuration.chainId, + ); + const address = this.#getCurrentAccount(); - const currentChainId = this.#getChainId(); + const currentChainId = networkClient.configuration.chainId; let remoteTransactions = []; @@ -197,9 +222,11 @@ export class IncomingTransactionHelper { updated: updatedTransactions, }); } + this.#updateLastFetchedBlockNumber( remoteTransactions, additionalLastFetchedKeys, + currentChainId, ); } finally { releaseLock(); @@ -241,16 +268,21 @@ export class IncomingTransactionHelper { ); } - #getLastFetchedBlockNumberDec(): number { + #getLastFetchedBlockNumberDec(chainId: Hex): number { const additionalLastFetchedKeys = this.#remoteTransactionSource.getLastBlockVariations?.() ?? []; - const lastFetchedKey = this.#getBlockNumberKey(additionalLastFetchedKeys); + + const lastFetchedKey = this.#getBlockNumberKey( + additionalLastFetchedKeys, + chainId, + ); + const lastFetchedBlockNumbers = this.#getLastFetchedBlockNumbers(); return lastFetchedBlockNumbers[lastFetchedKey]; } - #getFromBlock(latestBlockNumber: number): number | undefined { - const lastFetchedBlockNumber = this.#getLastFetchedBlockNumberDec(); + #getFromBlock(latestBlockNumber: number, chainId: Hex): number | undefined { + const lastFetchedBlockNumber = this.#getLastFetchedBlockNumberDec(chainId); if (lastFetchedBlockNumber) { return lastFetchedBlockNumber + 1; @@ -264,6 +296,7 @@ export class IncomingTransactionHelper { #updateLastFetchedBlockNumber( remoteTxs: TransactionMeta[], additionalKeys: string[], + chainId: Hex, ) { let lastFetchedBlockNumber = -1; @@ -282,7 +315,7 @@ export class IncomingTransactionHelper { return; } - const lastFetchedKey = this.#getBlockNumberKey(additionalKeys); + const lastFetchedKey = this.#getBlockNumberKey(additionalKeys, chainId); const lastFetchedBlockNumbers = this.#getLastFetchedBlockNumbers(); const previousValue = lastFetchedBlockNumbers[lastFetchedKey]; @@ -297,19 +330,17 @@ export class IncomingTransactionHelper { }); } - #getBlockNumberKey(additionalKeys: string[]): string { - const currentChainId = this.#getChainId(); + #getBlockNumberKey(additionalKeys: string[], chainId: Hex): string { const currentAccount = this.#getCurrentAccount()?.toLowerCase(); - return [currentChainId, currentAccount, ...additionalKeys].join('#'); + return [chainId, currentAccount, ...additionalKeys].join('#'); } - #canStart(): boolean { + #canStart(chainId: Hex): boolean { const isEnabled = this.#isEnabled(); - const currentChainId = this.#getChainId(); const isSupportedNetwork = - this.#remoteTransactionSource.isSupportedNetwork(currentChainId); + this.#remoteTransactionSource.isSupportedNetwork(chainId); return isEnabled && isSupportedNetwork; } diff --git a/packages/transaction-controller/src/helpers/MultichainTrackingHelper.ts b/packages/transaction-controller/src/helpers/MultichainTrackingHelper.ts index 3af5c2c09a..a67f95b684 100644 --- a/packages/transaction-controller/src/helpers/MultichainTrackingHelper.ts +++ b/packages/transaction-controller/src/helpers/MultichainTrackingHelper.ts @@ -7,11 +7,16 @@ import type { Provider, NetworkControllerStateChangeEvent, } from '@metamask/network-controller'; +import type { AutoManagedNetworkClient } from '@metamask/network-controller/src/create-auto-managed-network-client'; +import type { + CustomNetworkClientConfiguration, + NetworkClientConfiguration, +} from '@metamask/network-controller/src/types'; import type { Hex } from '@metamask/utils'; import { Mutex } from 'async-mutex'; import type { NonceLock, NonceTracker } from 'nonce-tracker'; -import { incomingTransactionsLogger as log } from '../logger'; +import { createModuleLogger, projectLogger } from '../logger'; import { EtherscanRemoteTransactionSource } from './EtherscanRemoteTransactionSource'; import type { IncomingTransactionHelper, @@ -19,6 +24,8 @@ import type { } from './IncomingTransactionHelper'; import type { PendingTransactionTracker } from './PendingTransactionTracker'; +const log = createModuleLogger(projectLogger, 'multichain'); + /** * Registry of network clients provided by the NetworkController */ @@ -28,13 +35,12 @@ type NetworkClientRegistry = ReturnType< export type MultichainTrackingHelperOptions = { isMultichainEnabled: boolean; - provider: Provider; - nonceTracker: NonceTracker; incomingTransactionOptions: IncomingTransactionOptions; findNetworkClientIdByChainId: NetworkController['findNetworkClientIdByChainId']; getNetworkClientById: NetworkController['getNetworkClientById']; getNetworkClientRegistry: NetworkController['getNetworkClientRegistry']; + getSelectedNetworkClientId: () => string | undefined; removeIncomingTransactionHelperListeners: ( IncomingTransactionHelper: IncomingTransactionHelper, @@ -45,17 +51,18 @@ export type MultichainTrackingHelperOptions = { createNonceTracker: (opts: { provider: Provider; blockTracker: BlockTracker; - chainId?: Hex; + chainId: Hex; }) => NonceTracker; createIncomingTransactionHelper: (opts: { - blockTracker: BlockTracker; + getNetworkClient: () => + | AutoManagedNetworkClient + | undefined; etherscanRemoteTransactionSource: EtherscanRemoteTransactionSource; - chainId?: Hex; }) => IncomingTransactionHelper; createPendingTransactionTracker: (opts: { - provider: Provider; - blockTracker: BlockTracker; - chainId?: Hex; + getNetworkClient: () => + | AutoManagedNetworkClient + | undefined; }) => PendingTransactionTracker; onNetworkStateChange: ( listener: ( @@ -67,10 +74,6 @@ export type MultichainTrackingHelperOptions = { export class MultichainTrackingHelper { #isMultichainEnabled: boolean; - readonly #provider: Provider; - - readonly #nonceTracker: NonceTracker; - readonly #incomingTransactionOptions: IncomingTransactionOptions; readonly #findNetworkClientIdByChainId: NetworkController['findNetworkClientIdByChainId']; @@ -79,6 +82,8 @@ export class MultichainTrackingHelper { readonly #getNetworkClientRegistry: NetworkController['getNetworkClientRegistry']; + readonly #getSelectedNetworkClientId: () => string | undefined; + readonly #removeIncomingTransactionHelperListeners: ( IncomingTransactionHelper: IncomingTransactionHelper, ) => void; @@ -90,19 +95,20 @@ export class MultichainTrackingHelper { readonly #createNonceTracker: (opts: { provider: Provider; blockTracker: BlockTracker; - chainId?: Hex; + chainId: Hex; }) => NonceTracker; readonly #createIncomingTransactionHelper: (opts: { - blockTracker: BlockTracker; - chainId?: Hex; + getNetworkClient: () => + | AutoManagedNetworkClient + | undefined; etherscanRemoteTransactionSource: EtherscanRemoteTransactionSource; }) => IncomingTransactionHelper; readonly #createPendingTransactionTracker: (opts: { - provider: Provider; - blockTracker: BlockTracker; - chainId?: Hex; + getNetworkClient: () => + | AutoManagedNetworkClient + | undefined; }) => PendingTransactionTracker; readonly #nonceMutexesByChainId = new Map>(); @@ -123,12 +129,11 @@ export class MultichainTrackingHelper { constructor({ isMultichainEnabled, - provider, - nonceTracker, incomingTransactionOptions, findNetworkClientIdByChainId, getNetworkClientById, getNetworkClientRegistry, + getSelectedNetworkClientId, removeIncomingTransactionHelperListeners, removePendingTransactionTrackerListeners, createNonceTracker, @@ -137,13 +142,12 @@ export class MultichainTrackingHelper { onNetworkStateChange, }: MultichainTrackingHelperOptions) { this.#isMultichainEnabled = isMultichainEnabled; - this.#provider = provider; - this.#nonceTracker = nonceTracker; this.#incomingTransactionOptions = incomingTransactionOptions; this.#findNetworkClientIdByChainId = findNetworkClientIdByChainId; this.#getNetworkClientById = getNetworkClientById; this.#getNetworkClientRegistry = getNetworkClientRegistry; + this.#getSelectedNetworkClientId = getSelectedNetworkClientId; this.#removeIncomingTransactionHelperListeners = removeIncomingTransactionHelperListeners; @@ -186,36 +190,35 @@ export class MultichainTrackingHelper { }: { networkClientId?: NetworkClientId; chainId?: Hex; - } = {}): EthQuery { - if (!this.#isMultichainEnabled) { - return new EthQuery(this.#provider); + } = {}): EthQuery | undefined { + if (!this.#isMultichainEnabled || (!networkClientId && !chainId)) { + const selectedNetworkClient = this.getSelectedNetworkClient(); + + return selectedNetworkClient + ? new EthQuery(selectedNetworkClient.provider) + : undefined; } + let networkClient: NetworkClient | undefined; if (networkClientId) { try { networkClient = this.#getNetworkClientById(networkClientId); } catch (err) { - log('failed to get network client by networkClientId'); + log('Failed to get network client by networkClientId', networkClientId); } } + if (!networkClient && chainId) { try { networkClientId = this.#findNetworkClientIdByChainId(chainId); networkClient = this.#getNetworkClientById(networkClientId); } catch (err) { - log('failed to get network client by chainId'); + log('Failed to get network client by chainId', chainId); } } - if (networkClient) { - return new EthQuery(networkClient.provider); - } - - // NOTE(JL): we're not ready to drop globally selected ethQuery yet. - // Some calls to getEthQuery only have access to optional networkClientId - // throw new Error('failed to get eth query instance'); - return new EthQuery(this.#provider); + return networkClient ? new EthQuery(networkClient.provider) : undefined; } /** @@ -258,9 +261,14 @@ export class MultichainTrackingHelper { async getNonceLock( address: string, networkClientId?: NetworkClientId, - ): Promise { + ): Promise { let releaseLockForChainIdKey: (() => void) | undefined; - let nonceTracker = this.#nonceTracker; + let nonceTracker: NonceTracker | undefined; + + if (!networkClientId || !this.#isMultichainEnabled) { + nonceTracker = this.#getSelectedNonceTracker(); + } + if (networkClientId && this.#isMultichainEnabled) { const networkClient = this.#getNetworkClientById(networkClientId); releaseLockForChainIdKey = await this.acquireNonceLockForChainIdKey({ @@ -274,6 +282,10 @@ export class MultichainTrackingHelper { nonceTracker = trackers.nonceTracker; } + if (!nonceTracker) { + return undefined; + } + // Acquires the lock for the chainId + address and the nonceLock from the nonceTracker, then // couples them together by replacing the nonceLock's releaseLock method with // an anonymous function that calls releases both the original nonceLock and the @@ -342,6 +354,25 @@ export class MultichainTrackingHelper { } } + getSelectedNetworkClient(): + | AutoManagedNetworkClient + | undefined { + const selectedNetworkClientId = this.#getSelectedNetworkClientId(); + + if (!selectedNetworkClientId) { + log('Cannot get selected client as no selected network client ID'); + return undefined; + } + + const selectedNetworkClient = this.#getNetworkClientById( + selectedNetworkClientId, + ); + + log('Retrieved selected network client', selectedNetworkClientId); + + return selectedNetworkClient; + } + #refreshTrackingMap = (networkClients: NetworkClientRegistry) => { this.#refreshEtherscanRemoteTransactionSources(networkClients); @@ -386,11 +417,9 @@ export class MultichainTrackingHelper { return; } - const { - provider, - blockTracker, - configuration: { chainId }, - } = this.#getNetworkClientById(networkClientId); + const networkClient = this.#getNetworkClientById(networkClientId); + const { provider, blockTracker, configuration } = networkClient; + const { chainId } = configuration; let etherscanRemoteTransactionSource = this.#etherscanRemoteTransactionSourcesMap.get(chainId); @@ -412,15 +441,12 @@ export class MultichainTrackingHelper { }); const incomingTransactionHelper = this.#createIncomingTransactionHelper({ - blockTracker, + getNetworkClient: () => networkClient, etherscanRemoteTransactionSource, - chainId, }); const pendingTransactionTracker = this.#createPendingTransactionTracker({ - provider, - blockTracker, - chainId, + getNetworkClient: () => networkClient, }); this.#trackingMap.set(networkClientId, { @@ -451,4 +477,18 @@ export class MultichainTrackingHelper { this.#etherscanRemoteTransactionSourcesMap.delete(chainId); }); }; + + #getSelectedNonceTracker(): NonceTracker | undefined { + const selectedNetworkClient = this.getSelectedNetworkClient(); + + if (!selectedNetworkClient) { + log('Cannot get nonce lock as selected network client is unavailable'); + return undefined; + } + + const { provider, blockTracker, configuration } = selectedNetworkClient; + const { chainId } = configuration; + + return this.#createNonceTracker({ provider, blockTracker, chainId }); + } } diff --git a/packages/transaction-controller/src/helpers/PendingTransactionTracker.ts b/packages/transaction-controller/src/helpers/PendingTransactionTracker.ts index c23bfc3e8c..9560d4e76b 100644 --- a/packages/transaction-controller/src/helpers/PendingTransactionTracker.ts +++ b/packages/transaction-controller/src/helpers/PendingTransactionTracker.ts @@ -1,9 +1,8 @@ import { query } from '@metamask/controller-utils'; -import type EthQuery from '@metamask/eth-query'; -import type { - BlockTracker, - NetworkClientId, -} from '@metamask/network-controller'; +import EthQuery from '@metamask/eth-query'; +import type { NetworkClientConfiguration } from '@metamask/network-controller'; +import type { AutoManagedNetworkClient } from '@metamask/network-controller/src/create-auto-managed-network-client'; +import type { Hex } from '@metamask/utils'; import { createModuleLogger } from '@metamask/utils'; import EventEmitter from 'events'; @@ -61,13 +60,17 @@ export class PendingTransactionTracker { #approveTransaction: (transactionId: string) => Promise; - #blockTracker: BlockTracker; + #beforeCheckPendingTransaction: (transactionMeta: TransactionMeta) => boolean; + + #beforePublish: (transactionMeta: TransactionMeta) => boolean; #droppedBlockCountByHash: Map; - #getChainId: () => string; + #getGlobalLock: (chainId: Hex) => Promise<() => void>; - #getEthQuery: (networkClientId?: NetworkClientId) => EthQuery; + #getNetworkClient: () => + | AutoManagedNetworkClient + | undefined; #getTransactions: () => TransactionMeta[]; @@ -77,21 +80,15 @@ export class PendingTransactionTracker { // eslint-disable-next-line @typescript-eslint/no-explicit-any #listener: any; - #getGlobalLock: () => Promise<() => void>; + #networkClient?: AutoManagedNetworkClient; #publishTransaction: (ethQuery: EthQuery, rawTx: string) => Promise; #running: boolean; - #beforeCheckPendingTransaction: (transactionMeta: TransactionMeta) => boolean; - - #beforePublish: (transactionMeta: TransactionMeta) => boolean; - constructor({ approveTransaction, - blockTracker, - getChainId, - getEthQuery, + getNetworkClient, getTransactions, isResubmitEnabled, getGlobalLock, @@ -99,12 +96,12 @@ export class PendingTransactionTracker { hooks, }: { approveTransaction: (transactionId: string) => Promise; - blockTracker: BlockTracker; - getChainId: () => string; - getEthQuery: (networkClientId?: NetworkClientId) => EthQuery; + getNetworkClient: () => + | AutoManagedNetworkClient + | undefined; getTransactions: () => TransactionMeta[]; isResubmitEnabled?: boolean; - getGlobalLock: () => Promise<() => void>; + getGlobalLock: (chainId: Hex) => Promise<() => void>; publishTransaction: (ethQuery: EthQuery, rawTx: string) => Promise; hooks?: { beforeCheckPendingTransaction?: ( @@ -116,10 +113,8 @@ export class PendingTransactionTracker { this.hub = new EventEmitter() as PendingTransactionTrackerEventEmitter; this.#approveTransaction = approveTransaction; - this.#blockTracker = blockTracker; this.#droppedBlockCountByHash = new Map(); - this.#getChainId = getChainId; - this.#getEthQuery = getEthQuery; + this.#getNetworkClient = getNetworkClient; this.#getTransactions = getTransactions; this.#isResubmitEnabled = isResubmitEnabled ?? true; this.#listener = this.#onLatestBlock.bind(this); @@ -132,39 +127,68 @@ export class PendingTransactionTracker { } startIfPendingTransactions = () => { - const pendingTransactions = this.#getPendingTransactions(); + this.#networkClient = this.#getNetworkClient(); + + if (!this.#networkClient) { + log('Unable to start as network client is not available'); + return; + } + + const pendingTransactions = this.#getPendingTransactions( + this.#networkClient.configuration.chainId, + ); if (pendingTransactions.length) { - this.#start(); + this.#start(this.#networkClient); } else { this.stop(); } }; /** - * Force checks the network if the given transaction is confirmed and updates it's status. + * Force checks the network if the given transaction is confirmed and updates its status. * * @param txMeta - The transaction to check */ async forceCheckTransaction(txMeta: TransactionMeta) { - const releaseLock = await this.#getGlobalLock(); + let releaseLock: (() => void) | undefined; try { - await this.#checkTransaction(txMeta); + const networkClient = this.#getNetworkClient(); + + if (!networkClient) { + log( + 'Cannot force check transaction as network client not available', + txMeta.id, + ); + return; + } + + releaseLock = await this.#getGlobalLock( + networkClient.configuration.chainId, + ); + + const ethQuery = new EthQuery(networkClient.provider); + + await this.#checkTransaction( + txMeta, + ethQuery, + networkClient.configuration.chainId, + ); } catch (error) { /* istanbul ignore next */ - log('Failed to check transaction', error); + log('Failed to force check transaction', error); } finally { - releaseLock(); + releaseLock?.(); } } - #start() { + #start(networkClient: AutoManagedNetworkClient) { if (this.#running) { return; } - this.#blockTracker.on('latest', this.#listener); + networkClient.blockTracker.on('latest', this.#listener); this.#running = true; log('Started polling'); @@ -175,36 +199,54 @@ export class PendingTransactionTracker { return; } - this.#blockTracker.removeListener('latest', this.#listener); + this.#networkClient?.blockTracker?.removeListener('latest', this.#listener); + this.#networkClient = undefined; this.#running = false; log('Stopped polling'); } async #onLatestBlock(latestBlockNumber: string) { - const releaseLock = await this.#getGlobalLock(); - try { - await this.#checkTransactions(); - } catch (error) { - /* istanbul ignore next */ - log('Failed to check transactions', error); - } finally { - releaseLock(); - } + const networkClient = this.#getNetworkClient(); - try { - await this.#resubmitTransactions(latestBlockNumber); + if (!networkClient) { + log('Cannot process latest block as network client not available'); + return; + } + + const releaseLock = await this.#getGlobalLock( + networkClient.configuration.chainId, + ); + + try { + await this.#checkTransactions(networkClient); + } catch (error) { + /* istanbul ignore next */ + log('Failed to check transactions', error); + } finally { + releaseLock(); + } + + try { + await this.#resubmitTransactions(latestBlockNumber, networkClient); + } catch (error) { + /* istanbul ignore next */ + log('Failed to resubmit transactions', error); + } } catch (error) { - /* istanbul ignore next */ - log('Failed to resubmit transactions', error); + log('Failed to process latest block', error); } } - async #checkTransactions() { + async #checkTransactions( + networkClient: AutoManagedNetworkClient, + ) { log('Checking transactions'); - const pendingTransactions = this.#getPendingTransactions(); + const pendingTransactions = this.#getPendingTransactions( + networkClient.configuration.chainId, + ); if (!pendingTransactions.length) { log('No pending transactions to check'); @@ -216,19 +258,32 @@ export class PendingTransactionTracker { ids: pendingTransactions.map((tx) => tx.id), }); + const ethQuery = new EthQuery(networkClient.provider); + await Promise.all( - pendingTransactions.map((tx) => this.#checkTransaction(tx)), + pendingTransactions.map((tx) => + this.#checkTransaction( + tx, + ethQuery, + networkClient.configuration.chainId, + ), + ), ); } - async #resubmitTransactions(latestBlockNumber: string) { + async #resubmitTransactions( + latestBlockNumber: string, + networkClient: AutoManagedNetworkClient, + ) { if (!this.#isResubmitEnabled || !this.#running) { return; } log('Resubmitting transactions'); - const pendingTransactions = this.#getPendingTransactions(); + const pendingTransactions = this.#getPendingTransactions( + networkClient.configuration.chainId, + ); if (!pendingTransactions.length) { log('No pending transactions to resubmit'); @@ -240,9 +295,11 @@ export class PendingTransactionTracker { ids: pendingTransactions.map((tx) => tx.id), }); + const ethQuery = new EthQuery(networkClient.provider); + for (const txMeta of pendingTransactions) { try { - await this.#resubmitTransaction(txMeta, latestBlockNumber); + await this.#resubmitTransaction(txMeta, latestBlockNumber, ethQuery); // TODO: Replace `any` with type // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error: any) { @@ -273,6 +330,7 @@ export class PendingTransactionTracker { async #resubmitTransaction( txMeta: TransactionMeta, latestBlockNumber: string, + ethQuery: EthQuery | undefined, ) { if (!this.#isResubmitDue(txMeta, latestBlockNumber)) { return; @@ -280,7 +338,7 @@ export class PendingTransactionTracker { log('Resubmitting transaction', txMeta.id); - const { rawTx } = txMeta; + const { id, rawTx } = txMeta; if (!this.#beforePublish(txMeta)) { return; @@ -292,7 +350,11 @@ export class PendingTransactionTracker { return; } - const ethQuery = this.#getEthQuery(txMeta.networkClientId); + if (!ethQuery) { + log('Cannot resubmit transaction as provider not available', id); + return; + } + await this.#publishTransaction(ethQuery, rawTx); txMeta.retryCount = (txMeta.retryCount ?? 0) + 1; @@ -332,7 +394,11 @@ export class PendingTransactionTracker { return blocksSinceFirstRetry >= requiredBlocksSinceFirstRetry; } - async #checkTransaction(txMeta: TransactionMeta) { + async #checkTransaction( + txMeta: TransactionMeta, + ethQuery: EthQuery, + chainId: Hex, + ) { const { hash, id } = txMeta; if (!hash && this.#beforeCheckPendingTransaction(txMeta)) { @@ -347,14 +413,14 @@ export class PendingTransactionTracker { return; } - if (this.#isNonceTaken(txMeta)) { + if (this.#isNonceTaken(txMeta, chainId)) { log('Nonce already taken', id); this.#dropTransaction(txMeta); return; } try { - const receipt = await this.#getTransactionReceipt(hash); + const receipt = await this.#getTransactionReceipt(ethQuery, hash); const isSuccess = receipt?.status === RECEIPT_STATUS_SUCCESS; const isFailure = receipt?.status === RECEIPT_STATUS_FAILURE; @@ -372,11 +438,15 @@ export class PendingTransactionTracker { const { blockNumber, blockHash } = receipt || {}; if (isSuccess && blockNumber && blockHash) { - await this.#onTransactionConfirmed(txMeta, { - ...receipt, - blockNumber, - blockHash, - }); + await this.#onTransactionConfirmed( + txMeta, + { + ...receipt, + blockNumber, + blockHash, + }, + ethQuery, + ); return; } @@ -394,7 +464,7 @@ export class PendingTransactionTracker { return; } - if (await this.#isTransactionDropped(txMeta)) { + if (await this.#isTransactionDropped(txMeta, ethQuery)) { this.#dropTransaction(txMeta); } } @@ -402,6 +472,7 @@ export class PendingTransactionTracker { async #onTransactionConfirmed( txMeta: TransactionMeta, receipt: SuccessfulTransactionReceipt, + ethQuery: EthQuery, ) { const { id } = txMeta; const { blockHash } = receipt; @@ -409,7 +480,7 @@ export class PendingTransactionTracker { log('Transaction confirmed', id); const { baseFeePerGas, timestamp: blockTimestamp } = - await this.#getBlockByHash(blockHash, false); + await this.#getBlockByHash(blockHash, false, ethQuery); txMeta.baseFeePerGas = baseFeePerGas; txMeta.blockTimestamp = blockTimestamp; @@ -426,7 +497,7 @@ export class PendingTransactionTracker { this.hub.emit('transaction-confirmed', txMeta); } - async #isTransactionDropped(txMeta: TransactionMeta) { + async #isTransactionDropped(txMeta: TransactionMeta, ethQuery: EthQuery) { const { hash, id, @@ -438,7 +509,11 @@ export class PendingTransactionTracker { return false; } - const networkNextNonceHex = await this.#getNetworkTransactionCount(from); + const networkNextNonceHex = await this.#getNetworkTransactionCount( + from, + ethQuery, + ); + const networkNextNonceNumber = parseInt(networkNextNonceHex, 16); const nonceNumber = parseInt(nonce, 16); @@ -465,10 +540,10 @@ export class PendingTransactionTracker { return true; } - #isNonceTaken(txMeta: TransactionMeta): boolean { + #isNonceTaken(txMeta: TransactionMeta, chainId: Hex): boolean { const { id, txParams } = txMeta; - return this.#getCurrentChainTransactions().some( + return this.#getCurrentChainTransactions(chainId).some( (tx) => tx.id !== id && tx.txParams.from === txParams.from && @@ -478,8 +553,8 @@ export class PendingTransactionTracker { ); } - #getPendingTransactions(): TransactionMeta[] { - return this.#getCurrentChainTransactions().filter( + #getPendingTransactions(chainId: Hex): TransactionMeta[] { + return this.#getCurrentChainTransactions(chainId).filter( (tx) => tx.status === TransactionStatus.submitted && !tx.verifiedOnBlockchain && @@ -514,30 +589,33 @@ export class PendingTransactionTracker { } async #getTransactionReceipt( + ethQuery: EthQuery, txHash?: string, ): Promise { - return await query(this.#getEthQuery(), 'getTransactionReceipt', [txHash]); + return await query(ethQuery, 'getTransactionReceipt', [txHash]); } async #getBlockByHash( blockHash: string, includeTransactionDetails: boolean, + ethQuery: EthQuery, // TODO: Replace `any` with type // eslint-disable-next-line @typescript-eslint/no-explicit-any ): Promise { - return await query(this.#getEthQuery(), 'getBlockByHash', [ + return await query(ethQuery, 'getBlockByHash', [ blockHash, includeTransactionDetails, ]); } - async #getNetworkTransactionCount(address: string): Promise { - return await query(this.#getEthQuery(), 'getTransactionCount', [address]); + async #getNetworkTransactionCount( + address: string, + ethQuery: EthQuery, + ): Promise { + return await query(ethQuery, 'getTransactionCount', [address]); } - #getCurrentChainTransactions(): TransactionMeta[] { - const currentChainId = this.#getChainId(); - + #getCurrentChainTransactions(currentChainId: Hex): TransactionMeta[] { return this.#getTransactions().filter( (tx) => tx.chainId === currentChainId, ); diff --git a/packages/transaction-controller/src/utils/nonce.ts b/packages/transaction-controller/src/utils/nonce.ts index 545f3a8156..d613fcf430 100644 --- a/packages/transaction-controller/src/utils/nonce.ts +++ b/packages/transaction-controller/src/utils/nonce.ts @@ -1,4 +1,5 @@ import { toHex } from '@metamask/controller-utils'; +import { providerErrors } from '@metamask/rpc-errors'; import type { NonceLock, Transaction as NonceTrackerTransaction, @@ -18,7 +19,7 @@ const log = createModuleLogger(projectLogger, 'nonce'); */ export async function getNextNonce( txMeta: TransactionMeta, - getNonceLock: (address: string) => Promise, + getNonceLock: (address: string) => Promise, ): Promise<[string, (() => void) | undefined]> { const { customNonceValue, @@ -38,6 +39,11 @@ export async function getNextNonce( } const nonceLock = await getNonceLock(from); + + if (!nonceLock) { + throw providerErrors.chainDisconnected(); + } + const nonce = toHex(nonceLock.nextNonce); const releaseLock = nonceLock.releaseLock.bind(nonceLock); From 8df4f82cd3d5f4b47b3403dfa7082f6501439e7b Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Thu, 29 Feb 2024 23:11:14 +0000 Subject: [PATCH 02/12] Generate selected network client using proxy objects and provider config Cache global nonce tracker after first creation. --- .../src/TransactionController.ts | 24 +++---- .../src/helpers/MultichainTrackingHelper.ts | 68 +++++++++++++------ 2 files changed, 57 insertions(+), 35 deletions(-) diff --git a/packages/transaction-controller/src/TransactionController.ts b/packages/transaction-controller/src/TransactionController.ts index c80e620316..36dcb7e73e 100644 --- a/packages/transaction-controller/src/TransactionController.ts +++ b/packages/transaction-controller/src/TransactionController.ts @@ -33,10 +33,7 @@ import type { } from '@metamask/network-controller'; import { NetworkClientType } from '@metamask/network-controller'; import type { AutoManagedNetworkClient } from '@metamask/network-controller/src/create-auto-managed-network-client'; -import type { - CustomNetworkClientConfiguration, - NetworkClientConfiguration, -} from '@metamask/network-controller/src/types'; +import type { NetworkClientConfiguration } from '@metamask/network-controller/src/types'; import { errorCodes, rpcErrors, providerErrors } from '@metamask/rpc-errors'; import type { Hex } from '@metamask/utils'; import { add0x } from '@metamask/utils'; @@ -250,6 +247,9 @@ export type TransactionControllerOptions = { chainId?: string, ) => NonceTrackerTransaction[]; getGasFeeEstimates?: () => Promise; + getGlobalProviderAndBlockTracker: () => + | { provider: Provider; blockTracker: BlockTracker } + | undefined; getNetworkState: () => NetworkState; getPermittedAccounts: (origin?: string) => Promise; getSavedGasFees?: (chainId: Hex) => SavedGasFees | undefined; @@ -334,18 +334,12 @@ export class TransactionController extends BaseControllerV1< private readonly inProcessOfSigning: Set = new Set(); - // TODO: Replace `any` with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private readonly registry: any; - private readonly mutex = new Mutex(); private readonly gasFeeFlows: GasFeeFlow[]; private readonly getSavedGasFees: (chainId: Hex) => SavedGasFees | undefined; - private readonly getNetworkState: () => NetworkState; - private readonly getCurrentAccountEIP1559Compatibility: () => Promise; private readonly getCurrentNetworkEIP1559Compatibility: ( @@ -473,6 +467,7 @@ export class TransactionController extends BaseControllerV1< getCurrentNetworkEIP1559Compatibility, getExternalPendingTransactions, getGasFeeEstimates, + getGlobalProviderAndBlockTracker, getNetworkState, getPermittedAccounts, getSavedGasFees, @@ -502,7 +497,6 @@ export class TransactionController extends BaseControllerV1< }; this.initialize(); this.messagingSystem = messenger; - this.getNetworkState = getNetworkState; this.isSendFlowHistoryDisabled = disableSendFlowHistory ?? false; this.isHistoryDisabled = disableHistory ?? false; this.isSwapsDisabled = disableSwaps ?? false; @@ -542,6 +536,8 @@ export class TransactionController extends BaseControllerV1< chainId, ); }, + getGlobalProviderAndBlockTracker, + getGlobalProviderConfig: () => getNetworkState()?.providerConfig, getNetworkClientById: ((networkClientId: NetworkClientId) => { return this.messagingSystem.call( `NetworkController:getNetworkClientById`, @@ -549,8 +545,6 @@ export class TransactionController extends BaseControllerV1< ); }) as NetworkController['getNetworkClientById'], getNetworkClientRegistry, - getSelectedNetworkClientId: () => - getNetworkState()?.selectedNetworkClientId, removeIncomingTransactionHelperListeners: this.#removeIncomingTransactionHelperListeners.bind(this), removePendingTransactionTrackerListeners: @@ -2783,8 +2777,6 @@ export class TransactionController extends BaseControllerV1< this.inProcessOfSigning.add(transactionMeta.id); - log('Signing Test', unsignedEthTx, txParams); - const signedTx = await new Promise((resolve, reject) => { this.sign?.( unsignedEthTx, @@ -3156,7 +3148,7 @@ export class TransactionController extends BaseControllerV1< } #getSelectedNetworkClientConfiguration(): - | CustomNetworkClientConfiguration + | NetworkClientConfiguration | undefined { return this.#multichainTrackingHelper.getSelectedNetworkClient() ?.configuration; diff --git a/packages/transaction-controller/src/helpers/MultichainTrackingHelper.ts b/packages/transaction-controller/src/helpers/MultichainTrackingHelper.ts index a67f95b684..691a4bf048 100644 --- a/packages/transaction-controller/src/helpers/MultichainTrackingHelper.ts +++ b/packages/transaction-controller/src/helpers/MultichainTrackingHelper.ts @@ -1,3 +1,4 @@ +import { NetworkType } from '@metamask/controller-utils'; import EthQuery from '@metamask/eth-query'; import type { NetworkClientId, @@ -6,12 +7,16 @@ import type { BlockTracker, Provider, NetworkControllerStateChangeEvent, + ProviderConfig, +} from '@metamask/network-controller'; +import { + NetworkClientType, + type NetworkClientConfiguration, } from '@metamask/network-controller'; -import type { AutoManagedNetworkClient } from '@metamask/network-controller/src/create-auto-managed-network-client'; import type { - CustomNetworkClientConfiguration, - NetworkClientConfiguration, -} from '@metamask/network-controller/src/types'; + AutoManagedNetworkClient, + ProxyWithAccessibleTarget, +} from '@metamask/network-controller/src/create-auto-managed-network-client'; import type { Hex } from '@metamask/utils'; import { Mutex } from 'async-mutex'; import type { NonceLock, NonceTracker } from 'nonce-tracker'; @@ -38,9 +43,12 @@ export type MultichainTrackingHelperOptions = { incomingTransactionOptions: IncomingTransactionOptions; findNetworkClientIdByChainId: NetworkController['findNetworkClientIdByChainId']; + getGlobalProviderAndBlockTracker: () => + | { provider: Provider; blockTracker: BlockTracker } + | undefined; + getGlobalProviderConfig: () => ProviderConfig | undefined; getNetworkClientById: NetworkController['getNetworkClientById']; getNetworkClientRegistry: NetworkController['getNetworkClientRegistry']; - getSelectedNetworkClientId: () => string | undefined; removeIncomingTransactionHelperListeners: ( IncomingTransactionHelper: IncomingTransactionHelper, @@ -74,16 +82,22 @@ export type MultichainTrackingHelperOptions = { export class MultichainTrackingHelper { #isMultichainEnabled: boolean; + #globalNonceTracker: NonceTracker | undefined; + readonly #incomingTransactionOptions: IncomingTransactionOptions; readonly #findNetworkClientIdByChainId: NetworkController['findNetworkClientIdByChainId']; + readonly #getGlobalProviderConfig: () => ProviderConfig | undefined; + + readonly #getGlobalProviderAndBlockTracker: () => + | { provider: Provider; blockTracker: BlockTracker } + | undefined; + readonly #getNetworkClientById: NetworkController['getNetworkClientById']; readonly #getNetworkClientRegistry: NetworkController['getNetworkClientRegistry']; - readonly #getSelectedNetworkClientId: () => string | undefined; - readonly #removeIncomingTransactionHelperListeners: ( IncomingTransactionHelper: IncomingTransactionHelper, ) => void; @@ -131,9 +145,10 @@ export class MultichainTrackingHelper { isMultichainEnabled, incomingTransactionOptions, findNetworkClientIdByChainId, + getGlobalProviderConfig, + getGlobalProviderAndBlockTracker, getNetworkClientById, getNetworkClientRegistry, - getSelectedNetworkClientId, removeIncomingTransactionHelperListeners, removePendingTransactionTrackerListeners, createNonceTracker, @@ -145,9 +160,10 @@ export class MultichainTrackingHelper { this.#incomingTransactionOptions = incomingTransactionOptions; this.#findNetworkClientIdByChainId = findNetworkClientIdByChainId; + this.#getGlobalProviderAndBlockTracker = getGlobalProviderAndBlockTracker; + this.#getGlobalProviderConfig = getGlobalProviderConfig; this.#getNetworkClientById = getNetworkClientById; this.#getNetworkClientRegistry = getNetworkClientRegistry; - this.#getSelectedNetworkClientId = getSelectedNetworkClientId; this.#removeIncomingTransactionHelperListeners = removeIncomingTransactionHelperListeners; @@ -266,7 +282,8 @@ export class MultichainTrackingHelper { let nonceTracker: NonceTracker | undefined; if (!networkClientId || !this.#isMultichainEnabled) { - nonceTracker = this.#getSelectedNonceTracker(); + this.#globalNonceTracker ??= this.#getSelectedNonceTracker(); + nonceTracker = this.#globalNonceTracker; } if (networkClientId && this.#isMultichainEnabled) { @@ -355,22 +372,35 @@ export class MultichainTrackingHelper { } getSelectedNetworkClient(): - | AutoManagedNetworkClient + | AutoManagedNetworkClient | undefined { - const selectedNetworkClientId = this.#getSelectedNetworkClientId(); + const providerBlockTracker = this.#getGlobalProviderAndBlockTracker(); + const providerConfig = this.#getGlobalProviderConfig(); - if (!selectedNetworkClientId) { - log('Cannot get selected client as no selected network client ID'); + if (!providerBlockTracker || !providerConfig) { + log('Cannot get global network client as provider is unavailable'); return undefined; } - const selectedNetworkClient = this.#getNetworkClientById( - selectedNetworkClientId, + log( + 'Retrieved global network client', + providerConfig.chainId, + providerConfig.type, ); - log('Retrieved selected network client', selectedNetworkClientId); - - return selectedNetworkClient; + const { blockTracker, provider } = providerBlockTracker; + + return { + provider: provider as ProxyWithAccessibleTarget, + blockTracker: blockTracker as ProxyWithAccessibleTarget, + configuration: { + chainId: providerConfig.chainId, + type: + providerConfig.type === NetworkType.rpc + ? NetworkClientType.Custom + : NetworkClientType.Infura, + }, + } as AutoManagedNetworkClient; } #refreshTrackingMap = (networkClients: NetworkClientRegistry) => { From 6d6cfb49d569d913863a7a66364e1ad70f3e5d4e Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Wed, 20 Mar 2024 10:41:41 +0000 Subject: [PATCH 03/12] Fix unit tests --- .../src/TransactionController.test.ts | 184 +++--------------- .../src/TransactionController.ts | 106 +++++----- .../TransactionControllerIntegration.test.ts | 126 +----------- .../helpers/IncomingTransactionHelper.test.ts | 12 +- .../src/helpers/IncomingTransactionHelper.ts | 66 +++---- .../helpers/MultichainTrackingHelper.test.ts | 152 ++++++--------- .../src/helpers/MultichainTrackingHelper.ts | 102 +++------- .../helpers/PendingTransactionTracker.test.ts | 2 +- .../src/helpers/PendingTransactionTracker.ts | 129 ++++++------ 9 files changed, 280 insertions(+), 599 deletions(-) diff --git a/packages/transaction-controller/src/TransactionController.test.ts b/packages/transaction-controller/src/TransactionController.test.ts index c9171a4734..d26a43b136 100644 --- a/packages/transaction-controller/src/TransactionController.test.ts +++ b/packages/transaction-controller/src/TransactionController.test.ts @@ -26,7 +26,6 @@ import { NetworkClientType, NetworkStatus } from '@metamask/network-controller'; import { errorCodes, providerErrors, rpcErrors } from '@metamask/rpc-errors'; import { createDeferredPromise } from '@metamask/utils'; import assert from 'assert'; -import * as NonceTrackerPackage from 'nonce-tracker'; import * as uuidModule from 'uuid'; import { FakeBlockTracker } from '../../../tests/fake-block-tracker'; @@ -273,9 +272,6 @@ function waitForTransactionFinished( const MOCK_PREFERENCES = { state: { selectedAddress: 'foo' } }; const INFURA_PROJECT_ID = '341eacb578dd44a1a049cbc5f6fd4035'; -const GOERLI_PROVIDER = new HttpProvider( - `https://goerli.infura.io/v3/${INFURA_PROJECT_ID}`, -); const MAINNET_PROVIDER = new HttpProvider( `https://mainnet.infura.io/v3/${INFURA_PROJECT_ID}`, ); @@ -310,24 +306,6 @@ const MOCK_NETWORK: MockNetwork = { }, subscribe: () => undefined, }; -const MOCK_NETWORK_WITHOUT_CHAIN_ID: MockNetwork = { - provider: GOERLI_PROVIDER, - blockTracker: buildMockBlockTracker('0x102833C'), - state: { - selectedNetworkClientId: NetworkType.goerli, - networksMetadata: { - [NetworkType.goerli]: { - EIPS: { 1559: false }, - status: NetworkStatus.Available, - }, - }, - providerConfig: { - type: NetworkType.goerli, - } as NetworkState['providerConfig'], - networkConfigurations: {}, - }, - subscribe: () => undefined, -}; const MOCK_MAINNET_NETWORK: MockNetwork = { provider: MAINNET_PROVIDER, blockTracker: buildMockBlockTracker('0x102833C'), @@ -536,11 +514,14 @@ describe('TransactionController', () => { ); const { messenger: givenRestrictedMessenger, ...otherOptions } = { - blockTracker: network.blockTracker, disableHistory: false, disableSendFlowHistory: false, disableSwaps: false, getCurrentNetworkEIP1559Compatibility: async () => false, + getGlobalProviderAndBlockTracker: () => ({ + provider: network.provider, + blockTracker: network.blockTracker, + }), getNetworkState: () => network.state, // TODO: Replace with a real type // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -550,7 +531,6 @@ describe('TransactionController', () => { isMultichainEnabled: false, hooks: {}, onNetworkStateChange: network.subscribe, - provider: network.provider, sign: async (transaction: TypedTransaction) => transaction, transactionHistoryLimit: 40, ...givenOptions, @@ -744,18 +724,22 @@ describe('TransactionController', () => { return pendingTransactionTrackerMock; }); - multichainTrackingHelperClassMock.mockImplementation(({ provider }) => { - multichainTrackingHelperMock = { - getEthQuery: jest.fn().mockImplementation(() => { - return new EthQuery(provider); - }), - checkForPendingTransactionAndStartPolling: jest.fn(), - getNonceLock: getNonceLockSpy, - initialize: jest.fn(), - has: jest.fn().mockReturnValue(false), - } as unknown as jest.Mocked; - return multichainTrackingHelperMock; - }); + multichainTrackingHelperClassMock.mockImplementation( + ({ getGlobalProviderAndBlockTracker }) => { + multichainTrackingHelperMock = { + getEthQuery: jest.fn().mockImplementation(() => { + return new EthQuery( + getGlobalProviderAndBlockTracker()?.provider as Provider, + ); + }), + checkForPendingTransactionAndStartPolling: jest.fn(), + getNonceLock: getNonceLockSpy, + initialize: jest.fn(), + has: jest.fn().mockReturnValue(false), + } as unknown as jest.Mocked; + return multichainTrackingHelperMock; + }, + ); defaultGasFeeFlowClassMock.mockImplementation(() => { defaultGasFeeFlowMock = { @@ -809,117 +793,6 @@ describe('TransactionController', () => { }), ); }); - - describe('nonce tracker', () => { - it('uses external pending transactions', async () => { - const nonceTrackerMock = jest - .spyOn(NonceTrackerPackage, 'NonceTracker') - .mockImplementation(); - - const externalPendingTransactions = [ - { - from: '0x1', - }, - { from: '0x2' }, - ]; - - const getExternalPendingTransactions = jest - .fn() - .mockReturnValueOnce(externalPendingTransactions); - - setupController({ - options: { - getExternalPendingTransactions, - state: { - transactions: [ - { - ...TRANSACTION_META_MOCK, - chainId: MOCK_NETWORK.state.providerConfig.chainId, - status: TransactionStatus.submitted, - }, - ], - }, - }, - }); - - const pendingTransactions = - nonceTrackerMock.mock.calls[0][0].getPendingTransactions( - ACCOUNT_MOCK, - ); - - expect(nonceTrackerMock).toHaveBeenCalledTimes(1); - expect(pendingTransactions).toStrictEqual([ - expect.any(Object), - ...externalPendingTransactions, - ]); - expect(getExternalPendingTransactions).toHaveBeenCalledTimes(1); - expect(getExternalPendingTransactions).toHaveBeenCalledWith( - ACCOUNT_MOCK, - // This is undefined for the base nonceTracker - undefined, - ); - }); - }); - - describe('onBootCleanup', () => { - afterEach(() => { - updateGasMock.mockReset(); - updateGasFeesMock.mockReset(); - }); - - it('submits approved transactions for all chains', async () => { - const mockTransactionMeta = { - from: ACCOUNT_MOCK, - status: TransactionStatus.approved, - txParams: { - from: ACCOUNT_MOCK, - to: ACCOUNT_2_MOCK, - }, - }; - const mockedTransactions = [ - { - id: '123', - history: [{ ...mockTransactionMeta, id: '123' }], - chainId: toHex(5), - ...mockTransactionMeta, - }, - { - id: '456', - history: [{ ...mockTransactionMeta, id: '456' }], - chainId: toHex(1), - ...mockTransactionMeta, - }, - { - id: '789', - history: [{ ...mockTransactionMeta, id: '789' }], - chainId: toHex(16), - ...mockTransactionMeta, - }, - ]; - - const mockedControllerState = { - transactions: mockedTransactions, - methodData: {}, - lastFetchedBlockNumbers: {}, - }; - - const { controller } = setupController({ - options: { - // TODO: Replace `any` with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - state: mockedControllerState as any, - }, - }); - - await flushPromises(); - - const { transactions } = controller.state; - - expect(transactions[0].status).toBe(TransactionStatus.submitted); - expect(transactions[1].status).toBe(TransactionStatus.submitted); - expect(transactions[2].status).toBe(TransactionStatus.submitted); - }); - }); }); describe('estimateGas', () => { @@ -1368,6 +1241,8 @@ describe('TransactionController', () => { to: ACCOUNT_MOCK, }); + await flushPromises(); + const expectedInitialSnapshot = { actionId: undefined, chainId: expect.any(String), @@ -1696,6 +1571,8 @@ describe('TransactionController', () => { to: ACCOUNT_MOCK, }); + await flushPromises(); + expect(getSimulationDataMock).toHaveBeenCalledTimes(1); expect(getSimulationDataMock).toHaveBeenCalledWith({ chainId: MOCK_NETWORK.state.providerConfig.chainId, @@ -1918,19 +1795,6 @@ describe('TransactionController', () => { await expectTransactionToFail(controller, 'No sign method defined'); }); - it('if no chainId defined', async () => { - const { controller } = setupController({ - network: MOCK_NETWORK_WITHOUT_CHAIN_ID, - messengerOptions: { - addTransactionApprovalRequest: { - state: 'approved', - }, - }, - }); - - await expectTransactionToFail(controller, 'No chainId defined'); - }); - it('if unexpected status', async () => { const { controller, messenger } = setupController(); diff --git a/packages/transaction-controller/src/TransactionController.ts b/packages/transaction-controller/src/TransactionController.ts index b0516f3cf0..1204a95dbc 100644 --- a/packages/transaction-controller/src/TransactionController.ts +++ b/packages/transaction-controller/src/TransactionController.ts @@ -18,8 +18,9 @@ import { ApprovalType, ORIGIN_METAMASK, convertHexToDecimal, + NetworkType, } from '@metamask/controller-utils'; -import type EthQuery from '@metamask/eth-query'; +import EthQuery from '@metamask/eth-query'; import type { GasFeeState } from '@metamask/gas-fee-controller'; import type { BlockTracker, @@ -32,8 +33,6 @@ import type { NetworkControllerGetNetworkClientByIdAction, } from '@metamask/network-controller'; import { NetworkClientType } from '@metamask/network-controller'; -import type { AutoManagedNetworkClient } from '@metamask/network-controller/src/create-auto-managed-network-client'; -import type { NetworkClientConfiguration } from '@metamask/network-controller/src/types'; import { errorCodes, rpcErrors, providerErrors } from '@metamask/rpc-errors'; import type { Hex } from '@metamask/utils'; import { add0x } from '@metamask/utils'; @@ -572,6 +571,8 @@ export class TransactionController extends BaseController< private readonly getSavedGasFees: (chainId: Hex) => SavedGasFees | undefined; + private readonly getNetworkState: () => NetworkState; + private readonly getCurrentAccountEIP1559Compatibility: () => Promise; private readonly getCurrentNetworkEIP1559Compatibility: ( @@ -607,6 +608,13 @@ export class TransactionController extends BaseController< #isSimulationEnabled: () => boolean; + #getGlobalProviderAndBlockTracker: () => + | { + provider: Provider; + blockTracker: BlockTracker; + } + | undefined; + private readonly afterSign: ( transactionMeta: TransactionMeta, signedTx: TypedTransaction, @@ -661,17 +669,14 @@ export class TransactionController extends BaseController< } private async registryLookup(fourBytePrefix: string): Promise { - const selectedNetworkClient = - this.#multichainTrackingHelper.getSelectedNetworkClient(); + const globalProvider = this.#getGlobalProviderAndBlockTracker()?.provider; - if (!selectedNetworkClient) { + if (!globalProvider) { throw providerErrors.disconnected(); } - const { provider } = selectedNetworkClient; - // @ts-expect-error the type in eth-method-registry is inappropriate and should be changed - const registry = new MethodRegistry({ provider }); + const registry = new MethodRegistry({ provider: globalProvider }); const registryMethod = await registry.lookup(fourBytePrefix); if (!registryMethod) { @@ -761,6 +766,7 @@ export class TransactionController extends BaseController< }); this.messagingSystem = messenger; + this.getNetworkState = getNetworkState; this.isSendFlowHistoryDisabled = disableSendFlowHistory ?? false; this.isHistoryDisabled = disableHistory ?? false; this.isSwapsDisabled = disableSwaps ?? false; @@ -779,7 +785,8 @@ export class TransactionController extends BaseController< this.#incomingTransactionOptions = incomingTransactions; this.#pendingTransactionOptions = pendingTransactions; this.#transactionHistoryLimit = transactionHistoryLimit; - this.#isSimulationEnabled = isSimulationEnabled ?? (() => false); + this.#isSimulationEnabled = isSimulationEnabled ?? (() => true); + this.#getGlobalProviderAndBlockTracker = getGlobalProviderAndBlockTracker; this.sign = sign; this.afterSign = hooks?.afterSign ?? (() => true); @@ -835,16 +842,21 @@ export class TransactionController extends BaseController< includeTokenTransfers: incomingTransactions.includeTokenTransfers, }); - const getSelectedNetworkClient = () => - this.#multichainTrackingHelper.getSelectedNetworkClient(); - this.incomingTransactionHelper = this.#createIncomingTransactionHelper({ - getNetworkClient: getSelectedNetworkClient, + getBlockTracker: () => getGlobalProviderAndBlockTracker()?.blockTracker, + getChainId: () => this.getChainId(), etherscanRemoteTransactionSource, }); + const getGlobalEthQuery = () => { + const globalProvider = getGlobalProviderAndBlockTracker()?.provider; + return globalProvider ? new EthQuery(globalProvider) : undefined; + }; + this.pendingTransactionTracker = this.#createPendingTransactionTracker({ - getNetworkClient: getSelectedNetworkClient, + getBlockTracker: () => getGlobalProviderAndBlockTracker()?.blockTracker, + getChainId: () => this.getChainId(), + getEthQuery: getGlobalEthQuery, }); this.gasFeeFlows = this.#getGasFeeFlows(); @@ -2374,14 +2386,12 @@ export class TransactionController extends BaseController< const { networkClientId, chainId } = transactionMeta; - const networkType = networkClientId + const isCustomNetwork = networkClientId ? this.messagingSystem.call( `NetworkController:getNetworkClientById`, networkClientId, ).configuration.type === NetworkClientType.Custom - : this.#getSelectedNetworkClientConfiguration()?.type; - - const isCustomNetwork = networkType === NetworkClientType.Custom; + : this.getNetworkState().providerConfig.type === NetworkType.rpc; await updateGas({ ethQuery, @@ -2862,7 +2872,7 @@ export class TransactionController extends BaseController< ).configuration.chainId; } - return this.#getSelectedNetworkClientConfiguration()?.chainId; + return this.getNetworkState()?.providerConfig.chainId; } private prepareUnsignedEthTx( @@ -3226,10 +3236,12 @@ export class TransactionController extends BaseController< private getNonceTrackerTransactions( status: TransactionStatus, address: string, - chainId: string, + chainId?: Hex, ) { + const finalChainId = chainId ?? (this.getChainId() as string); + return getAndFormatTransactionsForNonceTracker( - chainId, + finalChainId, address, status, this.state.transactions, @@ -3291,7 +3303,7 @@ export class TransactionController extends BaseController< }: { provider: Provider; blockTracker: BlockTracker; - chainId: Hex; + chainId?: Hex; }): NonceTracker { return new NonceTracker({ // TODO: Replace `any` with type @@ -3302,27 +3314,29 @@ export class TransactionController extends BaseController< this, chainId, ), - getConfirmedTransactions: this.getNonceTrackerTransactions.bind( - this, - TransactionStatus.confirmed, - chainId, - ), + getConfirmedTransactions: (address) => + this.getNonceTrackerTransactions( + TransactionStatus.confirmed, + address, + chainId, + ), }); } #createIncomingTransactionHelper({ - getNetworkClient, + getBlockTracker, + getChainId, etherscanRemoteTransactionSource, }: { - getNetworkClient: () => - | AutoManagedNetworkClient - | undefined; + getBlockTracker: () => BlockTracker | undefined; + getChainId: () => Hex | undefined; etherscanRemoteTransactionSource: EtherscanRemoteTransactionSource; }): IncomingTransactionHelper { const incomingTransactionHelper = new IncomingTransactionHelper({ + getBlockTracker, + getChainId, getCurrentAccount: this.getSelectedAddress, getLastFetchedBlockNumbers: () => this.state.lastFetchedBlockNumbers, - getNetworkClient, isEnabled: this.#incomingTransactionOptions.isEnabled, queryEntireHistory: this.#incomingTransactionOptions.queryEntireHistory, remoteTransactionSource: etherscanRemoteTransactionSource, @@ -3336,17 +3350,21 @@ export class TransactionController extends BaseController< } #createPendingTransactionTracker({ - getNetworkClient, + getBlockTracker, + getChainId, + getEthQuery, }: { - getNetworkClient: () => - | AutoManagedNetworkClient - | undefined; + getBlockTracker: () => BlockTracker | undefined; + getChainId: () => Hex | undefined; + getEthQuery: () => EthQuery | undefined; }): PendingTransactionTracker { const pendingTransactionTracker = new PendingTransactionTracker({ approveTransaction: async (transactionId: string) => { await this.approveTransaction(transactionId); }, - getNetworkClient, + getBlockTracker, + getChainId, + getEthQuery, getTransactions: () => this.state.transactions, isResubmitEnabled: this.#pendingTransactionOptions.isResubmitEnabled, getGlobalLock: (chainId: Hex) => @@ -3440,7 +3458,10 @@ export class TransactionController extends BaseController< ); } - #getNonceTrackerPendingTransactions(chainId: string, address: string) { + #getNonceTrackerPendingTransactions( + chainId: Hex | undefined, + address: string, + ) { const standardPendingTransactions = this.getNonceTrackerTransactions( TransactionStatus.submitted, address, @@ -3573,11 +3594,4 @@ export class TransactionController extends BaseController< return chainId; } - - #getSelectedNetworkClientConfiguration(): - | NetworkClientConfiguration - | undefined { - return this.#multichainTrackingHelper.getSelectedNetworkClient() - ?.configuration; - } } diff --git a/packages/transaction-controller/src/TransactionControllerIntegration.test.ts b/packages/transaction-controller/src/TransactionControllerIntegration.test.ts index 1c0ba2dc46..d19ca7fe13 100644 --- a/packages/transaction-controller/src/TransactionControllerIntegration.test.ts +++ b/packages/transaction-controller/src/TransactionControllerIntegration.test.ts @@ -48,6 +48,7 @@ import { import type { TransactionControllerActions, TransactionControllerEvents, + TransactionControllerOptions, } from './TransactionController'; import { TransactionController } from './TransactionController'; import type { TransactionMeta } from './types'; @@ -150,8 +151,7 @@ const setupController = async ( allowedEvents: ['NetworkController:stateChange'], }); - const options = { - blockTracker, + const options: TransactionControllerOptions = { disableHistory: false, disableSendFlowHistory: false, disableSwaps: false, @@ -163,6 +163,10 @@ const setupController = async ( false ); }, + getGlobalProviderAndBlockTracker: () => ({ + provider, + blockTracker, + }), getNetworkState: () => networkController.state, getNetworkClientRegistry: networkController.getNetworkClientRegistry.bind(networkController), @@ -174,7 +178,6 @@ const setupController = async ( onNetworkStateChange: () => { // noop }, - provider, sign: async (transaction: TypedTransaction) => transaction, transactionHistoryLimit: 40, ...givenOptions, @@ -206,123 +209,6 @@ describe('TransactionController Integration', () => { expect(transactionController).toBeDefined(); transactionController.destroy(); }); - - it('should submit all approved transactions in state', async () => { - mockNetwork({ - networkClientConfiguration: buildInfuraNetworkClientConfiguration( - InfuraNetworkType.goerli, - ), - mocks: [ - buildEthBlockNumberRequestMock('0x1'), - buildEthSendRawTransactionRequestMock( - '0x02e2050101018252089408f137f335ea1b8f193b8f6ea92561a60d23a2118080c0808080', - '0x1', - ), - ], - }); - - mockNetwork({ - networkClientConfiguration: buildInfuraNetworkClientConfiguration( - InfuraNetworkType.sepolia, - ), - mocks: [ - buildEthBlockNumberRequestMock('0x1'), - buildEthSendRawTransactionRequestMock( - '0x02e583aa36a70101018252089408f137f335ea1b8f193b8f6ea92561a60d23a2118080c0808080', - '0x1', - ), - ], - }); - - const { transactionController } = await setupController({ - isMultichainEnabled: true, - state: { - transactions: [ - { - actionId: undefined, - chainId: '0x5', - dappSuggestedGasFees: undefined, - deviceConfirmedOn: undefined, - id: 'ecfe8c60-ba27-11ee-8643-dfd28279a442', - origin: undefined, - securityAlertResponse: undefined, - status: TransactionStatus.approved, - time: 1706039113766, - txParams: { - from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', - gas: '0x5208', - nonce: '0x1', - to: '0x08f137f335ea1b8f193b8f6ea92561a60d23a211', - value: '0x0', - maxFeePerGas: '0x1', - maxPriorityFeePerGas: '0x1', - }, - userEditedGasLimit: false, - verifiedOnBlockchain: false, - type: TransactionType.simpleSend, - networkClientId: 'goerli', - simulationFails: undefined, - originalGasEstimate: '0x5208', - defaultGasEstimates: { - gas: '0x5208', - maxFeePerGas: '0x1', - maxPriorityFeePerGas: '0x1', - gasPrice: undefined, - estimateType: 'dappSuggested', - }, - userFeeLevel: 'dappSuggested', - sendFlowHistory: [], - }, - { - actionId: undefined, - chainId: '0xaa36a7', - dappSuggestedGasFees: undefined, - deviceConfirmedOn: undefined, - id: 'c4cc0ff0-ba28-11ee-926f-55a7f9c2c2c6', - origin: undefined, - securityAlertResponse: undefined, - status: TransactionStatus.approved, - time: 1706039113766, - txParams: { - from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', - gas: '0x5208', - nonce: '0x1', - to: '0x08f137f335ea1b8f193b8f6ea92561a60d23a211', - value: '0x0', - maxFeePerGas: '0x1', - maxPriorityFeePerGas: '0x1', - }, - userEditedGasLimit: false, - verifiedOnBlockchain: false, - type: TransactionType.simpleSend, - networkClientId: 'sepolia', - simulationFails: undefined, - originalGasEstimate: '0x5208', - defaultGasEstimates: { - gas: '0x5208', - maxFeePerGas: '0x1', - maxPriorityFeePerGas: '0x1', - gasPrice: undefined, - estimateType: 'dappSuggested', - }, - userFeeLevel: 'dappSuggested', - sendFlowHistory: [], - }, - ], - }, - }); - await advanceTime({ clock, duration: 1 }); - - expect(transactionController.state.transactions).toMatchObject([ - expect.objectContaining({ - status: 'submitted', - }), - expect.objectContaining({ - status: 'submitted', - }), - ]); - transactionController.destroy(); - }); }); describe('multichain transaction lifecycle', () => { diff --git a/packages/transaction-controller/src/helpers/IncomingTransactionHelper.test.ts b/packages/transaction-controller/src/helpers/IncomingTransactionHelper.test.ts index 49b39c4eff..52d18ca8b1 100644 --- a/packages/transaction-controller/src/helpers/IncomingTransactionHelper.test.ts +++ b/packages/transaction-controller/src/helpers/IncomingTransactionHelper.test.ts @@ -31,7 +31,7 @@ const BLOCK_TRACKER_MOCK = { } as unknown as jest.Mocked; const CONTROLLER_ARGS_MOCK = { - blockTracker: BLOCK_TRACKER_MOCK, + getBlockTracker: () => BLOCK_TRACKER_MOCK, getCurrentAccount: () => ADDRESS_MOCK, getLastFetchedBlockNumbers: () => ({}), getChainId: () => CHAIN_ID_MOCK, @@ -589,7 +589,7 @@ describe('IncomingTransactionHelper', () => { helper.start(); expect( - CONTROLLER_ARGS_MOCK.blockTracker.addListener, + CONTROLLER_ARGS_MOCK.getBlockTracker().addListener, ).toHaveBeenCalledTimes(1); }); @@ -603,7 +603,7 @@ describe('IncomingTransactionHelper', () => { helper.start(); expect( - CONTROLLER_ARGS_MOCK.blockTracker.addListener, + CONTROLLER_ARGS_MOCK.getBlockTracker().addListener, ).toHaveBeenCalledTimes(1); }); @@ -617,7 +617,7 @@ describe('IncomingTransactionHelper', () => { helper.start(); expect( - CONTROLLER_ARGS_MOCK.blockTracker.addListener, + CONTROLLER_ARGS_MOCK.getBlockTracker().addListener, ).not.toHaveBeenCalled(); }); @@ -632,7 +632,7 @@ describe('IncomingTransactionHelper', () => { helper.start(); expect( - CONTROLLER_ARGS_MOCK.blockTracker.addListener, + CONTROLLER_ARGS_MOCK.getBlockTracker().addListener, ).not.toHaveBeenCalled(); }); }); @@ -648,7 +648,7 @@ describe('IncomingTransactionHelper', () => { helper.stop(); expect( - CONTROLLER_ARGS_MOCK.blockTracker.removeListener, + CONTROLLER_ARGS_MOCK.getBlockTracker().removeListener, ).toHaveBeenCalledTimes(1); }); }); diff --git a/packages/transaction-controller/src/helpers/IncomingTransactionHelper.ts b/packages/transaction-controller/src/helpers/IncomingTransactionHelper.ts index 9135b19635..43ca79fd36 100644 --- a/packages/transaction-controller/src/helpers/IncomingTransactionHelper.ts +++ b/packages/transaction-controller/src/helpers/IncomingTransactionHelper.ts @@ -1,8 +1,4 @@ -import type { - BlockTracker, - NetworkClientConfiguration, -} from '@metamask/network-controller'; -import type { AutoManagedNetworkClient } from '@metamask/network-controller/src/create-auto-managed-network-client'; +import type { BlockTracker } from '@metamask/network-controller'; import type { Hex } from '@metamask/utils'; import { Mutex } from 'async-mutex'; import EventEmitter from 'events'; @@ -37,7 +33,11 @@ export type IncomingTransactionOptions = { export class IncomingTransactionHelper { hub: EventEmitter; - #blockTracker: BlockTracker | undefined; + #currentBlockTracker?: BlockTracker; + + #getBlockTracker: () => BlockTracker | undefined; + + #getChainId: () => Hex | undefined; #getCurrentAccount: () => string; @@ -45,10 +45,6 @@ export class IncomingTransactionHelper { #getLocalTransactions: () => TransactionMeta[]; - #getNetworkClient: () => - | AutoManagedNetworkClient - | undefined; - #isEnabled: () => boolean; #isRunning: boolean; @@ -66,22 +62,22 @@ export class IncomingTransactionHelper { #updateTransactions: boolean; constructor({ + getBlockTracker, + getChainId, getCurrentAccount, getLastFetchedBlockNumbers, getLocalTransactions, - getNetworkClient, isEnabled, queryEntireHistory, remoteTransactionSource, transactionLimit, updateTransactions, }: { + getBlockTracker: () => BlockTracker | undefined; + getChainId: () => Hex | undefined; getCurrentAccount: () => string; getLastFetchedBlockNumbers: () => Record; getLocalTransactions?: () => TransactionMeta[]; - getNetworkClient: () => - | AutoManagedNetworkClient - | undefined; isEnabled?: () => boolean; queryEntireHistory?: boolean; remoteTransactionSource: RemoteTransactionSource; @@ -90,10 +86,11 @@ export class IncomingTransactionHelper { }) { this.hub = new EventEmitter(); + this.#getBlockTracker = getBlockTracker; + this.#getChainId = getChainId; this.#getCurrentAccount = getCurrentAccount; this.#getLastFetchedBlockNumbers = getLastFetchedBlockNumbers; this.#getLocalTransactions = getLocalTransactions || (() => []); - this.#getNetworkClient = getNetworkClient; this.#isEnabled = isEnabled ?? (() => true); this.#isRunning = false; this.#queryEntireHistory = queryEntireHistory ?? true; @@ -117,25 +114,25 @@ export class IncomingTransactionHelper { return; } - const networkClient = this.#getNetworkClient(); + const { blockTracker, chainId } = this.#getNetworkObjects(); - if (!networkClient) { - log('Cannot start as network client is not available'); + if (!blockTracker || !chainId) { + log('Cannot start as network is not available'); return; } - if (!this.#canStart(networkClient.configuration.chainId)) { + if (!this.#canStart(chainId)) { return; } - this.#blockTracker = networkClient.blockTracker; - this.#blockTracker.addListener('latest', this.#onLatestBlock); + this.#currentBlockTracker = blockTracker; + this.#currentBlockTracker.addListener('latest', this.#onLatestBlock); this.#isRunning = true; } stop() { - this.#blockTracker?.removeListener('latest', this.#onLatestBlock); - this.#blockTracker = undefined; + this.#currentBlockTracker?.removeListener('latest', this.#onLatestBlock); + this.#currentBlockTracker = undefined; this.#isRunning = false; } @@ -145,32 +142,28 @@ export class IncomingTransactionHelper { log('Checking for incoming transactions'); try { - const networkClient = this.#getNetworkClient(); + const { blockTracker, chainId } = this.#getNetworkObjects(); - if (!networkClient || !this.#blockTracker) { + if (!blockTracker || !chainId) { log('Cannot update as network client is not available'); return; } - if (!this.#canStart(networkClient.configuration.chainId)) { + if (!this.#canStart(chainId)) { return; } const latestBlockNumber = parseInt( - latestBlockNumberHex || (await this.#blockTracker.getLatestBlock()), + latestBlockNumberHex || (await blockTracker.getLatestBlock()), 16, ); const additionalLastFetchedKeys = this.#remoteTransactionSource.getLastBlockVariations?.() ?? []; - const fromBlock = this.#getFromBlock( - latestBlockNumber, - networkClient.configuration.chainId, - ); - + const fromBlock = this.#getFromBlock(latestBlockNumber, chainId); const address = this.#getCurrentAccount(); - const currentChainId = networkClient.configuration.chainId; + const currentChainId = chainId; let remoteTransactions = []; @@ -346,4 +339,11 @@ export class IncomingTransactionHelper { return isEnabled && isSupportedNetwork; } + + #getNetworkObjects() { + const blockTracker = this.#getBlockTracker(); + const chainId = this.#getChainId(); + + return { blockTracker, chainId }; + } } diff --git a/packages/transaction-controller/src/helpers/MultichainTrackingHelper.test.ts b/packages/transaction-controller/src/helpers/MultichainTrackingHelper.test.ts index 133258eb6c..d627d5a274 100644 --- a/packages/transaction-controller/src/helpers/MultichainTrackingHelper.test.ts +++ b/packages/transaction-controller/src/helpers/MultichainTrackingHelper.test.ts @@ -8,6 +8,7 @@ import { useFakeTimers } from 'sinon'; import { advanceTime } from '../../../../tests/helpers'; import { EtherscanRemoteTransactionSource } from './EtherscanRemoteTransactionSource'; import type { IncomingTransactionHelper } from './IncomingTransactionHelper'; +import type { MultichainTrackingHelperOptions } from './MultichainTrackingHelper'; import { MultichainTrackingHelper } from './MultichainTrackingHelper'; import type { PendingTransactionTracker } from './PendingTransactionTracker'; @@ -139,11 +140,12 @@ function newMultichainTrackingHelper( const mockNonceLock = { releaseLock: jest.fn() }; const mockNonceTrackers: Record> = {}; + const mockGetNonceLock = jest.fn().mockResolvedValue(mockNonceLock); const mockCreateNonceTracker = jest .fn() .mockImplementation(({ chainId }: { chainId: Hex }) => { const mockNonceTracker = { - getNonceLock: jest.fn().mockResolvedValue(mockNonceLock), + getNonceLock: mockGetNonceLock, } as unknown as jest.Mocked; mockNonceTrackers[chainId] = mockNonceTracker; return mockNonceTracker; @@ -155,13 +157,14 @@ function newMultichainTrackingHelper( > = {}; const mockCreateIncomingTransactionHelper = jest .fn() - .mockImplementation(({ chainId }: { chainId: Hex }) => { + .mockImplementation(({ getChainId }: { getChainId: () => Hex }) => { const mockIncomingTransactionHelper = { start: jest.fn(), stop: jest.fn(), update: jest.fn(), } as unknown as jest.Mocked; - mockIncomingTransactionHelpers[chainId] = mockIncomingTransactionHelper; + mockIncomingTransactionHelpers[getChainId()] = + mockIncomingTransactionHelper; return mockIncomingTransactionHelper; }); @@ -171,18 +174,18 @@ function newMultichainTrackingHelper( > = {}; const mockCreatePendingTransactionTracker = jest .fn() - .mockImplementation(({ chainId }: { chainId: Hex }) => { + .mockImplementation(({ getChainId }: { getChainId: () => Hex }) => { const mockPendingTransactionTracker = { start: jest.fn(), stop: jest.fn(), } as unknown as jest.Mocked; - mockPendingTransactionTrackers[chainId] = mockPendingTransactionTracker; + mockPendingTransactionTrackers[getChainId()] = + mockPendingTransactionTracker; return mockPendingTransactionTracker; }); - const options = { + const options: jest.Mocked = { isMultichainEnabled: true, - provider: MOCK_PROVIDERS.mainnet, nonceTracker: { getNonceLock: jest.fn().mockResolvedValue(mockNonceLock), }, @@ -194,6 +197,10 @@ function newMultichainTrackingHelper( updateTransactions: true, }, findNetworkClientIdByChainId: mockFindNetworkClientIdByChainId, + getGlobalProviderAndBlockTracker: () => ({ + provider: MOCK_PROVIDERS.mainnet, + blockTracker: MOCK_BLOCK_TRACKERS.mainnet, + }), getNetworkClientById: mockGetNetworkClientById, getNetworkClientRegistry: mockGetNetworkClientRegistry, removeIncomingTransactionHelperListeners: jest.fn(), @@ -210,6 +217,7 @@ function newMultichainTrackingHelper( return { helper, options, + mockGetNonceLock, mockNonceLock, mockNonceTrackers, mockIncomingTransactionHelpers, @@ -341,20 +349,31 @@ describe('MultichainTrackingHelper', () => { }); expect(options.createIncomingTransactionHelper).toHaveBeenCalledTimes(1); - expect(options.createIncomingTransactionHelper).toHaveBeenCalledWith({ - blockTracker: MOCK_BLOCK_TRACKERS.mainnet, - etherscanRemoteTransactionSource: expect.any( - EtherscanRemoteTransactionSource, - ), - chainId: '0x1', - }); + expect( + options.createIncomingTransactionHelper.mock.calls[0][0].getBlockTracker(), + ).toBe(MOCK_BLOCK_TRACKERS.mainnet); + expect( + options.createIncomingTransactionHelper.mock.calls[0][0].getChainId(), + ).toBe('0x1'); + expect(options.createIncomingTransactionHelper).toHaveBeenCalledWith( + expect.objectContaining({ + etherscanRemoteTransactionSource: expect.any( + EtherscanRemoteTransactionSource, + ), + }), + ); expect(options.createPendingTransactionTracker).toHaveBeenCalledTimes(1); - expect(options.createPendingTransactionTracker).toHaveBeenCalledWith({ - provider: MOCK_PROVIDERS.mainnet, - blockTracker: MOCK_BLOCK_TRACKERS.mainnet, - chainId: '0x1', - }); + expect( + options.createPendingTransactionTracker.mock.calls[0][0].getBlockTracker(), + ).toBe(MOCK_BLOCK_TRACKERS.mainnet); + expect( + options.createPendingTransactionTracker.mock.calls[0][0].getChainId(), + ).toBe('0x1'); + expect( + options.createPendingTransactionTracker.mock.calls[0][0].getEthQuery() + ?.provider, + ).toBe(MOCK_PROVIDERS.mainnet); expect(helper.has('mainnet')).toBe(true); }); @@ -525,7 +544,7 @@ describe('MultichainTrackingHelper', () => { expect(releaseLockForChainIdKey).not.toHaveBeenCalled(); expect(mockNonceLock.releaseLock).not.toHaveBeenCalled(); - nonceLock.releaseLock(); + nonceLock?.releaseLock(); expect(releaseLockForChainIdKey).toHaveBeenCalled(); expect(mockNonceLock.releaseLock).toHaveBeenCalled(); @@ -585,23 +604,23 @@ describe('MultichainTrackingHelper', () => { }); it('gets the nonce lock from the global NonceTracker', async () => { - const { options, helper } = newMultichainTrackingHelper({}); + const { helper, mockNonceTrackers } = newMultichainTrackingHelper({}); helper.initialize(); await helper.getNonceLock('0xdeadbeef'); - expect(options.nonceTracker.getNonceLock).toHaveBeenCalledWith( + expect(mockNonceTrackers['0x1'].getNonceLock).toHaveBeenCalledWith( '0xdeadbeef', ); }); it('throws an error if unable to acquire nonce lock from the global NonceTracker', async () => { - const { options, helper } = newMultichainTrackingHelper({}); + const { helper, mockGetNonceLock } = newMultichainTrackingHelper({}); helper.initialize(); - options.nonceTracker.getNonceLock.mockRejectedValue( + mockGetNonceLock.mockRejectedValue( 'failed to acquire lock from nonceTracker', ); @@ -634,7 +653,7 @@ describe('MultichainTrackingHelper', () => { }); it('gets the nonce lock from the global NonceTracker', async () => { - const { options, helper } = newMultichainTrackingHelper({ + const { helper, mockNonceTrackers } = newMultichainTrackingHelper({ isMultichainEnabled: false, }); @@ -642,30 +661,26 @@ describe('MultichainTrackingHelper', () => { await helper.getNonceLock('0xdeadbeef', '0xabc'); - expect(options.nonceTracker.getNonceLock).toHaveBeenCalledWith( - '0xdeadbeef', - ); + expect(Object.values(mockNonceTrackers)).toHaveLength(1); + expect( + Object.values(mockNonceTrackers)[0].getNonceLock, + ).toHaveBeenCalledWith('0xdeadbeef'); }); it('throws an error if unable to acquire nonce lock from the global NonceTracker', async () => { - const { options, helper } = newMultichainTrackingHelper({ + const { helper, mockGetNonceLock } = newMultichainTrackingHelper({ isMultichainEnabled: false, }); helper.initialize(); - options.nonceTracker.getNonceLock.mockRejectedValue( + mockGetNonceLock.mockRejectedValue( 'failed to acquire lock from nonceTracker', ); - // for some reason jest expect().rejects.toThrow doesn't work here - let error = ''; - try { - await helper.getNonceLock('0xdeadbeef', '0xabc'); - } catch (err: unknown) { - error = err as string; - } - expect(error).toBe('failed to acquire lock from nonceTracker'); + await expect(helper.getNonceLock('0xdeadbeef', '0xabc')).rejects.toBe( + 'failed to acquire lock from nonceTracker', + ); }); }); }); @@ -733,7 +748,7 @@ describe('MultichainTrackingHelper', () => { networkClientId: 'goerli', chainId: '0xa', }); - expect(ethQuery.provider).toBe(MOCK_PROVIDERS.goerli); + expect(ethQuery?.provider).toBe(MOCK_PROVIDERS.goerli); expect(options.getNetworkClientById).toHaveBeenCalledTimes(1); expect(options.getNetworkClientById).toHaveBeenCalledWith('goerli'); @@ -746,7 +761,7 @@ describe('MultichainTrackingHelper', () => { networkClientId: 'missingNetworkClientId', chainId: '0xa', }); - expect(ethQuery.provider).toBe( + expect(ethQuery?.provider).toBe( MOCK_PROVIDERS['customNetworkClientId-1'], ); @@ -761,24 +776,6 @@ describe('MultichainTrackingHelper', () => { 'customNetworkClientId-1', ); }); - - it('returns EthQuery with the fallback global provider if networkClientId and chainId cannot be satisfied', () => { - const { options, helper } = newMultichainTrackingHelper(); - - const ethQuery = helper.getEthQuery({ - networkClientId: 'missingNetworkClientId', - chainId: '0xdeadbeef', - }); - expect(ethQuery.provider).toBe(MOCK_PROVIDERS.mainnet); - - expect(options.getNetworkClientById).toHaveBeenCalledTimes(1); - expect(options.getNetworkClientById).toHaveBeenCalledWith( - 'missingNetworkClientId', - ); - expect(options.findNetworkClientIdByChainId).toHaveBeenCalledWith( - '0xdeadbeef', - ); - }); }); describe('when given only networkClientId', () => { @@ -786,25 +783,11 @@ describe('MultichainTrackingHelper', () => { const { options, helper } = newMultichainTrackingHelper(); const ethQuery = helper.getEthQuery({ networkClientId: 'goerli' }); - expect(ethQuery.provider).toBe(MOCK_PROVIDERS.goerli); + expect(ethQuery?.provider).toBe(MOCK_PROVIDERS.goerli); expect(options.getNetworkClientById).toHaveBeenCalledTimes(1); expect(options.getNetworkClientById).toHaveBeenCalledWith('goerli'); }); - - it('returns EthQuery with the fallback global provider if networkClientId cannot be satisfied', () => { - const { options, helper } = newMultichainTrackingHelper(); - - const ethQuery = helper.getEthQuery({ - networkClientId: 'missingNetworkClientId', - }); - expect(ethQuery.provider).toBe(MOCK_PROVIDERS.mainnet); - - expect(options.getNetworkClientById).toHaveBeenCalledTimes(1); - expect(options.getNetworkClientById).toHaveBeenCalledWith( - 'missingNetworkClientId', - ); - }); }); describe('when given only chainId', () => { @@ -812,7 +795,7 @@ describe('MultichainTrackingHelper', () => { const { options, helper } = newMultichainTrackingHelper(); const ethQuery = helper.getEthQuery({ chainId: '0xa' }); - expect(ethQuery.provider).toBe( + expect(ethQuery?.provider).toBe( MOCK_PROVIDERS['customNetworkClientId-1'], ); @@ -824,24 +807,13 @@ describe('MultichainTrackingHelper', () => { 'customNetworkClientId-1', ); }); - - it('returns EthQuery with the fallback global provider if chainId cannot be satisfied', () => { - const { options, helper } = newMultichainTrackingHelper(); - - const ethQuery = helper.getEthQuery({ chainId: '0xdeadbeef' }); - expect(ethQuery.provider).toBe(MOCK_PROVIDERS.mainnet); - - expect(options.findNetworkClientIdByChainId).toHaveBeenCalledWith( - '0xdeadbeef', - ); - }); }); it('returns EthQuery with the global provider when no arguments are provided', () => { const { options, helper } = newMultichainTrackingHelper(); const ethQuery = helper.getEthQuery(); - expect(ethQuery.provider).toBe(MOCK_PROVIDERS.mainnet); + expect(ethQuery?.provider).toBe(MOCK_PROVIDERS.mainnet); expect(options.getNetworkClientById).not.toHaveBeenCalled(); }); @@ -855,13 +827,13 @@ describe('MultichainTrackingHelper', () => { networkClientId: 'goerli', chainId: '0x5', }); - expect(ethQuery.provider).toBe(MOCK_PROVIDERS.mainnet); + expect(ethQuery?.provider).toBe(MOCK_PROVIDERS.mainnet); ethQuery = helper.getEthQuery({ networkClientId: 'goerli' }); - expect(ethQuery.provider).toBe(MOCK_PROVIDERS.mainnet); + expect(ethQuery?.provider).toBe(MOCK_PROVIDERS.mainnet); ethQuery = helper.getEthQuery({ chainId: '0x5' }); - expect(ethQuery.provider).toBe(MOCK_PROVIDERS.mainnet); + expect(ethQuery?.provider).toBe(MOCK_PROVIDERS.mainnet); ethQuery = helper.getEthQuery(); - expect(ethQuery.provider).toBe(MOCK_PROVIDERS.mainnet); + expect(ethQuery?.provider).toBe(MOCK_PROVIDERS.mainnet); expect(options.getNetworkClientById).not.toHaveBeenCalled(); }); diff --git a/packages/transaction-controller/src/helpers/MultichainTrackingHelper.ts b/packages/transaction-controller/src/helpers/MultichainTrackingHelper.ts index 691a4bf048..55212d7ece 100644 --- a/packages/transaction-controller/src/helpers/MultichainTrackingHelper.ts +++ b/packages/transaction-controller/src/helpers/MultichainTrackingHelper.ts @@ -1,4 +1,3 @@ -import { NetworkType } from '@metamask/controller-utils'; import EthQuery from '@metamask/eth-query'; import type { NetworkClientId, @@ -9,14 +8,6 @@ import type { NetworkControllerStateChangeEvent, ProviderConfig, } from '@metamask/network-controller'; -import { - NetworkClientType, - type NetworkClientConfiguration, -} from '@metamask/network-controller'; -import type { - AutoManagedNetworkClient, - ProxyWithAccessibleTarget, -} from '@metamask/network-controller/src/create-auto-managed-network-client'; import type { Hex } from '@metamask/utils'; import { Mutex } from 'async-mutex'; import type { NonceLock, NonceTracker } from 'nonce-tracker'; @@ -59,18 +50,17 @@ export type MultichainTrackingHelperOptions = { createNonceTracker: (opts: { provider: Provider; blockTracker: BlockTracker; - chainId: Hex; + chainId?: Hex; }) => NonceTracker; createIncomingTransactionHelper: (opts: { - getNetworkClient: () => - | AutoManagedNetworkClient - | undefined; + getBlockTracker: () => BlockTracker | undefined; + getChainId: () => Hex | undefined; etherscanRemoteTransactionSource: EtherscanRemoteTransactionSource; }) => IncomingTransactionHelper; createPendingTransactionTracker: (opts: { - getNetworkClient: () => - | AutoManagedNetworkClient - | undefined; + getBlockTracker: () => BlockTracker | undefined; + getChainId: () => Hex | undefined; + getEthQuery: () => EthQuery | undefined; }) => PendingTransactionTracker; onNetworkStateChange: ( listener: ( @@ -109,20 +99,19 @@ export class MultichainTrackingHelper { readonly #createNonceTracker: (opts: { provider: Provider; blockTracker: BlockTracker; - chainId: Hex; + chainId?: Hex; }) => NonceTracker; readonly #createIncomingTransactionHelper: (opts: { - getNetworkClient: () => - | AutoManagedNetworkClient - | undefined; + getBlockTracker: () => BlockTracker | undefined; + getChainId: () => Hex | undefined; etherscanRemoteTransactionSource: EtherscanRemoteTransactionSource; }) => IncomingTransactionHelper; readonly #createPendingTransactionTracker: (opts: { - getNetworkClient: () => - | AutoManagedNetworkClient - | undefined; + getBlockTracker: () => BlockTracker | undefined; + getChainId: () => Hex | undefined; + getEthQuery: () => EthQuery | undefined; }) => PendingTransactionTracker; readonly #nonceMutexesByChainId = new Map>(); @@ -208,11 +197,8 @@ export class MultichainTrackingHelper { chainId?: Hex; } = {}): EthQuery | undefined { if (!this.#isMultichainEnabled || (!networkClientId && !chainId)) { - const selectedNetworkClient = this.getSelectedNetworkClient(); - - return selectedNetworkClient - ? new EthQuery(selectedNetworkClient.provider) - : undefined; + const globalProvider = this.#getGlobalProviderAndBlockTracker()?.provider; + return globalProvider ? new EthQuery(globalProvider) : undefined; } let networkClient: NetworkClient | undefined; @@ -282,7 +268,7 @@ export class MultichainTrackingHelper { let nonceTracker: NonceTracker | undefined; if (!networkClientId || !this.#isMultichainEnabled) { - this.#globalNonceTracker ??= this.#getSelectedNonceTracker(); + this.#globalNonceTracker ??= this.#getGlobalNonceTracker(); nonceTracker = this.#globalNonceTracker; } @@ -371,38 +357,6 @@ export class MultichainTrackingHelper { } } - getSelectedNetworkClient(): - | AutoManagedNetworkClient - | undefined { - const providerBlockTracker = this.#getGlobalProviderAndBlockTracker(); - const providerConfig = this.#getGlobalProviderConfig(); - - if (!providerBlockTracker || !providerConfig) { - log('Cannot get global network client as provider is unavailable'); - return undefined; - } - - log( - 'Retrieved global network client', - providerConfig.chainId, - providerConfig.type, - ); - - const { blockTracker, provider } = providerBlockTracker; - - return { - provider: provider as ProxyWithAccessibleTarget, - blockTracker: blockTracker as ProxyWithAccessibleTarget, - configuration: { - chainId: providerConfig.chainId, - type: - providerConfig.type === NetworkType.rpc - ? NetworkClientType.Custom - : NetworkClientType.Infura, - }, - } as AutoManagedNetworkClient; - } - #refreshTrackingMap = (networkClients: NetworkClientRegistry) => { this.#refreshEtherscanRemoteTransactionSources(networkClients); @@ -471,12 +425,15 @@ export class MultichainTrackingHelper { }); const incomingTransactionHelper = this.#createIncomingTransactionHelper({ - getNetworkClient: () => networkClient, + getBlockTracker: () => blockTracker, + getChainId: () => chainId, etherscanRemoteTransactionSource, }); const pendingTransactionTracker = this.#createPendingTransactionTracker({ - getNetworkClient: () => networkClient, + getBlockTracker: () => blockTracker, + getChainId: () => chainId, + getEthQuery: () => new EthQuery(provider), }); this.#trackingMap.set(networkClientId, { @@ -508,17 +465,20 @@ export class MultichainTrackingHelper { }); }; - #getSelectedNonceTracker(): NonceTracker | undefined { - const selectedNetworkClient = this.getSelectedNetworkClient(); + #getGlobalNonceTracker(): NonceTracker | undefined { + const globalProvider = this.#getGlobalProviderAndBlockTracker()?.provider; - if (!selectedNetworkClient) { - log('Cannot get nonce lock as selected network client is unavailable'); + const globalBlockTracker = + this.#getGlobalProviderAndBlockTracker()?.blockTracker; + + if (!globalProvider || !globalBlockTracker) { + log('Cannot get nonce lock as selected network is not available'); return undefined; } - const { provider, blockTracker, configuration } = selectedNetworkClient; - const { chainId } = configuration; - - return this.#createNonceTracker({ provider, blockTracker, chainId }); + return this.#createNonceTracker({ + provider: globalProvider, + blockTracker: globalBlockTracker, + }); } } diff --git a/packages/transaction-controller/src/helpers/PendingTransactionTracker.test.ts b/packages/transaction-controller/src/helpers/PendingTransactionTracker.test.ts index 846f8fa9fe..b003548a01 100644 --- a/packages/transaction-controller/src/helpers/PendingTransactionTracker.test.ts +++ b/packages/transaction-controller/src/helpers/PendingTransactionTracker.test.ts @@ -90,7 +90,7 @@ describe('PendingTransactionTracker', () => { options = { approveTransaction: jest.fn(), - blockTracker, + getBlockTracker: () => blockTracker, failTransaction, getChainId: () => CHAIN_ID_MOCK, getEthQuery: () => ETH_QUERY_MOCK, diff --git a/packages/transaction-controller/src/helpers/PendingTransactionTracker.ts b/packages/transaction-controller/src/helpers/PendingTransactionTracker.ts index f14fe2553d..49c4b92fcd 100644 --- a/packages/transaction-controller/src/helpers/PendingTransactionTracker.ts +++ b/packages/transaction-controller/src/helpers/PendingTransactionTracker.ts @@ -1,7 +1,6 @@ import { query } from '@metamask/controller-utils'; -import EthQuery from '@metamask/eth-query'; -import type { NetworkClientConfiguration } from '@metamask/network-controller'; -import type { AutoManagedNetworkClient } from '@metamask/network-controller/src/create-auto-managed-network-client'; +import type EthQuery from '@metamask/eth-query'; +import type { BlockTracker } from '@metamask/network-controller'; import type { Hex } from '@metamask/utils'; import { createModuleLogger } from '@metamask/utils'; import EventEmitter from 'events'; @@ -65,13 +64,17 @@ export class PendingTransactionTracker { #beforePublish: (transactionMeta: TransactionMeta) => boolean; + #currentBlockTracker?: BlockTracker; + #droppedBlockCountByHash: Map; - #getGlobalLock: (chainId: Hex) => Promise<() => void>; + #getBlockTracker: () => BlockTracker | undefined; - #getNetworkClient: () => - | AutoManagedNetworkClient - | undefined; + #getChainId: () => Hex | undefined; + + #getEthQuery: () => EthQuery | undefined; + + #getGlobalLock: (chainId: Hex) => Promise<() => void>; #getTransactions: () => TransactionMeta[]; @@ -81,15 +84,15 @@ export class PendingTransactionTracker { // eslint-disable-next-line @typescript-eslint/no-explicit-any #listener: any; - #networkClient?: AutoManagedNetworkClient; - #publishTransaction: (ethQuery: EthQuery, rawTx: string) => Promise; #running: boolean; constructor({ approveTransaction, - getNetworkClient, + getBlockTracker, + getChainId, + getEthQuery, getTransactions, isResubmitEnabled, getGlobalLock, @@ -97,9 +100,9 @@ export class PendingTransactionTracker { hooks, }: { approveTransaction: (transactionId: string) => Promise; - getNetworkClient: () => - | AutoManagedNetworkClient - | undefined; + getBlockTracker: () => BlockTracker | undefined; + getChainId: () => Hex | undefined; + getEthQuery: () => EthQuery | undefined; getTransactions: () => TransactionMeta[]; isResubmitEnabled?: boolean; getGlobalLock: (chainId: Hex) => Promise<() => void>; @@ -115,7 +118,9 @@ export class PendingTransactionTracker { this.#approveTransaction = approveTransaction; this.#droppedBlockCountByHash = new Map(); - this.#getNetworkClient = getNetworkClient; + this.#getBlockTracker = getBlockTracker; + this.#getChainId = getChainId; + this.#getEthQuery = getEthQuery; this.#getTransactions = getTransactions; this.#isResubmitEnabled = isResubmitEnabled ?? true; this.#listener = this.#onLatestBlock.bind(this); @@ -128,19 +133,19 @@ export class PendingTransactionTracker { } startIfPendingTransactions = () => { - this.#networkClient = this.#getNetworkClient(); + const { blockTracker, chainId } = this.#getNetworkObjects(); - if (!this.#networkClient) { - log('Unable to start as network client is not available'); + if (!blockTracker || !chainId) { + log('Unable to start as network is not available'); return; } - const pendingTransactions = this.#getPendingTransactions( - this.#networkClient.configuration.chainId, - ); + this.#currentBlockTracker = blockTracker; + + const pendingTransactions = this.#getPendingTransactions(chainId); if (pendingTransactions.length) { - this.#start(this.#networkClient); + this.#start(blockTracker); } else { this.stop(); } @@ -155,27 +160,19 @@ export class PendingTransactionTracker { let releaseLock: (() => void) | undefined; try { - const networkClient = this.#getNetworkClient(); + const { blockTracker, chainId, ethQuery } = this.#getNetworkObjects(); - if (!networkClient) { + if (!blockTracker || !chainId || !ethQuery) { log( - 'Cannot force check transaction as network client not available', + 'Cannot force check transaction as network not available', txMeta.id, ); return; } - releaseLock = await this.#getGlobalLock( - networkClient.configuration.chainId, - ); - - const ethQuery = new EthQuery(networkClient.provider); + releaseLock = await this.#getGlobalLock(chainId); - await this.#checkTransaction( - txMeta, - ethQuery, - networkClient.configuration.chainId, - ); + await this.#checkTransaction(txMeta, ethQuery, chainId); } catch (error) { /* istanbul ignore next */ log('Failed to force check transaction', error); @@ -184,12 +181,12 @@ export class PendingTransactionTracker { } } - #start(networkClient: AutoManagedNetworkClient) { + #start(blockTracker: BlockTracker) { if (this.#running) { return; } - networkClient.blockTracker.on('latest', this.#listener); + blockTracker.on('latest', this.#listener); this.#running = true; log('Started polling'); @@ -200,8 +197,8 @@ export class PendingTransactionTracker { return; } - this.#networkClient?.blockTracker?.removeListener('latest', this.#listener); - this.#networkClient = undefined; + this.#currentBlockTracker?.removeListener('latest', this.#listener); + this.#currentBlockTracker = undefined; this.#running = false; log('Stopped polling'); @@ -209,19 +206,17 @@ export class PendingTransactionTracker { async #onLatestBlock(latestBlockNumber: string) { try { - const networkClient = this.#getNetworkClient(); + const { blockTracker, chainId, ethQuery } = this.#getNetworkObjects(); - if (!networkClient) { - log('Cannot process latest block as network client not available'); + if (!blockTracker || !chainId || !ethQuery) { + log('Cannot process latest block as network not available'); return; } - const releaseLock = await this.#getGlobalLock( - networkClient.configuration.chainId, - ); + const releaseLock = await this.#getGlobalLock(chainId); try { - await this.#checkTransactions(networkClient); + await this.#checkTransactions(chainId, ethQuery); } catch (error) { /* istanbul ignore next */ log('Failed to check transactions', error); @@ -230,7 +225,7 @@ export class PendingTransactionTracker { } try { - await this.#resubmitTransactions(latestBlockNumber, networkClient); + await this.#resubmitTransactions(latestBlockNumber, chainId, ethQuery); } catch (error) { /* istanbul ignore next */ log('Failed to resubmit transactions', error); @@ -240,14 +235,10 @@ export class PendingTransactionTracker { } } - async #checkTransactions( - networkClient: AutoManagedNetworkClient, - ) { + async #checkTransactions(chainId: Hex, ethQuery: EthQuery) { log('Checking transactions'); - const pendingTransactions = this.#getPendingTransactions( - networkClient.configuration.chainId, - ); + const pendingTransactions = this.#getPendingTransactions(chainId); if (!pendingTransactions.length) { log('No pending transactions to check'); @@ -259,22 +250,17 @@ export class PendingTransactionTracker { ids: pendingTransactions.map((tx) => tx.id), }); - const ethQuery = new EthQuery(networkClient.provider); - await Promise.all( pendingTransactions.map((tx) => - this.#checkTransaction( - tx, - ethQuery, - networkClient.configuration.chainId, - ), + this.#checkTransaction(tx, ethQuery, chainId), ), ); } async #resubmitTransactions( latestBlockNumber: string, - networkClient: AutoManagedNetworkClient, + chainId: Hex, + ethQuery: EthQuery, ) { if (!this.#isResubmitEnabled || !this.#running) { return; @@ -282,9 +268,7 @@ export class PendingTransactionTracker { log('Resubmitting transactions'); - const pendingTransactions = this.#getPendingTransactions( - networkClient.configuration.chainId, - ); + const pendingTransactions = this.#getPendingTransactions(chainId); if (!pendingTransactions.length) { log('No pending transactions to resubmit'); @@ -296,8 +280,6 @@ export class PendingTransactionTracker { ids: pendingTransactions.map((tx) => tx.id), }); - const ethQuery = new EthQuery(networkClient.provider); - for (const txMeta of pendingTransactions) { try { await this.#resubmitTransaction(txMeta, latestBlockNumber, ethQuery); @@ -331,7 +313,7 @@ export class PendingTransactionTracker { async #resubmitTransaction( txMeta: TransactionMeta, latestBlockNumber: string, - ethQuery: EthQuery | undefined, + ethQuery: EthQuery, ) { if (!this.#isResubmitDue(txMeta, latestBlockNumber)) { return; @@ -339,7 +321,7 @@ export class PendingTransactionTracker { log('Resubmitting transaction', txMeta.id); - const { id, rawTx } = txMeta; + const { rawTx } = txMeta; if (!this.#beforePublish(txMeta)) { return; @@ -351,11 +333,6 @@ export class PendingTransactionTracker { return; } - if (!ethQuery) { - log('Cannot resubmit transaction as provider not available', id); - return; - } - await this.#publishTransaction(ethQuery, rawTx); const retryCount = (txMeta.retryCount ?? 0) + 1; @@ -624,4 +601,12 @@ export class PendingTransactionTracker { (tx) => tx.chainId === currentChainId, ); } + + #getNetworkObjects() { + const blockTracker = this.#getBlockTracker(); + const chainId = this.#getChainId(); + const ethQuery = this.#getEthQuery(); + + return { blockTracker, chainId, ethQuery }; + } } From 1f423f276b8ed261cf37c10ee4839375bf7b3140 Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Wed, 20 Mar 2024 11:57:38 +0000 Subject: [PATCH 04/12] Update coverage targets --- packages/transaction-controller/jest.config.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/transaction-controller/jest.config.js b/packages/transaction-controller/jest.config.js index 99047e2d36..1718911100 100644 --- a/packages/transaction-controller/jest.config.js +++ b/packages/transaction-controller/jest.config.js @@ -17,10 +17,10 @@ module.exports = merge(baseConfig, { // An object that configures minimum threshold enforcement for coverage results coverageThreshold: { global: { - branches: 94.31, - functions: 98.51, - lines: 98.99, - statements: 99, + branches: 91.98, + functions: 97.68, + lines: 97.52, + statements: 97.5, }, }, From ece1ce186fc1e5636cf7076b38b241f0e7f0b0ed Mon Sep 17 00:00:00 2001 From: legobt <6wbvkn0j@anonaddy.me> Date: Wed, 29 May 2024 11:23:24 +0000 Subject: [PATCH 05/12] lint:fix --- .../src/SubjectMetadataController.ts | 5 ++++- .../src/TransactionController.ts | 8 +++++--- .../src/helpers/GasFeePoller.ts | 14 ++++++++++---- .../src/helpers/MultichainTrackingHelper.ts | 5 ++++- packages/transaction-controller/src/utils/nonce.ts | 2 +- 5 files changed, 24 insertions(+), 10 deletions(-) diff --git a/packages/permission-controller/src/SubjectMetadataController.ts b/packages/permission-controller/src/SubjectMetadataController.ts index dcd2b9cfa7..203f9893ca 100644 --- a/packages/permission-controller/src/SubjectMetadataController.ts +++ b/packages/permission-controller/src/SubjectMetadataController.ts @@ -209,6 +209,8 @@ export class SubjectMetadataController extends BaseController< this.subjectsWithoutPermissionsEncounteredSinceStartup.add(origin); this.update((draftState) => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore something about string-indexing draftState.subjectMetadata[origin] = newMetadata; if (typeof originToForget === 'string') { delete draftState.subjectMetadata[originToForget]; @@ -231,7 +233,8 @@ export class SubjectMetadataController extends BaseController< */ trimMetadataState(): void { this.update((draftState) => { - // @ts-expect-error ts(2589) + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore infinite type-resursion srsly return SubjectMetadataController.getTrimmedState( draftState, this.subjectHasPermissions, diff --git a/packages/transaction-controller/src/TransactionController.ts b/packages/transaction-controller/src/TransactionController.ts index fda322a044..83e820824a 100644 --- a/packages/transaction-controller/src/TransactionController.ts +++ b/packages/transaction-controller/src/TransactionController.ts @@ -834,7 +834,6 @@ export class TransactionController extends BaseController< this.publish = hooks?.publish ?? (() => Promise.resolve({ transactionHash: undefined })); - const findNetworkClientIdByChainId = (chainId: Hex) => { return this.messagingSystem.call( `NetworkController:findNetworkClientIdByChainId`, @@ -2693,7 +2692,10 @@ export class TransactionController extends BaseController< this.approvingTransactionIds.delete(transactionId), ); - const getNonceLock = (address: string, networkClientId?: NetworkClientId) => + const getNonceLock = ( + address: string, + networkClientId?: NetworkClientId, + ) => this.#multichainTrackingHelper.getNonceLock(address, networkClientId); const [nonce, releaseNonce] = await getNextNonce( @@ -2996,7 +2998,7 @@ export class TransactionController extends BaseController< return { meta: transaction, isCompleted }; } -/* + /* private getChainId(networkClientId?: NetworkClientId): Hex | undefined { if (networkClientId) { return this.messagingSystem.call( diff --git a/packages/transaction-controller/src/helpers/GasFeePoller.ts b/packages/transaction-controller/src/helpers/GasFeePoller.ts index 8e73b12fc4..302646a15c 100644 --- a/packages/transaction-controller/src/helpers/GasFeePoller.ts +++ b/packages/transaction-controller/src/helpers/GasFeePoller.ts @@ -37,7 +37,10 @@ export class GasFeePoller { options: FetchGasFeeEstimateOptions, ) => Promise; - #getProvider: (chainId: Hex, networkClientId?: NetworkClientId) => Provider | undefined; + #getProvider: ( + chainId: Hex, + networkClientId?: NetworkClientId, + ) => Provider | undefined; #getTransactions: () => TransactionMeta[]; @@ -72,7 +75,10 @@ export class GasFeePoller { getGasFeeControllerEstimates: ( options: FetchGasFeeEstimateOptions, ) => Promise; - getProvider: (chainId: Hex, networkClientId?: NetworkClientId) => Provider | undefined; + getProvider: ( + chainId: Hex, + networkClientId?: NetworkClientId, + ) => Provider | undefined; getTransactions: () => TransactionMeta[]; layer1GasFeeFlows: Layer1GasFeeFlow[]; onStateChange: (listener: () => void) => void; @@ -195,13 +201,13 @@ export class GasFeePoller { const provider = this.#getProvider(chainId, networkClientId); if (!provider) { log('Provider not available', transactionMeta.id); - return; + return undefined; } const ethQuery = new EthQuery(provider); if (!ethQuery) { log('Provider not available', transactionMeta.id); - return; + return undefined; } const gasFeeFlow = getGasFeeFlow(transactionMeta, this.#gasFeeFlows); diff --git a/packages/transaction-controller/src/helpers/MultichainTrackingHelper.ts b/packages/transaction-controller/src/helpers/MultichainTrackingHelper.ts index 1ca7b53b94..403cfabdbd 100644 --- a/packages/transaction-controller/src/helpers/MultichainTrackingHelper.ts +++ b/packages/transaction-controller/src/helpers/MultichainTrackingHelper.ts @@ -233,7 +233,10 @@ export class MultichainTrackingHelper { chainId, }); - return networkClient?.provider || this.#getGlobalProviderAndBlockTracker()?.provider; + return ( + networkClient?.provider || + this.#getGlobalProviderAndBlockTracker()?.provider + ); } /** diff --git a/packages/transaction-controller/src/utils/nonce.ts b/packages/transaction-controller/src/utils/nonce.ts index f4b12cff8e..0aa25393d7 100644 --- a/packages/transaction-controller/src/utils/nonce.ts +++ b/packages/transaction-controller/src/utils/nonce.ts @@ -1,9 +1,9 @@ import { toHex } from '@metamask/controller-utils'; -import { providerErrors } from '@metamask/rpc-errors'; import type { NonceLock, Transaction as NonceTrackerTransaction, } from '@metamask/nonce-tracker'; +import { providerErrors } from '@metamask/rpc-errors'; import { createModuleLogger, projectLogger } from '../logger'; import type { TransactionMeta, TransactionStatus } from '../types'; From 9e27bfe7c2459f28da1fca367443d732ec76ac02 Mon Sep 17 00:00:00 2001 From: legobt <6wbvkn0j@anonaddy.me> Date: Tue, 28 May 2024 08:20:17 +0000 Subject: [PATCH 06/12] deps(transaction-controller): @metamask/nonce-tracker@^5.0.0->^6.0.0 --- packages/transaction-controller/package.json | 2 +- .../src/TransactionController.ts | 1 - yarn.lock | 10 +++++----- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/transaction-controller/package.json b/packages/transaction-controller/package.json index 583cfdd020..4376f54720 100644 --- a/packages/transaction-controller/package.json +++ b/packages/transaction-controller/package.json @@ -54,7 +54,7 @@ "@metamask/gas-fee-controller": "^17.0.0", "@metamask/metamask-eth-abis": "^3.1.1", "@metamask/network-controller": "^19.0.0", - "@metamask/nonce-tracker": "^5.0.0", + "@metamask/nonce-tracker": "^6.0.0", "@metamask/rpc-errors": "^6.2.1", "@metamask/utils": "^8.3.0", "async-mutex": "^0.5.0", diff --git a/packages/transaction-controller/src/TransactionController.ts b/packages/transaction-controller/src/TransactionController.ts index 3ead78cf6a..19c538be40 100644 --- a/packages/transaction-controller/src/TransactionController.ts +++ b/packages/transaction-controller/src/TransactionController.ts @@ -3405,7 +3405,6 @@ export class TransactionController extends BaseController< // TODO: Fix types // eslint-disable-next-line @typescript-eslint/no-explicit-any provider: provider as any, - // @ts-expect-error TODO: Fix types blockTracker, getPendingTransactions: this.#getNonceTrackerPendingTransactions.bind( this, diff --git a/yarn.lock b/yarn.lock index bece8b6e40..1b296a2e9d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2617,15 +2617,15 @@ __metadata: languageName: unknown linkType: soft -"@metamask/nonce-tracker@npm:^5.0.0": - version: 5.0.0 - resolution: "@metamask/nonce-tracker@npm:5.0.0" +"@metamask/nonce-tracker@npm:^6.0.0": + version: 6.0.0 + resolution: "@metamask/nonce-tracker@npm:6.0.0" dependencies: "@ethersproject/providers": ^5.7.2 async-mutex: ^0.3.1 peerDependencies: "@metamask/eth-block-tracker": ">=9" - checksum: 31de9d62f2aec52688a4b7ec1fab877d1f2f4e6b2b395abef2790ddee63b3511f312c07c29d1c191f900231dbd4cdde8e969b210462f78253a177cacee72688c + checksum: d24cdf8eedc892673c3fe4ee1eff87356810840d5d3abb59c3e8adc5fbf21a1a9498bc1df50a7a6a61e7ebe9f3a3f45df4dd4b5b4269e7a52e5d8121d68f83ef languageName: node linkType: hard @@ -3152,7 +3152,7 @@ __metadata: "@metamask/gas-fee-controller": ^17.0.0 "@metamask/metamask-eth-abis": ^3.1.1 "@metamask/network-controller": ^19.0.0 - "@metamask/nonce-tracker": ^5.0.0 + "@metamask/nonce-tracker": ^6.0.0 "@metamask/rpc-errors": ^6.2.1 "@metamask/utils": ^8.3.0 "@types/bn.js": ^5.1.5 From 91834efe3172bf47be0482bec7e4658345646910 Mon Sep 17 00:00:00 2001 From: legobt <6wbvkn0j@anonaddy.me> Date: Tue, 28 May 2024 10:18:49 +0000 Subject: [PATCH 07/12] deps(network-controller): @metamask/eth-block-tracker@^9.0.2->^9.0.3 --- package.json | 2 +- packages/network-controller/package.json | 2 +- yarn.lock | 27 +++++++++++++++++------- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 21d8ae7442..a861f4e741 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "@metamask/eslint-config-jest": "^12.1.0", "@metamask/eslint-config-nodejs": "^12.1.0", "@metamask/eslint-config-typescript": "^12.1.0", - "@metamask/eth-block-tracker": "^9.0.2", + "@metamask/eth-block-tracker": "^9.0.3", "@metamask/eth-json-rpc-provider": "^4.0.0", "@metamask/json-rpc-engine": "^9.0.0", "@metamask/utils": "^8.3.0", diff --git a/packages/network-controller/package.json b/packages/network-controller/package.json index b720f8f4b6..c33d1e7540 100644 --- a/packages/network-controller/package.json +++ b/packages/network-controller/package.json @@ -43,7 +43,7 @@ "dependencies": { "@metamask/base-controller": "^6.0.0", "@metamask/controller-utils": "^11.0.0", - "@metamask/eth-block-tracker": "^9.0.2", + "@metamask/eth-block-tracker": "^9.0.3", "@metamask/eth-json-rpc-infura": "^9.1.0", "@metamask/eth-json-rpc-middleware": "^12.1.1", "@metamask/eth-json-rpc-provider": "^4.0.0", diff --git a/yarn.lock b/yarn.lock index 1b296a2e9d..14bb108388 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1974,7 +1974,7 @@ __metadata: "@metamask/eslint-config-jest": ^12.1.0 "@metamask/eslint-config-nodejs": ^12.1.0 "@metamask/eslint-config-typescript": ^12.1.0 - "@metamask/eth-block-tracker": ^9.0.2 + "@metamask/eth-block-tracker": ^9.0.3 "@metamask/eth-json-rpc-provider": ^4.0.0 "@metamask/json-rpc-engine": ^9.0.0 "@metamask/utils": ^8.3.0 @@ -2104,16 +2104,16 @@ __metadata: languageName: node linkType: hard -"@metamask/eth-block-tracker@npm:^9.0.2": - version: 9.0.2 - resolution: "@metamask/eth-block-tracker@npm:9.0.2" +"@metamask/eth-block-tracker@npm:^9.0.2, @metamask/eth-block-tracker@npm:^9.0.3": + version: 9.0.3 + resolution: "@metamask/eth-block-tracker@npm:9.0.3" dependencies: - "@metamask/eth-json-rpc-provider": ^2.3.1 + "@metamask/eth-json-rpc-provider": ^3.0.2 "@metamask/safe-event-emitter": ^3.0.0 "@metamask/utils": ^8.1.0 json-rpc-random-id: ^1.0.1 pify: ^5.0.0 - checksum: ec66cb100b011cafb2052bf0ab6935336ea4c8afd1f6c48326faf362a387d36112b5fffe296f3c75edfb09b29516182015c6f31ee6cb615c0ef4d2aa4ddb9c88 + checksum: edd3d59a0416752d90c8e2d8c10c31635dbe3eb323fcb054c401528afe4cbbb6a5a85aedd6ffee4a504d9779656bfab027f2274fd95981c90bf56b6f565dbca2 languageName: node linkType: hard @@ -2178,7 +2178,7 @@ __metadata: languageName: unknown linkType: soft -"@metamask/eth-json-rpc-provider@npm:^2.1.0, @metamask/eth-json-rpc-provider@npm:^2.3.1": +"@metamask/eth-json-rpc-provider@npm:^2.1.0": version: 2.3.2 resolution: "@metamask/eth-json-rpc-provider@npm:2.3.2" dependencies: @@ -2189,6 +2189,17 @@ __metadata: languageName: node linkType: hard +"@metamask/eth-json-rpc-provider@npm:^3.0.2": + version: 3.0.2 + resolution: "@metamask/eth-json-rpc-provider@npm:3.0.2" + dependencies: + "@metamask/json-rpc-engine": ^8.0.2 + "@metamask/safe-event-emitter": ^3.0.0 + "@metamask/utils": ^8.3.0 + checksum: 0321eaad6fa205a9d3ddcfaf28e63c05291614893cb2e116151185a4acbd6bb6a508d6e556b3cb8bc4d3caef4bf0a638202d9b6bdc127fbcb81715eb2660a809 + languageName: node + linkType: hard + "@metamask/eth-query@npm:^4.0.0": version: 4.0.0 resolution: "@metamask/eth-query@npm:4.0.0" @@ -2589,7 +2600,7 @@ __metadata: "@metamask/auto-changelog": ^3.4.4 "@metamask/base-controller": ^6.0.0 "@metamask/controller-utils": ^11.0.0 - "@metamask/eth-block-tracker": ^9.0.2 + "@metamask/eth-block-tracker": ^9.0.3 "@metamask/eth-json-rpc-infura": ^9.1.0 "@metamask/eth-json-rpc-middleware": ^12.1.1 "@metamask/eth-json-rpc-provider": ^4.0.0 From 4d744d82e8bfd2837ca48db8d613e3293d1a6b04 Mon Sep 17 00:00:00 2001 From: legobt <6wbvkn0j@anonaddy.me> Date: Tue, 28 May 2024 10:19:13 +0000 Subject: [PATCH 08/12] chore(transaction-controller): reinit providers and blocktrackers between tests --- .../src/helpers/MultichainTrackingHelper.test.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/transaction-controller/src/helpers/MultichainTrackingHelper.test.ts b/packages/transaction-controller/src/helpers/MultichainTrackingHelper.test.ts index 5a03b5c55f..a2ac2293cf 100644 --- a/packages/transaction-controller/src/helpers/MultichainTrackingHelper.test.ts +++ b/packages/transaction-controller/src/helpers/MultichainTrackingHelper.test.ts @@ -220,6 +220,16 @@ function newMultichainTrackingHelper( describe('MultichainTrackingHelper', () => { beforeEach(() => { jest.resetAllMocks(); + + for (const network of [ + 'mainnet', + 'goerli', + 'sepolia', + 'customNetworkClientId-1', + ] as const) { + MOCK_BLOCK_TRACKERS[network] = buildMockBlockTracker(network); + MOCK_PROVIDERS[network] = buildMockProvider(network); + } }); describe('onNetworkStateChange', () => { From 2d19826a35fcda650f005415c6ae8085033976ec Mon Sep 17 00:00:00 2001 From: legobt <6wbvkn0j@anonaddy.me> Date: Thu, 6 Jun 2024 22:32:18 +0000 Subject: [PATCH 09/12] Revert "chore(transaction-controller): reinit providers and blocktrackers between tests" This reverts commit 4d744d82e8bfd2837ca48db8d613e3293d1a6b04. --- .../src/helpers/MultichainTrackingHelper.test.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/packages/transaction-controller/src/helpers/MultichainTrackingHelper.test.ts b/packages/transaction-controller/src/helpers/MultichainTrackingHelper.test.ts index a2ac2293cf..5a03b5c55f 100644 --- a/packages/transaction-controller/src/helpers/MultichainTrackingHelper.test.ts +++ b/packages/transaction-controller/src/helpers/MultichainTrackingHelper.test.ts @@ -220,16 +220,6 @@ function newMultichainTrackingHelper( describe('MultichainTrackingHelper', () => { beforeEach(() => { jest.resetAllMocks(); - - for (const network of [ - 'mainnet', - 'goerli', - 'sepolia', - 'customNetworkClientId-1', - ] as const) { - MOCK_BLOCK_TRACKERS[network] = buildMockBlockTracker(network); - MOCK_PROVIDERS[network] = buildMockProvider(network); - } }); describe('onNetworkStateChange', () => { From 00d9054941bef2b220ee92251b6d887a5de66fb4 Mon Sep 17 00:00:00 2001 From: legobt <6wbvkn0j@anonaddy.me> Date: Thu, 6 Jun 2024 23:12:36 +0000 Subject: [PATCH 10/12] test(transaction-controller): refactor provider injection to lookup table --- .../src/TransactionController.test.ts | 39 ++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/packages/transaction-controller/src/TransactionController.test.ts b/packages/transaction-controller/src/TransactionController.test.ts index d7dd3d8ee8..308a3134a5 100644 --- a/packages/transaction-controller/src/TransactionController.test.ts +++ b/packages/transaction-controller/src/TransactionController.test.ts @@ -298,12 +298,15 @@ function waitForTransactionFinished( const MOCK_PREFERENCES = { state: { selectedAddress: 'foo' } }; const INFURA_PROJECT_ID = '341eacb578dd44a1a049cbc5f6fd4035'; -const MAINNET_PROVIDER = new HttpProvider( - `https://mainnet.infura.io/v3/${INFURA_PROJECT_ID}`, -); -const PALM_PROVIDER = new HttpProvider( - `https://palm-mainnet.infura.io/v3/${INFURA_PROJECT_ID}`, -); +const HTTP_PROVIDERS = { + mainnet: new HttpProvider(`https://mainnet.infura.io/v3/${INFURA_PROJECT_ID}`), + palm: new HttpProvider(`https://palm-mainnet.infura.io/v3/${INFURA_PROJECT_ID}`), + /* + goerli: new HttpProvider(`https://palm-mainnet.infura.io/v3/${INFURA_PROJECT_ID}`), + linea: new HttpProvider(`https://palm-mainnet.infura.io/v3/${INFURA_PROJECT_ID}`), + linea_goerli: new HttpProvider(`https://palm-mainnet.infura.io/v3/${INFURA_PROJECT_ID}`), + */ +} type MockNetwork = { provider: Provider; @@ -313,8 +316,8 @@ type MockNetwork = { }; const MOCK_NETWORK: MockNetwork = { - provider: MAINNET_PROVIDER, - blockTracker: buildMockBlockTracker('0x102833C', MAINNET_PROVIDER), + provider: HTTP_PROVIDERS.mainnet, + blockTracker: buildMockBlockTracker('0x102833C', HTTP_PROVIDERS.mainnet), state: { selectedNetworkClientId: NetworkType.goerli, networksMetadata: { @@ -333,8 +336,8 @@ const MOCK_NETWORK: MockNetwork = { subscribe: () => undefined, }; const MOCK_NETWORK_WITHOUT_CHAIN_ID: MockNetwork = { - provider: PALM_PROVIDER, - blockTracker: buildMockBlockTracker('0x102833C', PALM_PROVIDER), + provider: HTTP_PROVIDERS.palm, + blockTracker: buildMockBlockTracker('0x102833C', HTTP_PROVIDERS.palm), state: { selectedNetworkClientId: NetworkType.goerli, networksMetadata: { @@ -351,8 +354,8 @@ const MOCK_NETWORK_WITHOUT_CHAIN_ID: MockNetwork = { subscribe: () => undefined, }; const MOCK_MAINNET_NETWORK: MockNetwork = { - provider: MAINNET_PROVIDER, - blockTracker: buildMockBlockTracker('0x102833C', MAINNET_PROVIDER), + provider: HTTP_PROVIDERS.mainnet, + blockTracker: buildMockBlockTracker('0x102833C', HTTP_PROVIDERS.mainnet), state: { selectedNetworkClientId: NetworkType.mainnet, networksMetadata: { @@ -372,8 +375,8 @@ const MOCK_MAINNET_NETWORK: MockNetwork = { }; const MOCK_LINEA_MAINNET_NETWORK: MockNetwork = { - provider: PALM_PROVIDER, - blockTracker: buildMockBlockTracker('0xA6EDFC', PALM_PROVIDER), + provider: HTTP_PROVIDERS.palm, + blockTracker: buildMockBlockTracker('0xA6EDFC', HTTP_PROVIDERS.palm), state: { selectedNetworkClientId: NetworkType['linea-mainnet'], networksMetadata: { @@ -393,8 +396,8 @@ const MOCK_LINEA_MAINNET_NETWORK: MockNetwork = { }; const MOCK_LINEA_GOERLI_NETWORK: MockNetwork = { - provider: PALM_PROVIDER, - blockTracker: buildMockBlockTracker('0xA6EDFC', PALM_PROVIDER), + provider: HTTP_PROVIDERS.palm, + blockTracker: buildMockBlockTracker('0xA6EDFC', HTTP_PROVIDERS.palm), state: { selectedNetworkClientId: NetworkType['linea-goerli'], networksMetadata: { @@ -414,8 +417,8 @@ const MOCK_LINEA_GOERLI_NETWORK: MockNetwork = { }; const MOCK_CUSTOM_NETWORK: MockNetwork = { - provider: PALM_PROVIDER, - blockTracker: buildMockBlockTracker('0xA6EDFC', PALM_PROVIDER), + provider: HTTP_PROVIDERS.palm, + blockTracker: buildMockBlockTracker('0xA6EDFC', HTTP_PROVIDERS.palm), state: { selectedNetworkClientId: 'uuid-1', networksMetadata: { From 9dbaaed92fbe2534f251f794630c0fb2a040bdd6 Mon Sep 17 00:00:00 2001 From: legobt <6wbvkn0j@anonaddy.me> Date: Thu, 6 Jun 2024 23:15:40 +0000 Subject: [PATCH 11/12] chore: cleanup merge --- .../src/TransactionController.test.ts | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/packages/transaction-controller/src/TransactionController.test.ts b/packages/transaction-controller/src/TransactionController.test.ts index 308a3134a5..efacffd9ca 100644 --- a/packages/transaction-controller/src/TransactionController.test.ts +++ b/packages/transaction-controller/src/TransactionController.test.ts @@ -335,24 +335,6 @@ const MOCK_NETWORK: MockNetwork = { }, subscribe: () => undefined, }; -const MOCK_NETWORK_WITHOUT_CHAIN_ID: MockNetwork = { - provider: HTTP_PROVIDERS.palm, - blockTracker: buildMockBlockTracker('0x102833C', HTTP_PROVIDERS.palm), - state: { - selectedNetworkClientId: NetworkType.goerli, - networksMetadata: { - [NetworkType.goerli]: { - EIPS: { 1559: false }, - status: NetworkStatus.Available, - }, - }, - providerConfig: { - type: NetworkType.goerli, - } as NetworkState['providerConfig'], - networkConfigurations: {}, - }, - subscribe: () => undefined, -}; const MOCK_MAINNET_NETWORK: MockNetwork = { provider: HTTP_PROVIDERS.mainnet, blockTracker: buildMockBlockTracker('0x102833C', HTTP_PROVIDERS.mainnet), From ad4f08f132ff448ba748f2199d8933ae0fe8f714 Mon Sep 17 00:00:00 2001 From: legobt <6wbvkn0j@anonaddy.me> Date: Thu, 6 Jun 2024 23:16:25 +0000 Subject: [PATCH 12/12] chore: use separate blockTrackers/providers per network in test previously a single provider referencing "palm mainnet" stood in for all except ethereum-mainnet --- .../src/TransactionController.test.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/transaction-controller/src/TransactionController.test.ts b/packages/transaction-controller/src/TransactionController.test.ts index efacffd9ca..4add11a0cb 100644 --- a/packages/transaction-controller/src/TransactionController.test.ts +++ b/packages/transaction-controller/src/TransactionController.test.ts @@ -300,12 +300,10 @@ const MOCK_PREFERENCES = { state: { selectedAddress: 'foo' } }; const INFURA_PROJECT_ID = '341eacb578dd44a1a049cbc5f6fd4035'; const HTTP_PROVIDERS = { mainnet: new HttpProvider(`https://mainnet.infura.io/v3/${INFURA_PROJECT_ID}`), - palm: new HttpProvider(`https://palm-mainnet.infura.io/v3/${INFURA_PROJECT_ID}`), - /* goerli: new HttpProvider(`https://palm-mainnet.infura.io/v3/${INFURA_PROJECT_ID}`), linea: new HttpProvider(`https://palm-mainnet.infura.io/v3/${INFURA_PROJECT_ID}`), linea_goerli: new HttpProvider(`https://palm-mainnet.infura.io/v3/${INFURA_PROJECT_ID}`), - */ + custom: new HttpProvider(`http://127.0.0.123:456/ethrpc?apiKey=foobar`), } type MockNetwork = { @@ -357,8 +355,8 @@ const MOCK_MAINNET_NETWORK: MockNetwork = { }; const MOCK_LINEA_MAINNET_NETWORK: MockNetwork = { - provider: HTTP_PROVIDERS.palm, - blockTracker: buildMockBlockTracker('0xA6EDFC', HTTP_PROVIDERS.palm), + provider: HTTP_PROVIDERS.linea, + blockTracker: buildMockBlockTracker('0xA6EDFC', HTTP_PROVIDERS.linea), state: { selectedNetworkClientId: NetworkType['linea-mainnet'], networksMetadata: { @@ -378,8 +376,8 @@ const MOCK_LINEA_MAINNET_NETWORK: MockNetwork = { }; const MOCK_LINEA_GOERLI_NETWORK: MockNetwork = { - provider: HTTP_PROVIDERS.palm, - blockTracker: buildMockBlockTracker('0xA6EDFC', HTTP_PROVIDERS.palm), + provider: HTTP_PROVIDERS.linea_goerli, + blockTracker: buildMockBlockTracker('0xA6EDFC', HTTP_PROVIDERS.linea_goerli), state: { selectedNetworkClientId: NetworkType['linea-goerli'], networksMetadata: { @@ -399,8 +397,8 @@ const MOCK_LINEA_GOERLI_NETWORK: MockNetwork = { }; const MOCK_CUSTOM_NETWORK: MockNetwork = { - provider: HTTP_PROVIDERS.palm, - blockTracker: buildMockBlockTracker('0xA6EDFC', HTTP_PROVIDERS.palm), + provider: HTTP_PROVIDERS.custom, + blockTracker: buildMockBlockTracker('0xA6EDFC', HTTP_PROVIDERS.custom), state: { selectedNetworkClientId: 'uuid-1', networksMetadata: {