From 8aee5bad21c5a5ef0d4dc19f0694c5f1b4665201 Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Thu, 27 Nov 2025 12:12:22 +0000 Subject: [PATCH 1/3] Fail required transactions on startup --- .../src/TransactionController.test.ts | 69 ++++++++++++++++++- .../src/TransactionController.ts | 25 ++++++- .../src/utils/gas-fee-tokens.test.ts | 5 ++ .../src/utils/gas-fee-tokens.ts | 5 +- 4 files changed, 101 insertions(+), 3 deletions(-) diff --git a/packages/transaction-controller/src/TransactionController.test.ts b/packages/transaction-controller/src/TransactionController.test.ts index aaa202a217d..1d0bec05a34 100644 --- a/packages/transaction-controller/src/TransactionController.test.ts +++ b/packages/transaction-controller/src/TransactionController.test.ts @@ -1131,6 +1131,72 @@ describe('TransactionController', () => { expect(transactions[5].status).toBe(TransactionStatus.failed); }); + it('fails required transactions of approved and signed transactions', async () => { + const mockTransactionMeta = { + from: ACCOUNT_MOCK, + txParams: { + from: ACCOUNT_MOCK, + to: ACCOUNT_2_MOCK, + }, + }; + const mockedTransactions = [ + { + id: '123', + history: [{ ...mockTransactionMeta, id: '123' }], + chainId: toHex(5), + status: TransactionStatus.approved, + requiredTransactionIds: ['222', '333'], + ...mockTransactionMeta, + }, + { + id: '111', + history: [{ ...mockTransactionMeta, id: '111' }], + chainId: toHex(5), + status: TransactionStatus.signed, + ...mockTransactionMeta, + }, + { + id: '222', + history: [{ ...mockTransactionMeta, id: '222' }], + chainId: toHex(1), + status: TransactionStatus.confirmed, + ...mockTransactionMeta, + }, + { + id: '333', + history: [{ ...mockTransactionMeta, id: '333' }], + chainId: toHex(16), + status: TransactionStatus.submitted, + ...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.map((t) => [t.id, t.status])).toStrictEqual([ + ['333', TransactionStatus.failed], + ['222', TransactionStatus.confirmed], + ['111', TransactionStatus.failed], + ['123', TransactionStatus.failed], + ]); + }); + it('removes unapproved transactions', async () => { const mockTransactionMeta = { from: ACCOUNT_MOCK, @@ -8498,7 +8564,7 @@ describe('TransactionController', () => { expect(result).toStrictEqual([GAS_FEE_TOKEN_MOCK]); }); - it('includes delegation address in request', async () => { + it('includes delegation address and isExternalSign in request', async () => { const { messenger } = setupController(); getGasFeeTokensMock.mockResolvedValueOnce({ @@ -8520,6 +8586,7 @@ describe('TransactionController', () => { expect.objectContaining({ transactionMeta: expect.objectContaining({ delegationAddress: ACCOUNT_2_MOCK, + isExternalSign: true, }), }), ); diff --git a/packages/transaction-controller/src/TransactionController.ts b/packages/transaction-controller/src/TransactionController.ts index 0d76fd01814..aaf8812d6fa 100644 --- a/packages/transaction-controller/src/TransactionController.ts +++ b/packages/transaction-controller/src/TransactionController.ts @@ -2996,6 +2996,27 @@ export class TransactionController extends BaseController< transactionMeta, new Error('Transaction incomplete at startup'), ); + + const requiredTransactionIds = + transactionMeta.requiredTransactionIds ?? []; + + for (const requiredTransactionId of requiredTransactionIds) { + const requiredTransactionMeta = this.#getTransaction( + requiredTransactionId, + ); + + if ( + !requiredTransactionMeta || + this.#isFinalState(requiredTransactionMeta.status) + ) { + continue; + } + + this.#failTransaction( + requiredTransactionMeta, + new Error('Parent transaction incomplete at startup'), + ); + } } } @@ -3422,7 +3443,8 @@ export class TransactionController extends BaseController< return ( status === TransactionStatus.rejected || status === TransactionStatus.confirmed || - status === TransactionStatus.failed + status === TransactionStatus.failed || + status === TransactionStatus.dropped ); } @@ -4737,6 +4759,7 @@ export class TransactionController extends BaseController< const transaction = { chainId, delegationAddress, + isExternalSign: true, txParams: { data, from, diff --git a/packages/transaction-controller/src/utils/gas-fee-tokens.test.ts b/packages/transaction-controller/src/utils/gas-fee-tokens.test.ts index cd89a1eafc5..de2908ddfd5 100644 --- a/packages/transaction-controller/src/utils/gas-fee-tokens.test.ts +++ b/packages/transaction-controller/src/utils/gas-fee-tokens.test.ts @@ -431,6 +431,11 @@ describe('Gas Fee Tokens Utils', () => { await checkGasFeeTokenBeforePublish(request); expect(request.fetchGasFeeTokens).toHaveBeenCalledTimes(1); + expect(request.fetchGasFeeTokens).toHaveBeenCalledWith( + expect.objectContaining({ + isExternalSign: true, + }), + ); }); it('sets external sign to true if gas fee token found', async () => { diff --git a/packages/transaction-controller/src/utils/gas-fee-tokens.ts b/packages/transaction-controller/src/utils/gas-fee-tokens.ts index 1f7d6e94852..c1cec4736a9 100644 --- a/packages/transaction-controller/src/utils/gas-fee-tokens.ts +++ b/packages/transaction-controller/src/utils/gas-fee-tokens.ts @@ -166,7 +166,10 @@ export async function checkGasFeeTokenBeforePublish({ return; } - const gasFeeTokens = await fetchGasFeeTokens(transaction); + const gasFeeTokens = await fetchGasFeeTokens({ + ...transaction, + isExternalSign: true, + }); updateTransaction(transaction.id, (tx) => { tx.gasFeeTokens = gasFeeTokens; From c11573e33ec3899e5c61b2b22b178fa68ee6b851 Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Thu, 27 Nov 2025 14:40:50 +0000 Subject: [PATCH 2/3] Update changelog --- packages/transaction-controller/CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/transaction-controller/CHANGELOG.md b/packages/transaction-controller/CHANGELOG.md index 28f942eb7d5..88d1ffd626d 100644 --- a/packages/transaction-controller/CHANGELOG.md +++ b/packages/transaction-controller/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Fail required transactions of any approved and signed transactions during initialisation ([#7251](https://github.com/MetaMask/core/pull/7251)) + - Include `isExternalSign` when fetching gas fee tokens in messenger action or before publish check. + ## [62.3.0] ### Changed From 77d67b84b44d34ffacdacb99d3524aca6840b0a8 Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Thu, 27 Nov 2025 14:45:23 +0000 Subject: [PATCH 3/3] Fix linting --- packages/transaction-controller/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/transaction-controller/CHANGELOG.md b/packages/transaction-controller/CHANGELOG.md index 88d1ffd626d..76552eb66be 100644 --- a/packages/transaction-controller/CHANGELOG.md +++ b/packages/transaction-controller/CHANGELOG.md @@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fail required transactions of any approved and signed transactions during initialisation ([#7251](https://github.com/MetaMask/core/pull/7251)) - - Include `isExternalSign` when fetching gas fee tokens in messenger action or before publish check. + - Include `isExternalSign` when fetching gas fee tokens in messenger action or before publish check. ## [62.3.0]