diff --git a/packages/transaction-controller/src/TransactionController.test.ts b/packages/transaction-controller/src/TransactionController.test.ts index eb2f5af802a..189504d7089 100644 --- a/packages/transaction-controller/src/TransactionController.test.ts +++ b/packages/transaction-controller/src/TransactionController.test.ts @@ -2068,6 +2068,11 @@ describe('TransactionController', () => { }, ], }); + + const testProvider = MAINNET_PROVIDER; + const testBlockTracker = buildMockBlockTracker('0x102833C'); + controller.delayedInit(testProvider, testBlockTracker); + const registry = await controller.handleMethodData('0xf39b5b9b'); expect(registry.parsedRegistryMethod).toStrictEqual({ @@ -2109,6 +2114,10 @@ describe('TransactionController', () => { ], }); + const testProvider = MAINNET_PROVIDER; + const testBlockTracker = buildMockBlockTracker('0x102833C'); + controller.delayedInit(testProvider, testBlockTracker); + await controller.handleMethodData('0xf39b5b9b'); const registryLookup = jest.spyOn( diff --git a/packages/transaction-controller/src/TransactionController.ts b/packages/transaction-controller/src/TransactionController.ts index 0823a4ce638..f09f7775dfb 100644 --- a/packages/transaction-controller/src/TransactionController.ts +++ b/packages/transaction-controller/src/TransactionController.ts @@ -105,6 +105,8 @@ import { validateTransactionOrigin, validateTxParams, } from './utils/validation'; +import { SafeEventEmitterProvider } from '@metamask/eth-json-rpc-provider'; +import { ProxyWithAccessibleTarget } from '@metamask/network-controller/src/create-auto-managed-network-client'; export const HARDFORK = Hardfork.London; @@ -329,7 +331,7 @@ export class TransactionController extends BaseControllerV1< private readonly inProcessOfSigning: Set = new Set(); - private readonly nonceTracker: NonceTracker; + private nonceTracker!: NonceTracker; // TODO: Replace `any` with type // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -337,7 +339,7 @@ export class TransactionController extends BaseControllerV1< private readonly mutex = new Mutex(); - private readonly gasFeeFlows: GasFeeFlow[]; + private gasFeeFlows!: GasFeeFlow[]; private readonly getSavedGasFees: (chainId: Hex) => SavedGasFees | undefined; @@ -364,13 +366,23 @@ export class TransactionController extends BaseControllerV1< readonly #incomingTransactionOptions: IncomingTransactionOptions; - private readonly incomingTransactionHelper: IncomingTransactionHelper; + private incomingTransactionHelper!: IncomingTransactionHelper; private readonly securityProviderRequest?: SecurityProviderRequest; readonly #pendingTransactionOptions: PendingTransactionOptions; - private readonly pendingTransactionTracker: PendingTransactionTracker; + private pendingTransactionTracker!: PendingTransactionTracker; + + readonly #onNetworkStateChange: ( + listener: (state: NetworkState) => void, + ) => void; + + readonly #isMultichainEnabled: boolean; + + readonly #incomingTransactions: IncomingTransactionOptions; + + readonly #getNetworkClientRegistry: NetworkController['getNetworkClientRegistry']; private readonly signAbortCallbacks: Map void> = new Map(); @@ -427,7 +439,7 @@ export class TransactionController extends BaseControllerV1< return { registryMethod, parsedRegistryMethod }; } - #multichainTrackingHelper: MultichainTrackingHelper; + #multichainTrackingHelper!: MultichainTrackingHelper; /** * EventEmitter instance used to listen to specific transactional events @@ -492,8 +504,7 @@ 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)); @@ -521,16 +532,36 @@ export class TransactionController extends BaseControllerV1< this.publish = hooks?.publish ?? (() => Promise.resolve({ transactionHash: undefined })); + this.#onNetworkStateChange = onNetworkStateChange; + this.#isMultichainEnabled = isMultichainEnabled; + this.#incomingTransactions = incomingTransactions; + this.#getNetworkClientRegistry = getNetworkClientRegistry; + } + + /** + * Initializes `registry`, `nonceTracker`, `#multichainTrackingHelper`, + * `incomingTransactionHelper`, `pendingTransactionTracker`, and passes a + * callback to `onNetworkStateChange`. + * + * This relies on the provider and block tracker being defined which may not + * have been the case at the time the Transaction Controller was instantiated. + */ + initialization() { + const { provider, blockTracker } = this.getProviderAndBlockTracker() + + // @ts-expect-error the type in eth-method-registry is inappropriate and should be changed + this.registry = new MethodRegistry({ provider }); + this.nonceTracker = this.#createNonceTracker({ provider, blockTracker, }); this.#multichainTrackingHelper = new MultichainTrackingHelper({ - isMultichainEnabled, + isMultichainEnabled: this.#isMultichainEnabled, provider, nonceTracker: this.nonceTracker, - incomingTransactionOptions: incomingTransactions, + incomingTransactionOptions: this.#incomingTransactions, findNetworkClientIdByChainId: (chainId: Hex) => { return this.messagingSystem.call( `NetworkController:findNetworkClientIdByChainId`, @@ -543,7 +574,7 @@ export class TransactionController extends BaseControllerV1< networkClientId, ); }) as NetworkController['getNetworkClientById'], - getNetworkClientRegistry, + getNetworkClientRegistry: this.#getNetworkClientRegistry, removeIncomingTransactionHelperListeners: this.#removeIncomingTransactionHelperListeners.bind(this), removePendingTransactionTrackerListeners: @@ -564,7 +595,7 @@ export class TransactionController extends BaseControllerV1< const etherscanRemoteTransactionSource = new EtherscanRemoteTransactionSource({ - includeTokenTransfers: incomingTransactions.includeTokenTransfers, + includeTokenTransfers: this.#incomingTransactions.includeTokenTransfers, }); this.incomingTransactionHelper = this.#createIncomingTransactionHelper({ @@ -604,7 +635,7 @@ export class TransactionController extends BaseControllerV1< // TODO once v2 is merged make sure this only runs when // selectedNetworkClientId changes - onNetworkStateChange(() => { + this.#onNetworkStateChange(() => { log('Detected network change', this.getChainId()); this.pendingTransactionTracker.startIfPendingTransactions(); this.onBootCleanup(); @@ -613,6 +644,30 @@ export class TransactionController extends BaseControllerV1< this.onBootCleanup(); } + /** + * Get the relevant provider and blockTracker instance. + * + * @returns Provider and BlockTracker instances. + */ + getProviderAndBlockTracker(): { provider: Provider, blockTracker: BlockTracker } { + const selectedNetworkClientId = this.getNetworkState().selectedNetworkClientId; + const networkClient = this.messagingSystem.call( + `NetworkController:getNetworkClientById`, + selectedNetworkClientId, + ) + const provider = networkClient.provider; + + if (provider === undefined) { + const MISSING_PROVIDER_ERROR = 'TransactionController failed to set the provider correctly. A provider must be set for this method to be available'; + + throw new Error(MISSING_PROVIDER_ERROR); + } + + const blockTracker = networkClient.blockTracker; + + return { provider, blockTracker }; + } + /** * Stops polling and removes listeners to prepare the controller for garbage collection. */