diff --git a/packages/transaction-controller/CHANGELOG.md b/packages/transaction-controller/CHANGELOG.md index cc9bb93dec7..30d5367adc1 100644 --- a/packages/transaction-controller/CHANGELOG.md +++ b/packages/transaction-controller/CHANGELOG.md @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Expose `confirmExternalTransaction`, `getNonceLock`, `getTransactions`, and `updateTransaction` actions through the messenger ([#6615](https://github.com/MetaMask/core/pull/6615)) + - Like other action methods, they are callable as `TransactionController:*` + - Also add associated types: + - `TransactionControllerConfirmExternalTransactionAction` + - `TransactionControllerGetNonceLockAction` + - `TransactionControllerGetTransactionsAction` + - `TransactionControllerUpdateTransactionAction` + ### Changed - Bump `@metamask/controller-utils` from `^11.12.0` to `^11.13.0` ([#6620](https://github.com/MetaMask/core/pull/6620)) diff --git a/packages/transaction-controller/src/TransactionController.test.ts b/packages/transaction-controller/src/TransactionController.test.ts index 80405a42225..d12e63d3d8b 100644 --- a/packages/transaction-controller/src/TransactionController.test.ts +++ b/packages/transaction-controller/src/TransactionController.test.ts @@ -8044,4 +8044,171 @@ describe('TransactionController', () => { `); }); }); + + describe('messenger actions', () => { + describe('TransactionController:confirmExternalTransaction', () => { + it('calls confirmExternalTransaction method via messenger', async () => { + const { controller, messenger } = setupController(); + const externalTransactionToConfirm = { + id: '1', + chainId: toHex(1), + networkClientId: NETWORK_CLIENT_ID_MOCK, + time: 123456789, + status: TransactionStatus.confirmed as const, + txParams: { + gasUsed: undefined, + from: ACCOUNT_MOCK, + to: ACCOUNT_2_MOCK, + }, + }; + const externalTransactionReceipt = { + gasUsed: '0x5208', + }; + const externalBaseFeePerGas = '0x14'; + + await messenger.call( + 'TransactionController:confirmExternalTransaction', + externalTransactionToConfirm, + externalTransactionReceipt, + externalBaseFeePerGas, + ); + + expect(controller.state.transactions).toHaveLength(1); + expect(controller.state.transactions[0]).toMatchObject({ + id: '1', + status: TransactionStatus.confirmed, + txParams: { + from: ACCOUNT_MOCK, + to: ACCOUNT_2_MOCK, + }, + }); + }); + }); + + describe('TransactionController:getNonceLock', () => { + it('calls getNonceLock method via messenger', async () => { + const { messenger } = setupController(); + + const result = await messenger.call( + 'TransactionController:getNonceLock', + ACCOUNT_MOCK, + NETWORK_CLIENT_ID_MOCK, + ); + + expect(result).toMatchObject({ + nextNonce: NONCE_MOCK, + releaseLock: expect.any(Function), + }); + expect(getNonceLockSpy).toHaveBeenCalledWith( + ACCOUNT_MOCK, + NETWORK_CLIENT_ID_MOCK, + ); + }); + }); + + describe('TransactionController:getTransactions', () => { + it('calls getTransactions method via messenger with no parameters', async () => { + const { messenger } = setupController({ + options: { + state: { + transactions: [ + { + ...TRANSACTION_META_MOCK, + txParams: { + from: ACCOUNT_MOCK, + to: ACCOUNT_2_MOCK, + }, + }, + ], + }, + }, + }); + + const result = await messenger.call( + 'TransactionController:getTransactions', + ); + + expect(result).toHaveLength(1); + expect(result[0]).toMatchObject({ + txParams: { + from: ACCOUNT_MOCK, + to: ACCOUNT_2_MOCK, + }, + }); + }); + + it('calls getTransactions method via messenger with search criteria', async () => { + const { messenger } = setupController({ + options: { + state: { + transactions: [ + { + ...TRANSACTION_META_MOCK, + id: '1', + txParams: { + from: ACCOUNT_MOCK, + to: ACCOUNT_2_MOCK, + }, + }, + { + ...TRANSACTION_META_2_MOCK, + id: '2', + txParams: { + from: ACCOUNT_2_MOCK, + to: ACCOUNT_MOCK, + }, + }, + ], + }, + }, + }); + + const result = await messenger.call( + 'TransactionController:getTransactions', + { + searchCriteria: { + from: ACCOUNT_MOCK, + }, + }, + ); + + expect(result).toHaveLength(1); + expect(result[0].txParams.from).toBe(ACCOUNT_MOCK); + }); + }); + + describe('TransactionController:updateTransaction', () => { + it('calls updateTransaction method via messenger', async () => { + const transaction = { + ...TRANSACTION_META_MOCK, + txParams: { + from: ACCOUNT_MOCK, + to: ACCOUNT_2_MOCK, + }, + }; + const { controller, messenger } = setupController({ + options: { + state: { + transactions: [transaction], + }, + }, + }); + const updatedTransaction = { + ...transaction, + txParams: { + ...transaction.txParams, + value: '0x1', + }, + }; + + await messenger.call( + 'TransactionController:updateTransaction', + updatedTransaction, + 'Test update note', + ); + + expect(controller.state.transactions[0].txParams.value).toBe('0x1'); + }); + }); + }); }); diff --git a/packages/transaction-controller/src/TransactionController.ts b/packages/transaction-controller/src/TransactionController.ts index 07d928c4385..67b926459b0 100644 --- a/packages/transaction-controller/src/TransactionController.ts +++ b/packages/transaction-controller/src/TransactionController.ts @@ -308,13 +308,59 @@ export type TransactionControllerEstimateGasAction = { handler: TransactionController['estimateGas']; }; +/** + * Adds external provided transaction to state as confirmed transaction. + * + * @param transactionMeta - TransactionMeta to add transactions. + * @param transactionReceipt - TransactionReceipt of the external transaction. + * @param baseFeePerGas - Base fee per gas of the external transaction. + */ +export type TransactionControllerConfirmExternalTransactionAction = { + type: `${typeof controllerName}:confirmExternalTransaction`; + handler: TransactionController['confirmExternalTransaction']; +}; + +export type TransactionControllerGetNonceLockAction = { + type: `${typeof controllerName}:getNonceLock`; + handler: TransactionController['getNonceLock']; +}; + +/** + * Search transaction metadata for matching entries. + * + * @param opts - Options bag. + * @param opts.initialList - The transactions to search. Defaults to the current state. + * @param opts.limit - The maximum number of transactions to return. No limit by default. + * @param opts.searchCriteria - An object containing values or functions for transaction properties to filter transactions with. + * @returns An array of transactions matching the provided options. + */ +export type TransactionControllerGetTransactionsAction = { + type: `${typeof controllerName}:getTransactions`; + handler: TransactionController['getTransactions']; +}; + +/** + * Updates an existing transaction in state. + * + * @param transactionMeta - The new transaction to store in state. + * @param note - A note or update reason to include in the transaction history. + */ +export type TransactionControllerUpdateTransactionAction = { + type: `${typeof controllerName}:updateTransaction`; + handler: TransactionController['updateTransaction']; +}; + /** * The internal actions available to the TransactionController. */ export type TransactionControllerActions = + | TransactionControllerConfirmExternalTransactionAction | TransactionControllerEstimateGasAction + | TransactionControllerGetNonceLockAction | TransactionControllerGetStateAction - | TransactionControllerUpdateCustodialTransactionAction; + | TransactionControllerGetTransactionsAction + | TransactionControllerUpdateCustodialTransactionAction + | TransactionControllerUpdateTransactionAction; /** * Configuration options for the PendingTransactionTracker @@ -4416,15 +4462,35 @@ export class TransactionController extends BaseController< } #registerActionHandlers(): void { + this.messagingSystem.registerActionHandler( + `${controllerName}:confirmExternalTransaction`, + this.confirmExternalTransaction.bind(this), + ); + this.messagingSystem.registerActionHandler( `${controllerName}:estimateGas`, this.estimateGas.bind(this), ); + this.messagingSystem.registerActionHandler( + `${controllerName}:getNonceLock`, + this.getNonceLock.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `${controllerName}:getTransactions`, + this.getTransactions.bind(this), + ); + this.messagingSystem.registerActionHandler( `${controllerName}:updateCustodialTransaction`, this.updateCustodialTransaction.bind(this), ); + + this.messagingSystem.registerActionHandler( + `${controllerName}:updateTransaction`, + this.updateTransaction.bind(this), + ); } #deleteTransaction(transactionId: string) { diff --git a/packages/transaction-controller/src/index.ts b/packages/transaction-controller/src/index.ts index c435c57a832..fcd4e123f3e 100644 --- a/packages/transaction-controller/src/index.ts +++ b/packages/transaction-controller/src/index.ts @@ -2,9 +2,12 @@ export type { MethodData, Result, TransactionControllerActions, + TransactionControllerConfirmExternalTransactionAction, TransactionControllerEvents, TransactionControllerEstimateGasAction, + TransactionControllerGetNonceLockAction, TransactionControllerGetStateAction, + TransactionControllerGetTransactionsAction, TransactionControllerIncomingTransactionsReceivedEvent, TransactionControllerPostTransactionBalanceUpdatedEvent, TransactionControllerSpeedupTransactionAddedEvent, @@ -23,6 +26,7 @@ export type { TransactionControllerTransactionSubmittedEvent, TransactionControllerUnapprovedTransactionAddedEvent, TransactionControllerUpdateCustodialTransactionAction, + TransactionControllerUpdateTransactionAction, TransactionControllerMessenger, TransactionControllerOptions, } from './TransactionController';