From d2e530b1c777ba52de61c3ad31ad85001908c150 Mon Sep 17 00:00:00 2001 From: Vinicius Stevam Date: Fri, 5 Dec 2025 14:08:16 +0000 Subject: [PATCH 01/11] refactor: remove transaction history and sendFlowHistory from TransactionController --- .../src/TransactionController.test.ts | 429 +----------------- .../src/TransactionController.ts | 267 ++--------- .../TransactionControllerIntegration.test.ts | 9 +- .../helpers/IncomingTransactionHelper.test.ts | 2 - .../src/helpers/IncomingTransactionHelper.ts | 12 +- packages/transaction-controller/src/index.ts | 7 - packages/transaction-controller/src/types.ts | 51 --- .../src/utils/history.test.ts | 401 ---------------- .../src/utils/history.ts | 216 --------- 9 files changed, 39 insertions(+), 1355 deletions(-) delete mode 100644 packages/transaction-controller/src/utils/history.test.ts delete mode 100644 packages/transaction-controller/src/utils/history.ts diff --git a/packages/transaction-controller/src/TransactionController.test.ts b/packages/transaction-controller/src/TransactionController.test.ts index 2e2d2a5e991..8f2dc665556 100644 --- a/packages/transaction-controller/src/TransactionController.test.ts +++ b/packages/transaction-controller/src/TransactionController.test.ts @@ -65,7 +65,6 @@ import type { TransactionMeta, DappSuggestedGasFees, TransactionParams, - TransactionHistoryEntry, TransactionError, GasFeeFlow, GasFeeFlowResponse, @@ -691,8 +690,6 @@ describe('TransactionController', () => { messenger: givenRestrictedMessenger, ...otherOptions }: Partial = { - disableHistory: false, - disableSendFlowHistory: false, disableSwaps: false, getCurrentNetworkEIP1559Compatibility: async () => false, getNetworkState: () => networkState, @@ -704,7 +701,6 @@ describe('TransactionController', () => { isEIP7702GasFeeTokensEnabled: isEIP7702GasFeeTokensEnabledMock, publicKeyEIP7702: '0x1234', sign: signMock, - transactionHistoryLimit: 40, ...givenOptions, }; @@ -1062,42 +1058,36 @@ describe('TransactionController', () => { const mockedTransactions = [ { id: '123', - history: [{ ...mockTransactionMeta, id: '123' }], chainId: toHex(5), status: TransactionStatus.approved, ...mockTransactionMeta, }, { id: '456', - history: [{ ...mockTransactionMeta, id: '456' }], chainId: toHex(1), status: TransactionStatus.approved, ...mockTransactionMeta, }, { id: '789', - history: [{ ...mockTransactionMeta, id: '789' }], chainId: toHex(16), status: TransactionStatus.approved, ...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.signed, ...mockTransactionMeta, }, { id: '333', - history: [{ ...mockTransactionMeta, id: '333' }], chainId: toHex(16), status: TransactionStatus.signed, ...mockTransactionMeta, @@ -1141,7 +1131,6 @@ describe('TransactionController', () => { const mockedTransactions = [ { id: '123', - history: [{ ...mockTransactionMeta, id: '123' }], chainId: toHex(5), status: TransactionStatus.approved, requiredTransactionIds: ['222', '333'], @@ -1149,21 +1138,18 @@ describe('TransactionController', () => { }, { 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, @@ -1189,10 +1175,10 @@ describe('TransactionController', () => { 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], + ['111', TransactionStatus.failed], + ['222', TransactionStatus.confirmed], + ['333', TransactionStatus.failed], ]); }); @@ -1208,21 +1194,18 @@ describe('TransactionController', () => { const mockedTransactions = [ { id: '123', - history: [{ ...mockTransactionMeta, id: '123' }], chainId: toHex(5), status: TransactionStatus.unapproved, ...mockTransactionMeta, }, { id: '456', - history: [{ ...mockTransactionMeta, id: '456' }], chainId: toHex(1), status: TransactionStatus.unapproved, ...mockTransactionMeta, }, { id: '789', - history: [{ ...mockTransactionMeta, id: '789' }], chainId: toHex(16), status: TransactionStatus.unapproved, ...mockTransactionMeta, @@ -1582,13 +1565,6 @@ describe('TransactionController', () => { operator: '0x92a3b9773b1763efa556f55ccbeb20441962d9b2', }, }; - const mockSendFlowHistory = [ - { - entry: - 'sendFlow - user selected transfer to my accounts on recipient screen', - timestamp: 1650663928211, - }, - ]; await controller.addTransaction( { from: ACCOUNT_MOCK, @@ -1602,7 +1578,6 @@ describe('TransactionController', () => { deviceConfirmedOn: mockDeviceConfirmedOn, origin: mockOrigin, securityAlertResponse: mockSecurityAlertResponse, - sendFlowHistory: mockSendFlowHistory, networkClientId: NETWORK_CLIENT_ID_MOCK, }, ); @@ -1624,9 +1599,6 @@ describe('TransactionController', () => { expect(transactionMeta.securityAlertResponse).toStrictEqual( mockSecurityAlertResponse, ); - expect(controller.state.transactions[0].sendFlowHistory).toStrictEqual( - mockSendFlowHistory, - ); expect(updateFirstTimeInteractionMock).toHaveBeenCalledTimes(1); }); @@ -1732,52 +1704,6 @@ describe('TransactionController', () => { }); }); - it('generates initial history', async () => { - const { controller } = setupController(); - - await controller.addTransaction( - { - from: ACCOUNT_MOCK, - to: ACCOUNT_MOCK, - }, - { - networkClientId: NETWORK_CLIENT_ID_MOCK, - }, - ); - - const expectedInitialSnapshot = { - actionId: undefined, - assetsFiatValues: undefined, - batchId: undefined, - chainId: expect.any(String), - dappSuggestedGasFees: undefined, - deviceConfirmedOn: undefined, - disableGasBuffer: undefined, - id: expect.any(String), - isFirstTimeInteraction: undefined, - isGasFeeIncluded: undefined, - isGasFeeSponsored: undefined, - isGasFeeTokenIgnoredIfBalance: false, - nestedTransactions: undefined, - networkClientId: NETWORK_CLIENT_ID_MOCK, - origin: undefined, - securityAlertResponse: undefined, - selectedGasFeeToken: undefined, - sendFlowHistory: expect.any(Array), - status: TransactionStatus.unapproved as const, - time: expect.any(Number), - txParams: expect.anything(), - userEditedGasLimit: false, - type: TransactionType.simpleSend, - verifiedOnBlockchain: expect.any(Boolean), - }; - - // Expect initial snapshot to be in place - expect(controller.state.transactions[0].history).toStrictEqual([ - expectedInitialSnapshot, - ]); - }); - describe('adds dappSuggestedGasFees to transaction', () => { it.each([ ['origin is MM', ORIGIN_METAMASK], @@ -4320,38 +4246,6 @@ describe('TransactionController', () => { expect(transactions[1].originalGasEstimate).toBe('0x1'); }); - it('allows transaction count to exceed txHistorylimit', async () => { - const { controller } = setupController({ - options: { - transactionHistoryLimit: 1, - }, - messengerOptions: { - addTransactionApprovalRequest: { - state: 'approved', - }, - }, - }); - - const { transactionMeta, result } = await controller.addTransaction( - { - from: ACCOUNT_MOCK, - nonce: '1111111', - gas: '0x0', - gasPrice: '0x50fd51da', - to: ACCOUNT_MOCK, - value: '0x0', - }, - { - networkClientId: NETWORK_CLIENT_ID_MOCK, - }, - ); - - await result; - await controller.speedUpTransaction(transactionMeta.id); - - expect(controller.state.transactions).toHaveLength(2); - }); - it('publishes transaction events', async () => { const { controller, messenger } = setupController({ network: MOCK_LINEA_MAINNET_NETWORK, @@ -4508,81 +4402,6 @@ describe('TransactionController', () => { ); }); - it('generates initial history', async () => { - const { controller } = setupController(); - - const externalTransactionToConfirm = { - from: ACCOUNT_MOCK, - to: ACCOUNT_2_MOCK, - id: '1', - chainId: toHex(1), - networkClientId: NETWORK_CLIENT_ID_MOCK, - status: TransactionStatus.confirmed as const, - time: 123456789, - txParams: { - gasUsed: undefined, - from: ACCOUNT_MOCK, - to: ACCOUNT_2_MOCK, - }, - }; - - const externalTransactionReceipt = { - gasUsed: '0x5208', - }; - - const externalBaseFeePerGas = '0x14'; - - await controller.confirmExternalTransaction( - externalTransactionToConfirm, - externalTransactionReceipt, - externalBaseFeePerGas, - ); - - const expectedInitialSnapshot = { - chainId: '0x1', - from: ACCOUNT_MOCK, - id: '1', - networkClientId: NETWORK_CLIENT_ID_MOCK, - time: 123456789, - status: TransactionStatus.confirmed as const, - to: ACCOUNT_2_MOCK, - txParams: { - from: ACCOUNT_MOCK, - to: ACCOUNT_2_MOCK, - gasUsed: undefined, - }, - }; - - // Expect initial snapshot to be the first history item - expect(controller.state.transactions[0]?.history?.[0]).toStrictEqual( - expectedInitialSnapshot, - ); - // Expect modification history to be present - expect(controller.state.transactions[0]?.history?.[1]).toStrictEqual([ - { - note: expect.any(String), - op: 'remove', - path: '/txParams/gasUsed', - timestamp: expect.any(Number), - }, - { - op: 'add', - path: '/txParams/value', - value: '0x0', - }, - { - op: 'add', - path: '/txReceipt', - value: expect.anything(), - }, - { - op: 'add', - path: '/baseFeePerGas', - value: expect.any(String), - }, - ]); - }); - it('marks local transactions with the same nonce and chainId as status dropped and defines replacedBy properties', async () => { const externalTransactionId = '1'; const externalTransactionHash = '0x1'; @@ -4614,7 +4433,6 @@ describe('TransactionController', () => { const { controller, messenger } = setupController({ options: { - disableHistory: true, state: { transactions: [ // Local unapproved transaction with the same chainId and nonce @@ -4854,194 +4672,6 @@ describe('TransactionController', () => { }); }); - describe('updateTransactionSendFlowHistory', () => { - it('appends sendFlowHistory entries to transaction meta', async () => { - const { controller } = setupController(); - const mockSendFlowHistory = [ - { - entry: - 'sendFlow - user selected transfer to my accounts on recipient screen', - timestamp: 1650663928211, - }, - ]; - await controller.addTransaction( - { - from: ACCOUNT_MOCK, - to: ACCOUNT_MOCK, - }, - { networkClientId: NETWORK_CLIENT_ID_MOCK }, - ); - const addedTxId = controller.state.transactions[0].id; - controller.updateTransactionSendFlowHistory( - addedTxId, - 0, - mockSendFlowHistory, - ); - - expect(controller.state.transactions[0].sendFlowHistory).toStrictEqual( - mockSendFlowHistory, - ); - }); - - it('appends sendFlowHistory entries to existing entries in transaction meta', async () => { - const { controller } = setupController(); - const mockSendFlowHistory = [ - { - entry: - 'sendFlow - user selected transfer to my accounts on recipient screen', - timestamp: 1650663928211, - }, - ]; - const mockExistingSendFlowHistory = [ - { - entry: 'sendFlow - user selected transfer to my accounts', - timestamp: 1650663928210, - }, - ]; - await controller.addTransaction( - { - from: ACCOUNT_MOCK, - to: ACCOUNT_MOCK, - }, - { - sendFlowHistory: mockExistingSendFlowHistory, - networkClientId: NETWORK_CLIENT_ID_MOCK, - }, - ); - const addedTxId = controller.state.transactions[0].id; - controller.updateTransactionSendFlowHistory( - addedTxId, - 1, - mockSendFlowHistory, - ); - - expect(controller.state.transactions[0].sendFlowHistory).toStrictEqual([ - ...mockExistingSendFlowHistory, - ...mockSendFlowHistory, - ]); - }); - - it('doesnt append if current sendFlowHistory lengths doesnt match', async () => { - const { controller } = setupController(); - const mockSendFlowHistory = [ - { - entry: - 'sendFlow - user selected transfer to my accounts on recipient screen', - timestamp: 1650663928211, - }, - ]; - await controller.addTransaction( - { - from: ACCOUNT_MOCK, - to: ACCOUNT_MOCK, - }, - { - networkClientId: NETWORK_CLIENT_ID_MOCK, - }, - ); - const addedTxId = controller.state.transactions[0].id; - controller.updateTransactionSendFlowHistory( - addedTxId, - 5, - mockSendFlowHistory, - ); - - expect(controller.state.transactions[0].sendFlowHistory).toStrictEqual( - [], - ); - }); - - it('throws if sendFlowHistory persistence is disabled', async () => { - const { controller } = setupController({ - options: { disableSendFlowHistory: true }, - }); - const mockSendFlowHistory = [ - { - entry: - 'sendFlow - user selected transfer to my accounts on recipient screen', - timestamp: 1650663928211, - }, - ]; - await controller.addTransaction( - { - from: ACCOUNT_MOCK, - to: ACCOUNT_MOCK, - }, - { - networkClientId: NETWORK_CLIENT_ID_MOCK, - }, - ); - const addedTxId = controller.state.transactions[0].id; - expect(() => - controller.updateTransactionSendFlowHistory( - addedTxId, - 0, - mockSendFlowHistory, - ), - ).toThrow( - 'Send flow history is disabled for the current transaction controller', - ); - }); - - it('throws if transactionMeta is not found', async () => { - const { controller } = setupController(); - const mockSendFlowHistory = [ - { - entry: - 'sendFlow - user selected transfer to my accounts on recipient screen', - timestamp: 1650663928211, - }, - ]; - expect(() => - controller.updateTransactionSendFlowHistory( - 'foo', - 0, - mockSendFlowHistory, - ), - ).toThrow( - 'Cannot update send flow history as no transaction metadata found', - ); - }); - - it('throws if the transaction is not unapproved status', async () => { - const { controller } = setupController({ - options: { - state: { - transactions: [ - { - id: 'foo', - chainId: toHex(5), - networkClientId: NETWORK_CLIENT_ID_MOCK, - hash: '1337', - status: TransactionStatus.submitted as const, - time: 123456789, - txParams: { - from: MOCK_PREFERENCES.state.selectedAddress, - }, - }, - ], - }, - }, - }); - const mockSendFlowHistory = [ - { - entry: - 'sendFlow - user selected transfer to my accounts on recipient screen', - timestamp: 1650663928211, - }, - ]; - expect(() => - controller.updateTransactionSendFlowHistory( - 'foo', - 0, - mockSendFlowHistory, - ), - ) - .toThrow(`TransactionsController: Can only call updateTransactionSendFlowHistory on an unapproved transaction. - Current tx status: submitted`); - }); - }); - describe('clearUnapprovedTransactions', () => { it('clears unapproved transactions', async () => { const firstUnapprovedTxId = '1'; @@ -5124,23 +4754,6 @@ describe('TransactionController', () => { ]); }); - it('limits max transactions when adding to state', async () => { - const { controller } = setupController({ - options: { transactionHistoryLimit: 1 }, - }); - - // TODO: Replace `any` with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - await (incomingTransactionHelperMock.hub.on as any).mock.calls[0][1]([ - TRANSACTION_META_MOCK, - TRANSACTION_META_2_MOCK, - ]); - - expect(controller.state.transactions).toStrictEqual([ - { ...TRANSACTION_META_2_MOCK, networkClientId: NETWORK_CLIENT_ID_MOCK }, - ]); - }); - it('publishes TransactionController:incomingTransactionsReceived', async () => { const listener = jest.fn(); @@ -5262,10 +4875,6 @@ describe('TransactionController', () => { networkClientId: NETWORK_CLIENT_ID_MOCK, time: 123456789, status: TransactionStatus.unapproved as const, - history: [ - {} as TransactionMeta, - ...([{}] as TransactionHistoryEntry[]), - ], txParams: { from: ACCOUNT_MOCK, to: ACCOUNT_2_MOCK, @@ -5329,10 +4938,6 @@ describe('TransactionController', () => { networkClientId: NETWORK_CLIENT_ID_MOCK, time: 123456789, status: TransactionStatus.unapproved as const, - history: [ - {} as TransactionMeta, - ...([{}] as TransactionHistoryEntry[]), - ], txParams: { from: ACCOUNT_MOCK, to: ACCOUNT_2_MOCK, @@ -5681,7 +5286,6 @@ describe('TransactionController', () => { { id: transactionId, status: TransactionStatus.unapproved, - history: [{}], txParams: { from: ACCOUNT_MOCK, to: ACCOUNT_2_MOCK, @@ -5880,7 +5484,6 @@ describe('TransactionController', () => { it('sets status to dropped on transaction-dropped event', async () => { const { controller } = setupController({ options: { - disableHistory: true, state: { transactions: [{ ...TRANSACTION_META_MOCK }], }, @@ -5901,7 +5504,6 @@ describe('TransactionController', () => { const statusUpdatedEventListener = jest.fn(); const { controller, messenger } = setupController({ options: { - disableHistory: true, state: { transactions: [{ ...TRANSACTION_META_MOCK }], }, @@ -5947,7 +5549,6 @@ describe('TransactionController', () => { state: { transactions: [TRANSACTION_META_MOCK], }, - disableHistory: true, }, }); @@ -6212,7 +5813,6 @@ describe('TransactionController', () => { expect.objectContaining({ txParams: expect.objectContaining(paramsMock), }), - 'TransactionController#signTransaction - Update after sign', ); expect(transactionMeta.status).toBe(TransactionStatus.approved); @@ -6279,7 +5879,6 @@ describe('TransactionController', () => { expect.objectContaining({ txParams: expect.objectContaining(paramsMock), }), - 'TransactionController#signTransaction - Update after sign', ); }); @@ -6415,14 +6014,6 @@ describe('TransactionController', () => { }); describe('updateSecurityAlertResponse', () => { - const mockSendFlowHistory = [ - { - entry: - 'sendFlow - user selected transfer to my accounts on recipient screen', - timestamp: 1650663928211, - }, - ]; - it('add securityAlertResponse to transaction meta', async () => { const transactionMeta = TRANSACTION_META_MOCK; const { controller } = setupController({ @@ -6509,10 +6100,7 @@ describe('TransactionController', () => { from: ACCOUNT_MOCK, to: ACCOUNT_2_MOCK, }, - history: mockSendFlowHistory, - // TODO: Replace `any` with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any, + } as unknown as TransactionMeta, ], }, }, @@ -6552,7 +6140,6 @@ describe('TransactionController', () => { }; transactionMeta = { ...baseTransaction, - history: [{ ...baseTransaction }], }; }); @@ -6805,7 +6392,7 @@ describe('TransactionController', () => { it('throws if custodial transaction does not exists', async () => { const nonExistentId = 'nonExistentId'; - const newStatus = TransactionStatus.approved as const; + const newStatus = TransactionStatus.approved; const { controller } = setupController(); expect(() => @@ -6819,7 +6406,7 @@ describe('TransactionController', () => { }); it('throws if status is invalid', async () => { - const newStatus = TransactionStatus.approved as const; + const newStatus = TransactionStatus.approved; const { controller } = setupController({ options: { state: { @@ -7352,7 +6939,6 @@ describe('TransactionController', () => { }; const transactionMeta: TransactionMeta = { ...baseTransaction, - history: [{ ...baseTransaction }], }; it('updates editable params and returns updated transaction metadata', async () => { @@ -7429,7 +7015,6 @@ describe('TransactionController', () => { expect.objectContaining({ transactionMeta: { ...updatedTransaction, - history: expect.any(Array), }, }), ); @@ -7767,7 +7352,6 @@ describe('TransactionController', () => { { id: transactionId, status: TransactionStatus.unapproved, - history: [{}], txParams: { from: ACCOUNT_MOCK, to: ACCOUNT_2_MOCK, @@ -8533,7 +8117,6 @@ describe('TransactionController', () => { 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 9f2245a8027..1ee002df02b 100644 --- a/packages/transaction-controller/src/TransactionController.ts +++ b/packages/transaction-controller/src/TransactionController.ts @@ -89,7 +89,6 @@ import type { Layer1GasFeeFlow, SavedGasFees, SecurityProviderRequest, - SendFlowHistoryEntry, TransactionParams, TransactionMeta, TransactionReceipt, @@ -144,10 +143,6 @@ import { } from './utils/gas-fee-tokens'; import { updateGasFees } from './utils/gas-fees'; import { getGasFeeFlow } from './utils/gas-flow'; -import { - addInitialHistorySnapshot, - updateTransactionHistory, -} from './utils/history'; import { getTransactionLayer1GasFee, updateTransactionLayer1GasFee, @@ -417,12 +412,6 @@ export type PendingTransactionOptions = { /** TransactionController constructor options. */ export type TransactionControllerOptions = { - /** Whether to disable storing history in transaction metadata. */ - disableHistory: boolean; - - /** Explicitly disable transaction metadata history. */ - disableSendFlowHistory: boolean; - /** Whether to disable additional processing on swaps transactions. */ disableSwaps: boolean; @@ -510,9 +499,6 @@ export type TransactionControllerOptions = { testGasFeeFlows?: boolean; trace?: TraceCallback; - /** Transaction history limit. */ - transactionHistoryLimit: number; - /** The controller hooks. */ hooks: { /** Additional logic to execute after adding a transaction. */ @@ -878,10 +864,6 @@ export class TransactionController extends BaseController< readonly #isFirstTimeInteractionEnabled: () => boolean; - readonly #isHistoryDisabled: boolean; - - readonly #isSendFlowHistoryDisabled: boolean; - readonly #isSimulationEnabled: () => boolean; readonly #isSwapsDisabled: boolean; @@ -919,8 +901,6 @@ export class TransactionController extends BaseController< readonly #trace: TraceCallback; - readonly #transactionHistoryLimit: number; - /** * Constructs a TransactionController. * @@ -928,8 +908,6 @@ export class TransactionController extends BaseController< */ constructor(options: TransactionControllerOptions) { const { - disableHistory, - disableSendFlowHistory, disableSwaps, getCurrentAccountEIP1559Compatibility, getCurrentNetworkEIP1559Compatibility, @@ -954,7 +932,6 @@ export class TransactionController extends BaseController< state, testGasFeeFlows, trace, - transactionHistoryLimit = 40, } = options; super({ @@ -999,8 +976,6 @@ export class TransactionController extends BaseController< isEIP7702GasFeeTokensEnabled ?? (() => Promise.resolve(false)); this.#isFirstTimeInteractionEnabled = isFirstTimeInteractionEnabled ?? (() => true); - this.#isHistoryDisabled = disableHistory ?? false; - this.#isSendFlowHistoryDisabled = disableSendFlowHistory ?? false; this.#isSimulationEnabled = isSimulationEnabled ?? (() => true); this.#isSwapsDisabled = disableSwaps ?? false; this.#pendingTransactionOptions = pendingTransactions; @@ -1012,7 +987,6 @@ export class TransactionController extends BaseController< this.#sign = sign; this.#testGasFeeFlows = testGasFeeFlows === true; this.#trace = trace ?? (((_request, fn) => fn?.()) as TraceCallback); - this.#transactionHistoryLimit = transactionHistoryLimit; const findNetworkClientIdByChainId = (chainId: Hex) => { return this.messenger.call( @@ -1091,7 +1065,6 @@ export class TransactionController extends BaseController< isEnabled: this.#incomingTransactionOptions.isEnabled, messenger: this.messenger, remoteTransactionSource: new AccountsApiRemoteTransactionSource(), - trimTransactions: this.#trimTransactionsForState.bind(this), updateTransactions: this.#incomingTransactionOptions.updateTransactions, }); @@ -1238,7 +1211,6 @@ export class TransactionController extends BaseController< publishHook, requireApproval, securityAlertResponse, - sendFlowHistory, skipInitialGasEstimate, swaps = {}, traceContext, @@ -1372,7 +1344,6 @@ export class TransactionController extends BaseController< this.#updateTransactionInternal( { transactionId: newTransactionMeta.id, - skipHistory: true, skipResimulateCheck: true, skipValidation: true, }, @@ -1403,14 +1374,6 @@ export class TransactionController extends BaseController< securityProviderResponse; } - if (!this.#isSendFlowHistoryDisabled) { - addedTransactionMeta.sendFlowHistory = sendFlowHistory ?? []; - } - // Initial history push - if (!this.#isHistoryDisabled) { - addedTransactionMeta = addInitialHistorySnapshot(addedTransactionMeta); - } - addedTransactionMeta = updateSwapsTransaction( addedTransactionMeta, transactionType, @@ -1429,7 +1392,6 @@ export class TransactionController extends BaseController< this.#updateTransactionInternal( { transactionId: addedTransactionMeta.id, - skipHistory: true, skipResimulateCheck: true, skipValidation: true, }, @@ -1777,12 +1739,11 @@ export class TransactionController extends BaseController< * 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. */ - updateTransaction(transactionMeta: TransactionMeta, note: string) { + updateTransaction(transactionMeta: TransactionMeta) { const { id: transactionId } = transactionMeta; - this.#updateTransactionInternal({ transactionId, note }, () => ({ + this.#updateTransactionInternal({ transactionId }, () => ({ ...transactionMeta, })); } @@ -1812,10 +1773,7 @@ export class TransactionController extends BaseController< ...transactionMeta, securityAlertResponse, }; - this.updateTransaction( - updatedTransactionMeta, - `${controllerName}:updatesecurityAlertResponse - securityAlertResponse updated`, - ); + this.updateTransaction(updatedTransactionMeta); } /** @@ -1859,7 +1817,7 @@ export class TransactionController extends BaseController< ); this.update((state) => { - state.transactions = this.#trimTransactionsForState(newTransactions); + state.transactions = newTransactions; }); } @@ -1895,10 +1853,7 @@ export class TransactionController extends BaseController< this.#markNonceDuplicatesDropped(transactionId); // Update external provided transaction with updated gas values and confirmed status. - this.updateTransaction( - updatedTransactionMeta, - `${controllerName}:confirmExternalTransaction - Add external transaction`, - ); + this.updateTransaction(updatedTransactionMeta); this.#onTransactionStatusChange(updatedTransactionMeta); // Intentional given potential duration of process. @@ -1917,53 +1872,6 @@ export class TransactionController extends BaseController< } } - /** - * Append new send flow history to a transaction. - * - * @param transactionID - The ID of the transaction to update. - * @param currentSendFlowHistoryLength - The length of the current sendFlowHistory array. - * @param sendFlowHistoryToAdd - The sendFlowHistory entries to add. - * @returns The updated transactionMeta. - */ - updateTransactionSendFlowHistory( - transactionID: string, - currentSendFlowHistoryLength: number, - sendFlowHistoryToAdd: SendFlowHistoryEntry[], - ): TransactionMeta { - if (this.#isSendFlowHistoryDisabled) { - throw new Error( - 'Send flow history is disabled for the current transaction controller', - ); - } - - const transactionMeta = this.#getTransaction(transactionID); - - if (!transactionMeta) { - throw new Error( - `Cannot update send flow history as no transaction metadata found`, - ); - } - - validateIfTransactionUnapproved( - transactionMeta, - 'updateTransactionSendFlowHistory', - ); - - const sendFlowHistory = transactionMeta.sendFlowHistory ?? []; - if (currentSendFlowHistoryLength === sendFlowHistory.length) { - const updatedTransactionMeta = { - ...transactionMeta, - sendFlowHistory: [...sendFlowHistory, ...sendFlowHistoryToAdd], - }; - this.updateTransaction( - updatedTransactionMeta, - `${controllerName}:updateTransactionSendFlowHistory - sendFlowHistory updated`, - ); - } - - return this.#getTransaction(transactionID) as TransactionMeta; - } - /** * Update the gas values of a transaction. * @@ -2078,7 +1986,6 @@ export class TransactionController extends BaseController< this.#updateTransactionInternal( { transactionId, - note: `${controllerName}:updateTransactionGasFees - gas values updated`, skipResimulateCheck: true, }, (draftTxMeta) => { @@ -2143,10 +2050,7 @@ export class TransactionController extends BaseController< // merge updated previous gas values with existing transaction meta const updatedMeta = merge({}, transactionMeta, transactionPreviousGas); - this.updateTransaction( - updatedMeta, - `${controllerName}:updatePreviousGasParams - Previous gas values updated`, - ); + this.updateTransaction(updatedMeta); return this.#getTransaction(transactionId) as TransactionMeta; } @@ -2257,10 +2161,7 @@ export class TransactionController extends BaseController< transactionMeta: updatedTransaction, }); - this.updateTransaction( - updatedTransaction, - `Update Editable Params for ${txId}`, - ); + this.updateTransaction(updatedTransaction); return this.#getTransaction(txId); } @@ -2281,8 +2182,6 @@ export class TransactionController extends BaseController< this.#updateTransactionInternal( { transactionId, - note: 'TransactionController#setTransactionActive - Transaction isActive updated', - skipHistory: true, skipValidation: true, skipResimulateCheck: true, }, @@ -2435,10 +2334,7 @@ export class TransactionController extends BaseController< delete updatedTransactionMeta.txParams.maxPriorityFeePerGas; } - this.updateTransaction( - updatedTransactionMeta, - `${controllerName}:updateCustodialTransaction - Custodial transaction updated`, - ); + this.updateTransaction(updatedTransactionMeta); if ( status && @@ -2670,7 +2566,7 @@ export class TransactionController extends BaseController< ({ status }) => status !== TransactionStatus.unapproved, ); this.update((state) => { - state.transactions = this.#trimTransactionsForState(transactions); + state.transactions = transactions; }); } @@ -2727,7 +2623,6 @@ export class TransactionController extends BaseController< const updatedTransactionMeta = this.#updateTransactionInternal( { transactionId, - note: 'TransactionController#updateAtomicBatchData - Atomic batch data updated', }, (transactionMeta) => { const { nestedTransactions, txParams } = transactionMeta; @@ -2765,7 +2660,6 @@ export class TransactionController extends BaseController< this.#updateTransactionInternal( { transactionId, - note: 'TransactionController#updateAtomicBatchData - Gas estimate updated', }, (transactionMeta) => { transactionMeta.txParams.gas = draftTransaction.txParams.gas; @@ -2797,7 +2691,6 @@ export class TransactionController extends BaseController< this.#updateTransactionInternal( { transactionId, - note: 'TransactionController#updateBatchTransactions - Batch transactions updated', }, (transactionMeta) => { transactionMeta.batchTransactions = batchTransactions; @@ -2909,10 +2802,7 @@ export class TransactionController extends BaseController< }); } - this.updateTransaction( - updatedTransactionMeta, - 'Generated from user operation', - ); + this.updateTransaction(updatedTransactionMeta); this.messenger.publish('TransactionController:transactionStatusUpdated', { transactionMeta: updatedTransactionMeta, @@ -2922,10 +2812,7 @@ export class TransactionController extends BaseController< #addMetadata(transactionMeta: TransactionMeta) { validateTxParams(transactionMeta.txParams); this.update((state) => { - state.transactions = this.#trimTransactionsForState([ - ...state.transactions, - transactionMeta, - ]); + state.transactions = [...state.transactions, transactionMeta]; }); } @@ -3070,10 +2957,7 @@ export class TransactionController extends BaseController< params: updatedTransaction.txParams, }); - this.updateTransaction( - updatedTransaction, - 'TransactionController#processApproval - Updated with approval data', - ); + this.updateTransaction(updatedTransaction); } } @@ -3207,7 +3091,6 @@ export class TransactionController extends BaseController< transactionMeta = this.#updateTransactionInternal( { transactionId, - note: 'TransactionController#approveTransaction - Transaction approved', }, (draftTxMeta) => { const { chainId, txParams } = draftTxMeta; @@ -3289,7 +3172,6 @@ export class TransactionController extends BaseController< transactionMeta = this.#updateTransactionInternal( { transactionId, - note: 'TransactionController#approveTransaction - Transaction submitted', }, (draftTxMeta) => { draftTxMeta.hash = hash; @@ -3382,53 +3264,6 @@ export class TransactionController extends BaseController< this.#onTransactionStatusChange(updatedTransactionMeta); } - /** - * Trim the amount of transactions that are set on the state. Checks - * if the length of the tx history is longer then desired persistence - * limit and then if it is removes the oldest confirmed or rejected tx. - * Pending or unapproved transactions will not be removed by this - * operation. For safety of presenting a fully functional transaction UI - * representation, this function will not break apart transactions with the - * same nonce, created on the same day, per network. Not accounting for - * transactions of the same nonce, same day and network combo can result in - * confusing or broken experiences in the UI. - * - * @param transactions - The transactions to be applied to the state. - * @returns The trimmed list of transactions. - */ - #trimTransactionsForState( - transactions: TransactionMeta[], - ): TransactionMeta[] { - const nonceNetworkSet = new Set(); - - const txsToKeep = [...transactions] - .sort((a, b) => (a.time > b.time ? -1 : 1)) // Descending time order - .filter((tx) => { - const { chainId, status, txParams, time } = tx; - - if (txParams) { - const key = `${String(txParams.nonce)}-${convertHexToDecimal( - chainId, - )}-${new Date(time).toDateString()}`; - - if (nonceNetworkSet.has(key)) { - return true; - } else if ( - nonceNetworkSet.size < this.#transactionHistoryLimit || - !this.#isFinalState(status) - ) { - nonceNetworkSet.add(key); - return true; - } - } - - return false; - }); - - txsToKeep.reverse(); // Ascending time order - return txsToKeep; - } - /** * Determines if the transaction is in a final state. * @@ -3601,10 +3436,7 @@ export class TransactionController extends BaseController< this.update((state) => { const { transactions: currentTransactions } = state; - state.transactions = this.#trimTransactionsForState([ - ...finalTransactions, - ...currentTransactions, - ]); + state.transactions = [...finalTransactions, ...currentTransactions]; log( 'Added incoming transactions to state', @@ -3685,20 +3517,11 @@ export class TransactionController extends BaseController< pendingTxs, ); - // Make sure provided external transaction has non empty history array - const newTransactionMeta = - (transactionMeta.history ?? []).length === 0 && !this.#isHistoryDisabled - ? addInitialHistorySnapshot(transactionMeta) - : transactionMeta; - this.update((state) => { - state.transactions = this.#trimTransactionsForState([ - ...state.transactions, - newTransactionMeta, - ]); + state.transactions = [...state.transactions, transactionMeta]; }); - return newTransactionMeta; + return transactionMeta; } /** @@ -3765,10 +3588,7 @@ export class TransactionController extends BaseController< this.messenger.publish(`${controllerName}:transactionDropped`, { transactionMeta: updatedTransactionMeta, }); - this.updateTransaction( - updatedTransactionMeta, - 'TransactionController#setTransactionStatusDropped - Transaction dropped', - ); + this.updateTransaction(updatedTransactionMeta); this.#onTransactionStatusChange(updatedTransactionMeta); } @@ -3846,7 +3666,7 @@ export class TransactionController extends BaseController< if (updateTransaction) { this.#updateTransactionInternal( - { transactionId, skipResimulateCheck: true, note: 'beforeSign Hook' }, + { transactionId, skipResimulateCheck: true }, updateTransaction, ); @@ -3921,10 +3741,7 @@ export class TransactionController extends BaseController< const transactionMetaFromHook = cloneDeep(finalTransactionMeta); if (!this.#afterSign(transactionMetaFromHook, signedTx)) { - this.updateTransaction( - transactionMetaFromHook, - 'TransactionController#signTransaction - Update after sign', - ); + this.updateTransaction(transactionMetaFromHook); log('Skipping signed status based on hook'); @@ -3937,10 +3754,7 @@ export class TransactionController extends BaseController< txParams: finalTxParams, }; - this.updateTransaction( - transactionMetaWithRsv, - 'TransactionController#approveTransaction - Transaction signed', - ); + this.updateTransaction(transactionMetaWithRsv); this.#onTransactionStatusChange(transactionMetaWithRsv); @@ -3950,10 +3764,7 @@ export class TransactionController extends BaseController< rawTx, }); - this.updateTransaction( - transactionMetaWithRawTx, - 'TransactionController#approveTransaction - RawTransaction added', - ); + this.updateTransaction(transactionMetaWithRawTx); return rawTx; } @@ -4210,14 +4021,10 @@ export class TransactionController extends BaseController< #updateTransactionInternal( { transactionId, - note, - skipHistory, skipValidation, skipResimulateCheck, }: { transactionId: string; - note?: string; - skipHistory?: boolean; skipValidation?: boolean; skipResimulateCheck?: boolean; }, @@ -4257,14 +4064,6 @@ export class TransactionController extends BaseController< ); } - const shouldSkipHistory = this.#isHistoryDisabled || skipHistory; - - if (!shouldSkipHistory) { - transactionMeta = updateTransactionHistory( - transactionMeta, - note ?? 'Transaction updated', - ); - } state.transactions[index] = transactionMeta; }); @@ -4371,7 +4170,6 @@ export class TransactionController extends BaseController< const updatedTransactionMeta = this.#updateTransactionInternal( { transactionId, - note: 'TransactionController#updateSimulationData - Update simulation data', skipResimulateCheck: Boolean(blockTime), }, (txMeta) => { @@ -4401,18 +4199,15 @@ export class TransactionController extends BaseController< gasFeeEstimatesLoaded?: boolean; layer1GasFee?: Hex; }) { - this.#updateTransactionInternal( - { transactionId, skipHistory: true }, - (txMeta) => { - updateTransactionGasProperties({ - txMeta, - gasFeeEstimates, - gasFeeEstimatesLoaded, - isTxParamsGasFeeUpdatesEnabled: this.#isAutomaticGasFeeUpdateEnabled, - layer1GasFee, - }); - }, - ); + this.#updateTransactionInternal({ transactionId }, (txMeta) => { + updateTransactionGasProperties({ + txMeta, + gasFeeEstimates, + gasFeeEstimatesLoaded, + isTxParamsGasFeeUpdatesEnabled: this.#isAutomaticGasFeeUpdateEnabled, + layer1GasFee, + }); + }); } #onGasFeePollerTransactionBatchUpdate({ @@ -4578,7 +4373,7 @@ export class TransactionController extends BaseController< ({ id }) => id !== transactionId, ); - state.transactions = this.#trimTransactionsForState(transactions); + state.transactions = transactions; }); } @@ -4617,7 +4412,6 @@ export class TransactionController extends BaseController< newTransactionMeta = this.#updateTransactionInternal( { transactionId: transactionMeta.id, - note: 'TransactionController#failTransaction - Add error message and set status to failed', skipValidation: true, }, (draftTransactionMeta) => { @@ -4684,7 +4478,6 @@ export class TransactionController extends BaseController< { transactionId, skipResimulateCheck: true, - note: 'afterSimulate Hook', }, (txMeta) => { txMeta.txParamsOriginal = cloneDeep(txMeta.txParams); diff --git a/packages/transaction-controller/src/TransactionControllerIntegration.test.ts b/packages/transaction-controller/src/TransactionControllerIntegration.test.ts index efc26963003..3d51207d8be 100644 --- a/packages/transaction-controller/src/TransactionControllerIntegration.test.ts +++ b/packages/transaction-controller/src/TransactionControllerIntegration.test.ts @@ -273,8 +273,6 @@ const setupController = async ( ); const options: TransactionControllerOptions = { - disableHistory: false, - disableSendFlowHistory: false, disableSwaps: false, isAutomaticGasFeeUpdateEnabled: () => true, getCurrentNetworkEIP1559Compatibility: async ( @@ -295,7 +293,6 @@ const setupController = async ( isResubmitEnabled: () => false, }, sign: async (transaction: TypedTransaction) => transaction, - transactionHistoryLimit: 40, ...givenOptions, }; @@ -398,7 +395,6 @@ describe('TransactionController Integration', () => { estimateType: 'dappSuggested', }, userFeeLevel: 'dappSuggested', - sendFlowHistory: [], }, { actionId: undefined, @@ -433,7 +429,6 @@ describe('TransactionController Integration', () => { estimateType: 'dappSuggested', }, userFeeLevel: 'dappSuggested', - sendFlowHistory: [], }, ], }, @@ -655,13 +650,13 @@ describe('TransactionController Integration', () => { 'confirmed', ); expect( - transactionController.state.transactions[0].networkClientId, + transactionController.state.transactions[1].networkClientId, ).toBe('sepolia'); expect(transactionController.state.transactions[1].status).toBe( 'confirmed', ); expect( - transactionController.state.transactions[1].networkClientId, + transactionController.state.transactions[0].networkClientId, ).toBe('linea-sepolia'); transactionController.destroy(); }); diff --git a/packages/transaction-controller/src/helpers/IncomingTransactionHelper.test.ts b/packages/transaction-controller/src/helpers/IncomingTransactionHelper.test.ts index b7c8761fcec..91e41facafc 100644 --- a/packages/transaction-controller/src/helpers/IncomingTransactionHelper.test.ts +++ b/packages/transaction-controller/src/helpers/IncomingTransactionHelper.test.ts @@ -44,7 +44,6 @@ const CONTROLLER_ARGS_MOCK: ConstructorParameters< getLocalTransactions: () => [], messenger: MESSENGER_MOCK, remoteTransactionSource: {} as RemoteTransactionSource, - trimTransactions: (transactions) => transactions, }; const TRANSACTION_MOCK: TransactionMeta = { @@ -317,7 +316,6 @@ describe('IncomingTransactionHelper', () => { it('does not if all unique transactions are truncated', async () => { const helper = new IncomingTransactionHelper({ ...CONTROLLER_ARGS_MOCK, - trimTransactions: () => [], remoteTransactionSource: createRemoteTransactionSourceMock([ TRANSACTION_MOCK, ]), diff --git a/packages/transaction-controller/src/helpers/IncomingTransactionHelper.ts b/packages/transaction-controller/src/helpers/IncomingTransactionHelper.ts index 6e30cbb6fb7..7cbd8dc0ece 100644 --- a/packages/transaction-controller/src/helpers/IncomingTransactionHelper.ts +++ b/packages/transaction-controller/src/helpers/IncomingTransactionHelper.ts @@ -53,10 +53,6 @@ export class IncomingTransactionHelper { #timeoutId?: unknown; - readonly #trimTransactions: ( - transactions: TransactionMeta[], - ) => TransactionMeta[]; - readonly #updateTransactions?: boolean; constructor({ @@ -67,7 +63,6 @@ export class IncomingTransactionHelper { isEnabled, messenger, remoteTransactionSource, - trimTransactions, updateTransactions, }: { client?: string; @@ -79,7 +74,6 @@ export class IncomingTransactionHelper { isEnabled?: () => boolean; messenger: TransactionControllerMessenger; remoteTransactionSource: RemoteTransactionSource; - trimTransactions: (transactions: TransactionMeta[]) => TransactionMeta[]; updateTransactions?: boolean; }) { this.hub = new EventEmitter(); @@ -93,7 +87,6 @@ export class IncomingTransactionHelper { this.#isUpdating = false; this.#messenger = messenger; this.#remoteTransactionSource = remoteTransactionSource; - this.#trimTransactions = trimTransactions; this.#updateTransactions = updateTransactions; } @@ -229,10 +222,7 @@ export class IncomingTransactionHelper { uniqueTransactions, ); - const trimmedTransactions = this.#trimTransactions([ - ...uniqueTransactions, - ...localTransactions, - ]); + const trimmedTransactions = [...uniqueTransactions, ...localTransactions]; const uniqueTransactionIds = uniqueTransactions.map((tx) => tx.id); diff --git a/packages/transaction-controller/src/index.ts b/packages/transaction-controller/src/index.ts index 288c6f3ab61..b817f486f90 100644 --- a/packages/transaction-controller/src/index.ts +++ b/packages/transaction-controller/src/index.ts @@ -77,7 +77,6 @@ export type { SavedGasFees, SecurityAlertResponse, SecurityProviderRequest, - SendFlowHistoryEntry, SimulationBalanceChange, SimulationData, SimulationError, @@ -87,8 +86,6 @@ export type { TransactionBatchRequest, TransactionBatchResult, TransactionError, - TransactionHistory, - TransactionHistoryEntry, TransactionMeta, TransactionParams, TransactionReceipt, @@ -106,10 +103,6 @@ export { UserFeeLevel, WalletDevice, } from './types'; -export { - DISPLAYED_TRANSACTION_HISTORY_PATHS, - MAX_TRANSACTION_HISTORY_LENGTH, -} from './utils/history'; export { determineTransactionType } from './utils/transaction-type'; export { mergeGasFeeEstimates } from './utils/gas-flow'; export { diff --git a/packages/transaction-controller/src/types.ts b/packages/transaction-controller/src/types.ts index 99831d6c30e..ddab6097a79 100644 --- a/packages/transaction-controller/src/types.ts +++ b/packages/transaction-controller/src/types.ts @@ -249,11 +249,6 @@ export type TransactionMeta = { */ hash?: string; - /** - * A history of mutations to TransactionMeta. - */ - history?: TransactionHistory; - /** * Generated UUID associated with this transaction. */ @@ -416,12 +411,6 @@ export type TransactionMeta = { */ selectedGasFeeToken?: Hex; - /** - * An array of entries that describe the user's journey through the send flow. - * This is purely attached to state logs for troubleshooting and support. - */ - sendFlowHistory?: SendFlowHistoryEntry[]; - /** * Simulation data for the transaction used to predict its outcome. */ @@ -599,18 +588,6 @@ export type TransactionBatchMeta = { transactions?: NestedTransactionMetadata[]; }; -export type SendFlowHistoryEntry = { - /** - * String to indicate user interaction information. - */ - entry: string; - - /** - * Timestamp associated with this entry. - */ - timestamp: number; -}; - /** * Represents the status of a transaction within the wallet. * Each status reflects the state of the transaction internally, @@ -1152,31 +1129,6 @@ export interface SavedGasFees { priorityFee: string; } -/** - * A transaction history operation that includes a note and timestamp. - */ -type ExtendedHistoryOperation = JsonCompatibleOperation & { - note?: string; - timestamp?: number; -}; - -/** - * A transaction history entry that includes the ExtendedHistoryOperation as the first element. - */ -export type TransactionHistoryEntry = [ - ExtendedHistoryOperation, - ...JsonCompatibleOperation[], -]; - -/** - * A transaction history that includes the transaction meta as the first element. - * And the rest of the elements are the operation arrays that were applied to the transaction meta. - */ -export type TransactionHistory = [ - TransactionMeta, - ...TransactionHistoryEntry[], -]; - /** * Result of inferring the transaction type. */ @@ -2111,9 +2063,6 @@ export type AddTransactionOptions = { /** Response from security validator. */ securityAlertResponse?: SecurityAlertResponse; - /** Entries to add to the `sendFlowHistory`. */ - sendFlowHistory?: SendFlowHistoryEntry[]; - /** Whether to skip the initial gas calculation and rely only on the polling. */ skipInitialGasEstimate?: boolean; diff --git a/packages/transaction-controller/src/utils/history.test.ts b/packages/transaction-controller/src/utils/history.test.ts deleted file mode 100644 index 02e96e91fd8..00000000000 --- a/packages/transaction-controller/src/utils/history.test.ts +++ /dev/null @@ -1,401 +0,0 @@ -import { toHex } from '@metamask/controller-utils'; -import { add0x } from '@metamask/utils'; -import { cloneDeep } from 'lodash'; - -import { - MAX_TRANSACTION_HISTORY_LENGTH, - updateTransactionHistory, -} from './history'; -import { TransactionStatus } from '../types'; -import type { - TransactionHistory, - TransactionMeta, - TransactionHistoryEntry, -} from '../types'; - -describe('History', () => { - describe('updateTransactionHistory', () => { - it('does nothing if the history property is missing', () => { - const mockTransaction = createMinimalMockTransaction(); - expect(mockTransaction.history).toBeUndefined(); - const originalInputTransaction = cloneDeep(mockTransaction); - - const updatedTransaction = updateTransactionHistory( - mockTransaction, - 'test update', - ); - - expect(updatedTransaction).toBe(mockTransaction); - expect(updatedTransaction).toStrictEqual(originalInputTransaction); - }); - - it('does nothing if there have been no changes', () => { - const originalTransaction = createMinimalMockTransaction(); - const mockTransaction = createMockTransaction({ originalTransaction }); - const mockTransactionClone = cloneDeep(mockTransaction); - - const updatedTransaction = updateTransactionHistory( - mockTransaction, - 'test update', - ); - - expect(updatedTransaction).toBe(mockTransaction); - expect(updatedTransaction).toStrictEqual(mockTransactionClone); - }); - - it('adds a new history entry', () => { - const originalTransaction = createMinimalMockTransaction(); - const mockTransaction = createMockTransaction({ - originalTransaction, - partialTransaction: { - history: [originalTransaction], - txParams: { from: generateAddress(123) }, - }, - }); - - const updatedTransaction = updateTransactionHistory( - mockTransaction, - 'test update', - ); - - expect(updatedTransaction).not.toBe(mockTransaction); - expect(updatedTransaction).toStrictEqual({ - ...mockTransaction, - history: [ - originalTransaction, - [ - { - note: 'test update', - op: 'replace', - path: '/txParams/from', - timestamp: expect.any(Number), - value: generateAddress(123), - }, - ], - ], - }); - }); - - describe('when history is past max size with non-displayed entries', () => { - it('merges a non-displayed entry when adding a new entry after max history size is reached', () => { - const originalTransaction = createMinimalMockTransaction(); - const mockTransaction = createMockTransaction({ - partialTransaction: { - history: generateMockHistory({ - originalTransaction, - length: MAX_TRANSACTION_HISTORY_LENGTH, - }), - // This is the changed value - txParams: { from: generateAddress(123) }, - }, - }); - // Validate that last history entry is correct - expect( - mockTransaction.history?.[mockTransaction.history.length - 1], - ).toStrictEqual([ - { - note: 'Mock non-displayed change', - op: 'replace', - path: '/txParams/from', - value: generateAddress(MAX_TRANSACTION_HISTORY_LENGTH - 1), - }, - ]); - expect(mockTransaction.history).toHaveLength( - MAX_TRANSACTION_HISTORY_LENGTH, - ); - - const updatedTransaction = updateTransactionHistory( - mockTransaction, - 'test update', - ); - - expect(updatedTransaction).not.toBe(mockTransaction); - expect(updatedTransaction).toStrictEqual({ - ...mockTransaction, - history: [ - originalTransaction, - // This is the merged entry of mockTransaction.history[1] and mockTransaction.history[2] - [ - { - note: 'Mock non-displayed change, Mock non-displayed change', - op: 'replace', - path: '/txParams/from', - timestamp: expect.any(Number), - value: generateAddress(2), - }, - ], - ...mockTransaction.history.slice(3), - // This is the new entry: - [ - { - note: 'test update', - op: 'replace', - path: '/txParams/from', - timestamp: expect.any(Number), - value: generateAddress(123), - }, - ], - ], - }); - expect(updatedTransaction.history).toHaveLength( - MAX_TRANSACTION_HISTORY_LENGTH, - ); - }); - }); - - describe('when history is past max size with a single non-displayed entry at the end', () => { - it('merges a non-displayed entry when adding a new entry after max history size is reached', () => { - const originalTransaction = createMinimalMockTransaction(); - // This matches the last gas price change in the mock history - const mockTransactionGasPrice = toHex( - MAX_TRANSACTION_HISTORY_LENGTH - 1, - ); - const mockTransaction = createMockTransaction({ - partialTransaction: { - history: generateMockHistory({ - numberOfDisplayEntries: MAX_TRANSACTION_HISTORY_LENGTH - 1, - originalTransaction, - length: MAX_TRANSACTION_HISTORY_LENGTH, - }), - txParams: { - // This is the changed value - from: generateAddress(123), - // This matches the last gas price change in the mock history - gasPrice: mockTransactionGasPrice, - }, - }, - }); - // Validate that last history entry is correct - expect( - mockTransaction.history?.[mockTransaction.history.length - 1], - ).toStrictEqual([ - { - note: 'Mock displayed change', - op: 'replace', - path: '/txParams/gasPrice', - value: mockTransactionGasPrice, - }, - ]); - const mockTransactionClone = cloneDeep(mockTransaction); - expect(mockTransaction.history).toHaveLength( - MAX_TRANSACTION_HISTORY_LENGTH, - ); - - const updatedTransaction = updateTransactionHistory( - mockTransaction, - 'test update', - ); - - expect(updatedTransaction).not.toBe(mockTransaction); - expect(updatedTransaction).toStrictEqual({ - ...mockTransaction, - history: [ - ...mockTransactionClone.history.slice( - 0, - MAX_TRANSACTION_HISTORY_LENGTH - 1, - ), - // This is the new merged entry: - [ - { - note: 'Mock displayed change, test update', - op: 'replace', - path: '/txParams/gasPrice', - timestamp: expect.any(Number), - value: mockTransactionGasPrice, - }, - { - op: 'replace', - path: '/txParams/from', - value: generateAddress(123), - }, - ], - ], - }); - expect(updatedTransaction.history).toHaveLength( - MAX_TRANSACTION_HISTORY_LENGTH, - ); - }); - }); - - describe('when history is past max size with only displayed entries', () => { - it('adds a new history entry, exceeding max size', () => { - const originalTransaction = createMinimalMockTransaction(); - const mockTransaction = createMockTransaction({ - partialTransaction: { - history: generateMockHistory({ - numberOfDisplayEntries: MAX_TRANSACTION_HISTORY_LENGTH - 1, - originalTransaction, - length: MAX_TRANSACTION_HISTORY_LENGTH, - }), - txParams: { - from: originalTransaction.txParams.from, - // This is the changed value - gasPrice: toHex(1337), - }, - }, - }); - // Validate that last history entry is correct - expect( - mockTransaction.history?.[mockTransaction.history.length - 1], - ).toStrictEqual([ - { - note: 'Mock displayed change', - op: 'replace', - path: '/txParams/gasPrice', - value: toHex(MAX_TRANSACTION_HISTORY_LENGTH - 1), - }, - ]); - expect(mockTransaction.history).toHaveLength( - MAX_TRANSACTION_HISTORY_LENGTH, - ); - - const updatedTransaction = updateTransactionHistory( - mockTransaction, - 'test update', - ); - - expect(updatedTransaction).not.toBe(mockTransaction); - expect(updatedTransaction).toStrictEqual({ - ...mockTransaction, - history: [ - ...mockTransaction.history, - // This is the new entry: - [ - { - note: 'test update', - op: 'replace', - path: '/txParams/gasPrice', - timestamp: expect.any(Number), - value: toHex(1337), - }, - ], - ], - }); - expect(updatedTransaction.history).toHaveLength( - MAX_TRANSACTION_HISTORY_LENGTH + 1, - ); - }); - }); - }); -}); - -/** - * Create a minimal mock transaction. It has just enough to satify the type. - * - * @returns A minimal transaction. - */ -function createMinimalMockTransaction(): TransactionMeta { - return { - chainId: toHex(1337), - id: 'mock-id', - networkClientId: 'testNetworkClientId', - time: 0, - status: TransactionStatus.submitted as const, - txParams: { - from: '', - }, - }; -} - -/** - * Create a mock transaction. - * - * Optionally an incomplete transaction can be passed in, and any missing required proeprties will - * be filled out. The 'status' property is not allowed as input only because it was complicated to - * get the types to be correct if it was provided. - * - * A `history` property is included only if an original transaction is passed in. - * - * @param options - Options. - * @param options.partialTransaction - A partial transaction, without a 'status' property. - * @param options.originalTransaction - The original transaction object to include in the transaction - * history. - * @returns A mock transaction. - */ -function createMockTransaction({ - partialTransaction, - originalTransaction, -}: { - partialTransaction?: Omit, 'status'>; - originalTransaction?: TransactionMeta; -} = {}): TransactionMeta & Required> { - const minimalTransaction = createMinimalMockTransaction(); - - if (originalTransaction) { - if (originalTransaction.history) { - throw new Error( - 'The original transaction should be an initial snapshot of the transaction, with no history property', - ); - } - minimalTransaction.history = [originalTransaction]; - } else { - minimalTransaction.history = [{ ...minimalTransaction }]; - } - - return { - // Cast used here because TypeScript wasn't able to infer that `history` was guaranteed to be set - ...(minimalTransaction as TransactionMeta & - Required>), - ...partialTransaction, - }; -} - -/** - * Generate a mock transaction history. - * - * @param args - Arguments. - * @param args.numberOfDisplayEntries - The number of displayed history entries to generate. - * @param args.originalTransaction - The original transaction, before any history changes. - * @param args.length - The total length of history to generate. - * @returns Mock transaction history. - */ -function generateMockHistory({ - numberOfDisplayEntries = 0, - originalTransaction, - length, -}: { - numberOfDisplayEntries?: number; - originalTransaction: TransactionMeta; - length: number; -}): TransactionHistory { - if (length < 1) { - throw new Error('Invalid length'); - } else if (numberOfDisplayEntries >= length) { - throw new Error('Length must exceed number of displayed entries'); - } - - const historyEntries: TransactionHistoryEntry[] = [ - ...Array(length - 1).keys(), - ].map((index: number) => { - // Use index of this entry in history array, for better readability/debugging of mock values - const historyIndex = index + 1; - - return [ - numberOfDisplayEntries < historyIndex - ? { - note: 'Mock non-displayed change', - op: 'replace', - path: '/txParams/from', - value: generateAddress(historyIndex), - } - : { - note: 'Mock displayed change', - op: index === 0 ? 'add' : 'replace', - path: '/txParams/gasPrice', - value: toHex(historyIndex), - }, - ]; - }); - - return [originalTransaction, ...historyEntries]; -} - -/** - * Generate a mock lowercase Ethereum address. - * - * @param number - The address as a decimal number. - * @returns The mock address - */ -function generateAddress(number: number) { - return add0x(number.toString(16).padStart(40, '0')); -} diff --git a/packages/transaction-controller/src/utils/history.ts b/packages/transaction-controller/src/utils/history.ts deleted file mode 100644 index 84238cef6e5..00000000000 --- a/packages/transaction-controller/src/utils/history.ts +++ /dev/null @@ -1,216 +0,0 @@ -import jsonDiffer from 'fast-json-patch'; -import { cloneDeep, merge } from 'lodash'; - -import type { - TransactionHistory, - TransactionHistoryEntry, - TransactionMeta, -} from '../types'; - -/** - * The maximum allowed length of the `transaction.history` property. - */ -export const MAX_TRANSACTION_HISTORY_LENGTH = 100; - -/** - * A list of trarnsaction history paths that may be used for display. These entries will not be - * compressed. - */ -export const DISPLAYED_TRANSACTION_HISTORY_PATHS = [ - '/status', - '/txParams/gasPrice', - '/txParams/gas', - '/estimatedBaseFee', - '/blockTimestamp', -]; - -/** - * Build a new version of the provided transaction with an initial history - * entry, which is just a snapshot of the transaction. - * - * @param transactionMeta - TransactionMeta to add initial history snapshot to. - * @returns A copy of `transactionMeta` with a new `history` property. - */ -export function addInitialHistorySnapshot( - transactionMeta: TransactionMeta, -): TransactionMeta { - const snapshot = snapshotFromTransactionMeta(transactionMeta); - return merge({}, transactionMeta, { history: [snapshot] }); -} - -/** - * Builds a new version of the transaction with a new history entry if - * it has a `history` property, or just returns the transaction. - * - * @param transactionMeta - TransactionMeta to add history entry to. - * @param note - Note to add to history entry. - * @returns A copy of `transactionMeta` with a new `history` entry if it has an - * existing non-empty `history` array. - */ -export function updateTransactionHistory( - transactionMeta: TransactionMeta, - note: string, -): TransactionMeta { - if (!transactionMeta.history) { - return transactionMeta; - } - - const currentState = snapshotFromTransactionMeta(transactionMeta); - const previousState = replayHistory(transactionMeta.history); - const newHistoryEntry = generateHistoryEntry( - previousState, - currentState, - note, - ); - - if (newHistoryEntry.length === 0) { - return transactionMeta; - } - - // Casts required here because this list has two separate types of entries: - // TransactionMeta and TransactionHistoryEntry. The only TransactionMeta is the first - // entry, but TypeScript loses that type information when `slice` is called for some reason. - let updatedHistory = [ - ...transactionMeta.history, - newHistoryEntry, - ] as TransactionHistory; - - if (updatedHistory.length > MAX_TRANSACTION_HISTORY_LENGTH) { - updatedHistory = compressTransactionHistory(updatedHistory); - } - - return merge({}, transactionMeta, { - history: updatedHistory, - }); -} - -/** - * Compress the transaction history, if it is possible to do so without compressing entries used - * for display. History entries are merged together to make room for a single new entry. - * - * @param transactionHistory - The transaction history to compress. - * @returns A compressed transaction history. - */ -function compressTransactionHistory( - transactionHistory: TransactionHistory, -): TransactionHistory { - const initialEntry = transactionHistory[0]; - // Casts required here because this list has two separate types of entries: - // TransactionMeta and TransactionHistoryEntry. The only TransactionMeta is the first - // entry, but TypeScript loses that type information when `slice` is called for some reason. - const historyEntries = transactionHistory.slice( - 1, - ) as TransactionHistoryEntry[]; - - const firstNonDisplayedEntryIndex = historyEntries.findIndex( - (historyEntry) => { - return !historyEntry.some(({ path }) => - DISPLAYED_TRANSACTION_HISTORY_PATHS.includes(path), - ); - }, - ); - - // If no non-displayed entry is found, let history exceed max size. - // TODO: Move data used for display to another property, so that we can more reliably limit - // history size or remove it altogether. - if (firstNonDisplayedEntryIndex === -1) { - return transactionHistory; - } - - // If a non-displayed entry is found that we can remove, merge it with another entry. - // The entry we're merging with might be a "displayed" entry, but that's OK, merging more changes - // in does not break our display logic. - const mergeTargetEntryIndex = - // Merge with previous entry if there is no next entry. - // We default to merging with next because the next entry might also be non-displayed, so it - // might be removed in a future trim, saving more space. - firstNonDisplayedEntryIndex === historyEntries.length - 1 - ? firstNonDisplayedEntryIndex - 1 - : firstNonDisplayedEntryIndex + 1; - const firstIndexToMerge = Math.min( - firstNonDisplayedEntryIndex, - mergeTargetEntryIndex, - ); - const firstEntryToMerge = historyEntries[firstIndexToMerge]; - const secondEntryToMerge = historyEntries[firstIndexToMerge + 1]; - - const beforeMergeState = replayHistory([ - initialEntry, - ...historyEntries.slice(0, firstIndexToMerge), - ]); - const afterMergeState = replayHistory([ - beforeMergeState, - firstEntryToMerge, - secondEntryToMerge, - ]); - const mergedHistoryEntry = generateHistoryEntry( - beforeMergeState, - afterMergeState, - `${String(firstEntryToMerge[0].note)}, ${String( - secondEntryToMerge[0].note, - )}`, - ); - - historyEntries.splice(firstIndexToMerge, 2, mergedHistoryEntry); - return [initialEntry, ...historyEntries]; -} - -/** - * Generates a history entry from the previous and new transaction metadata. - * - * @param previousState - The previous transaction metadata. - * @param currentState - The new transaction metadata. - * @param note - A note for the transaction metada update. - * @returns An array of history operation. - */ -function generateHistoryEntry( - // TODO: Replace `any` with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - previousState: any, - currentState: TransactionMeta, - note: string, -): TransactionHistoryEntry { - const historyOperationsEntry = jsonDiffer.compare( - previousState, - currentState, - ) as TransactionHistoryEntry; - // Add a note to the first operation, since it breaks if we append it to the entry - if (historyOperationsEntry[0]) { - if (note) { - historyOperationsEntry[0].note = note; - } - historyOperationsEntry[0].timestamp = Date.now(); - } - return historyOperationsEntry; -} - -/** - * Recovers previous transactionMeta from passed history array. - * - * @param transactionHistory - The transaction metadata to replay. - * @returns The transaction metadata. - */ -function replayHistory( - transactionHistory: TransactionHistory, -): TransactionMeta { - const shortHistory = cloneDeep(transactionHistory); - return shortHistory.reduce( - // TODO: Replace `any` with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (val, entry: any) => jsonDiffer.applyPatch(val, entry).newDocument, - ) as TransactionMeta; -} - -/** - * Clone the transaction meta data without the history property. - * - * @param transactionMeta - The transaction metadata to snapshot. - * @returns A deep clone of transaction metadata without history property. - */ -function snapshotFromTransactionMeta( - transactionMeta: TransactionMeta, -): TransactionMeta { - const snapshot = { ...transactionMeta }; - delete snapshot.history; - return cloneDeep(snapshot); -} From b37deb9a509c1b364d0ffc44475b4c96fcb9682f Mon Sep 17 00:00:00 2001 From: Vinicius Stevam Date: Fri, 5 Dec 2025 14:26:56 +0000 Subject: [PATCH 02/11] fix int --- .../src/TransactionController.ts | 1 - packages/transaction-controller/src/types.ts | 20 +------------------ 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/packages/transaction-controller/src/TransactionController.ts b/packages/transaction-controller/src/TransactionController.ts index 1ee002df02b..5244bcd332d 100644 --- a/packages/transaction-controller/src/TransactionController.ts +++ b/packages/transaction-controller/src/TransactionController.ts @@ -18,7 +18,6 @@ import { query, ApprovalType, ORIGIN_METAMASK, - convertHexToDecimal, } from '@metamask/controller-utils'; import type { TraceCallback, TraceContext } from '@metamask/controller-utils'; import EthQuery from '@metamask/eth-query'; diff --git a/packages/transaction-controller/src/types.ts b/packages/transaction-controller/src/types.ts index ddab6097a79..b12939dbbbe 100644 --- a/packages/transaction-controller/src/types.ts +++ b/packages/transaction-controller/src/types.ts @@ -3,28 +3,10 @@ import type { AccountsController } from '@metamask/accounts-controller'; import type EthQuery from '@metamask/eth-query'; import type { GasFeeState } from '@metamask/gas-fee-controller'; import type { NetworkClientId, Provider } from '@metamask/network-controller'; -import type { Hex, Json } from '@metamask/utils'; -import type { Operation } from 'fast-json-patch'; +import type { Hex } from '@metamask/utils'; import type { TransactionControllerMessenger } from './TransactionController'; -/** - * Given a record, ensures that each property matches the `Json` type. - */ -type MakeJsonCompatible = T extends Json - ? T - : { - [K in keyof T]: T[K] extends Json ? T[K] : never; - }; - -/** - * `Json` from `@metamask/utils` is defined as a recursive type alias, but - * `Operation` is defined as an interface, and the two are not compatible with - * each other. Therefore, this is a variant of Operation from `fast-json-patch` - * which is guaranteed to be type-compatible with `Json`. - */ -type JsonCompatibleOperation = MakeJsonCompatible; - /** * Information about a single transaction such as status and block number. */ From 6444493033d2bb4b2a36106d0cb64397a4c5a1af Mon Sep 17 00:00:00 2001 From: Vinicius Stevam Date: Fri, 5 Dec 2025 14:52:56 +0000 Subject: [PATCH 03/11] fix build errors --- .../src/utils/transaction.test.ts | 6 ------ .../bridge-status-controller/src/utils/transaction.ts | 2 +- .../src/strategy/bridge/bridge-submit.ts | 1 - .../src/strategy/relay/relay-submit.test.ts | 1 - .../src/strategy/relay/relay-submit.ts | 3 --- packages/transaction-pay-controller/src/utils/quotes.ts | 1 - .../src/utils/transaction.test.ts | 3 --- .../transaction-pay-controller/src/utils/transaction.ts | 8 +------- 8 files changed, 2 insertions(+), 23 deletions(-) diff --git a/packages/bridge-status-controller/src/utils/transaction.test.ts b/packages/bridge-status-controller/src/utils/transaction.test.ts index eb6e075e1ea..b8255094b63 100644 --- a/packages/bridge-status-controller/src/utils/transaction.test.ts +++ b/packages/bridge-status-controller/src/utils/transaction.test.ts @@ -1851,7 +1851,6 @@ describe('Bridge Status Controller Transaction Utils', () => { id: 'tx1', type: TransactionType.swap, }), - 'Update tx type to swap', ); // Should update the approval transaction @@ -1860,7 +1859,6 @@ describe('Bridge Status Controller Transaction Utils', () => { id: 'tx2', type: TransactionType.swapApproval, }), - 'Update tx type to swapApproval', ); }); @@ -1895,7 +1893,6 @@ describe('Bridge Status Controller Transaction Utils', () => { id: 'tx1', type: TransactionType.swap, }), - 'Update tx type to swap', ); }); @@ -1929,7 +1926,6 @@ describe('Bridge Status Controller Transaction Utils', () => { id: 'tx1', type: TransactionType.swapApproval, }), - 'Update tx type to swapApproval', ); }); @@ -1967,7 +1963,6 @@ describe('Bridge Status Controller Transaction Utils', () => { id: 'tx1', type: TransactionType.bridge, }), - 'Update tx type to bridge', ); expect(mockUpdateTransactionFn).toHaveBeenCalledWith( @@ -1975,7 +1970,6 @@ describe('Bridge Status Controller Transaction Utils', () => { id: 'tx2', type: TransactionType.bridgeApproval, }), - 'Update tx type to bridgeApproval', ); }); diff --git a/packages/bridge-status-controller/src/utils/transaction.ts b/packages/bridge-status-controller/src/utils/transaction.ts index 63248853447..eedfe459c52 100644 --- a/packages/bridge-status-controller/src/utils/transaction.ts +++ b/packages/bridge-status-controller/src/utils/transaction.ts @@ -440,7 +440,7 @@ export const findAndUpdateTransactionsInBatch = ({ if (txMeta) { const updatedTx = { ...txMeta, type: txType as TransactionType }; - updateTransactionFn(updatedTx, `Update tx type to ${txType}`); + updateTransactionFn(updatedTx); txBatch[ [TransactionType.bridgeApproval, TransactionType.swapApproval].includes( txType as TransactionType, diff --git a/packages/transaction-pay-controller/src/strategy/bridge/bridge-submit.ts b/packages/transaction-pay-controller/src/strategy/bridge/bridge-submit.ts index 770ae26b5d1..b89a7b6bef3 100644 --- a/packages/transaction-pay-controller/src/strategy/bridge/bridge-submit.ts +++ b/packages/transaction-pay-controller/src/strategy/bridge/bridge-submit.ts @@ -106,7 +106,6 @@ async function submitBridgeTransaction( { transactionId: transaction.id, messenger, - note: 'Add required transaction ID', }, (transactionMeta) => { if (!transactionMeta.requiredTransactionIds) { diff --git a/packages/transaction-pay-controller/src/strategy/relay/relay-submit.test.ts b/packages/transaction-pay-controller/src/strategy/relay/relay-submit.test.ts index b9fb77220c3..ea1ab724d2e 100644 --- a/packages/transaction-pay-controller/src/strategy/relay/relay-submit.test.ts +++ b/packages/transaction-pay-controller/src/strategy/relay/relay-submit.test.ts @@ -421,7 +421,6 @@ describe('Relay Submit Utils', () => { { transactionId: ORIGINAL_TRANSACTION_ID_MOCK, messenger, - note: expect.any(String), }, expect.any(Function), ); diff --git a/packages/transaction-pay-controller/src/strategy/relay/relay-submit.ts b/packages/transaction-pay-controller/src/strategy/relay/relay-submit.ts index dd58badeb0c..03c603ef7fa 100644 --- a/packages/transaction-pay-controller/src/strategy/relay/relay-submit.ts +++ b/packages/transaction-pay-controller/src/strategy/relay/relay-submit.ts @@ -77,7 +77,6 @@ async function executeSingleQuote( { transactionId: transaction.id, messenger, - note: 'Remove nonce from skipped transaction', }, (tx) => { tx.txParams.nonce = undefined; @@ -94,7 +93,6 @@ async function executeSingleQuote( { transactionId: transaction.id, messenger, - note: 'Intent complete after Relay completion', }, (tx) => { tx.isIntentComplete = true; @@ -217,7 +215,6 @@ async function submitTransactions( { transactionId: parentTransactionId, messenger, - note: 'Add required transaction ID from Relay submission', }, (tx) => { if (!tx.requiredTransactionIds) { diff --git a/packages/transaction-pay-controller/src/utils/quotes.ts b/packages/transaction-pay-controller/src/utils/quotes.ts index e27f9208c05..fc447f5efa8 100644 --- a/packages/transaction-pay-controller/src/utils/quotes.ts +++ b/packages/transaction-pay-controller/src/utils/quotes.ts @@ -138,7 +138,6 @@ function syncTransaction({ { transactionId, messenger: messenger as never, - note: 'Update transaction pay data', }, (tx: TransactionMeta) => { tx.batchTransactions = batchTransactions; diff --git a/packages/transaction-pay-controller/src/utils/transaction.test.ts b/packages/transaction-pay-controller/src/utils/transaction.test.ts index bd6f7ddb6ee..511febb4639 100644 --- a/packages/transaction-pay-controller/src/utils/transaction.test.ts +++ b/packages/transaction-pay-controller/src/utils/transaction.test.ts @@ -198,7 +198,6 @@ describe('Transaction Utils', () => { { transactionId: TRANSACTION_ID_MOCK, messenger: messenger as never, - note: 'Test note', }, (draft) => { draft.txParams.from = '0x456'; @@ -212,7 +211,6 @@ describe('Transaction Utils', () => { from: '0x456', }), }), - 'Test note', ); }); @@ -226,7 +224,6 @@ describe('Transaction Utils', () => { { transactionId: TRANSACTION_ID_MOCK, messenger: messenger as never, - note: 'Test note', }, noop, ), diff --git a/packages/transaction-pay-controller/src/utils/transaction.ts b/packages/transaction-pay-controller/src/utils/transaction.ts index 6ccf4609fb4..466626643ab 100644 --- a/packages/transaction-pay-controller/src/utils/transaction.ts +++ b/packages/transaction-pay-controller/src/utils/transaction.ts @@ -172,11 +172,9 @@ export function updateTransaction( { transactionId, messenger, - note, }: { transactionId: string; messenger: TransactionPayControllerMessenger; - note: string; }, fn: (draft: TransactionMeta) => void, ) { @@ -190,11 +188,7 @@ export function updateTransaction( fn(newTransaction); - messenger.call( - 'TransactionController:updateTransaction', - newTransaction, - note, - ); + messenger.call('TransactionController:updateTransaction', newTransaction); } /** From 55daea2b3c772812e1b7ac8604eb48a3eeddab3c Mon Sep 17 00:00:00 2001 From: Vinicius Stevam Date: Tue, 9 Dec 2025 06:01:44 +0000 Subject: [PATCH 04/11] fix lint --- packages/transaction-controller/CHANGELOG.md | 1 + .../transaction-controller/src/TransactionController.test.ts | 3 +-- packages/transaction-pay-controller/src/utils/transaction.ts | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/transaction-controller/CHANGELOG.md b/packages/transaction-controller/CHANGELOG.md index 2a8ee5ddab1..c2b48d4c4a1 100644 --- a/packages/transaction-controller/CHANGELOG.md +++ b/packages/transaction-controller/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- **BREAKING**: Remove `history` and `sendFlowHistory` properties from `TransactionMeta` ([#7326](https://github.com/MetaMask/core/pull/7326)) - Bump `@metamask/remote-feature-flag-controller` from `^2.0.1` to `^3.0.0` ([#7309](https://github.com/MetaMask/core/pull/7309) ## [62.4.0] diff --git a/packages/transaction-controller/src/TransactionController.test.ts b/packages/transaction-controller/src/TransactionController.test.ts index 8f2dc665556..ff570c3663f 100644 --- a/packages/transaction-controller/src/TransactionController.test.ts +++ b/packages/transaction-controller/src/TransactionController.test.ts @@ -353,11 +353,10 @@ function waitForTransactionFinished( }); } -const MOCK_PREFERENCES = { state: { selectedAddress: 'foo' } }; const INFURA_PROJECT_ID = 'testinfuraid'; const HTTP_PROVIDERS = { sepolia: new HttpProvider('https://sepolia.infura.io/v3/sepolia-pid'), - // TODO: Investigate and address why tests break when mainet has a different INFURA_PROJECT_ID + // TODO: Investigate and address why tests break when mainnet has a different INFURA_PROJECT_ID mainnet: new HttpProvider( `https://mainnet.infura.io/v3/${INFURA_PROJECT_ID}`, ), diff --git a/packages/transaction-pay-controller/src/utils/transaction.ts b/packages/transaction-pay-controller/src/utils/transaction.ts index 466626643ab..56208aff616 100644 --- a/packages/transaction-pay-controller/src/utils/transaction.ts +++ b/packages/transaction-pay-controller/src/utils/transaction.ts @@ -165,7 +165,6 @@ export function waitForTransactionConfirmed( * @param request - Request object. * @param request.transactionId - ID of the transaction to update. * @param request.messenger - Controller messenger. - * @param request.note - Note describing the update. * @param fn - Function that applies updates to the transaction draft. */ export function updateTransaction( From a77f40734a63d50f5d2b02410fe0c122a61bed3e Mon Sep 17 00:00:00 2001 From: Vinicius Stevam Date: Tue, 9 Dec 2025 11:27:45 +0000 Subject: [PATCH 05/11] remove note from docs --- packages/transaction-controller/src/TransactionController.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/transaction-controller/src/TransactionController.ts b/packages/transaction-controller/src/TransactionController.ts index 5244bcd332d..c66ca74a7c5 100644 --- a/packages/transaction-controller/src/TransactionController.ts +++ b/packages/transaction-controller/src/TransactionController.ts @@ -337,7 +337,6 @@ export type TransactionControllerGetTransactionsAction = { * 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`; From 5b439b5b02349dbb347a0a213028a0c84ced9eda Mon Sep 17 00:00:00 2001 From: Vinicius Stevam Date: Thu, 11 Dec 2025 06:31:29 +0000 Subject: [PATCH 06/11] deprecate history and sendFlowHistory --- .../src/utils/transaction.test.ts | 6 ++ .../src/utils/transaction.ts | 2 +- .../src/TransactionController.test.ts | 7 +- .../src/TransactionController.ts | 75 ++++++++++++++--- .../TransactionControllerIntegration.test.ts | 3 + packages/transaction-controller/src/index.ts | 3 + packages/transaction-controller/src/types.ts | 82 ++++++++++++++++++- .../src/strategy/bridge/bridge-submit.ts | 1 + .../src/strategy/relay/relay-submit.test.ts | 81 ++++++++++++++++-- .../src/strategy/relay/relay-submit.ts | 63 +++++++++----- .../src/utils/quotes.ts | 1 + .../src/utils/transaction.test.ts | 3 + .../src/utils/transaction.ts | 9 +- 13 files changed, 293 insertions(+), 43 deletions(-) diff --git a/packages/bridge-status-controller/src/utils/transaction.test.ts b/packages/bridge-status-controller/src/utils/transaction.test.ts index b8255094b63..eb6e075e1ea 100644 --- a/packages/bridge-status-controller/src/utils/transaction.test.ts +++ b/packages/bridge-status-controller/src/utils/transaction.test.ts @@ -1851,6 +1851,7 @@ describe('Bridge Status Controller Transaction Utils', () => { id: 'tx1', type: TransactionType.swap, }), + 'Update tx type to swap', ); // Should update the approval transaction @@ -1859,6 +1860,7 @@ describe('Bridge Status Controller Transaction Utils', () => { id: 'tx2', type: TransactionType.swapApproval, }), + 'Update tx type to swapApproval', ); }); @@ -1893,6 +1895,7 @@ describe('Bridge Status Controller Transaction Utils', () => { id: 'tx1', type: TransactionType.swap, }), + 'Update tx type to swap', ); }); @@ -1926,6 +1929,7 @@ describe('Bridge Status Controller Transaction Utils', () => { id: 'tx1', type: TransactionType.swapApproval, }), + 'Update tx type to swapApproval', ); }); @@ -1963,6 +1967,7 @@ describe('Bridge Status Controller Transaction Utils', () => { id: 'tx1', type: TransactionType.bridge, }), + 'Update tx type to bridge', ); expect(mockUpdateTransactionFn).toHaveBeenCalledWith( @@ -1970,6 +1975,7 @@ describe('Bridge Status Controller Transaction Utils', () => { id: 'tx2', type: TransactionType.bridgeApproval, }), + 'Update tx type to bridgeApproval', ); }); diff --git a/packages/bridge-status-controller/src/utils/transaction.ts b/packages/bridge-status-controller/src/utils/transaction.ts index eedfe459c52..63248853447 100644 --- a/packages/bridge-status-controller/src/utils/transaction.ts +++ b/packages/bridge-status-controller/src/utils/transaction.ts @@ -440,7 +440,7 @@ export const findAndUpdateTransactionsInBatch = ({ if (txMeta) { const updatedTx = { ...txMeta, type: txType as TransactionType }; - updateTransactionFn(updatedTx); + updateTransactionFn(updatedTx, `Update tx type to ${txType}`); txBatch[ [TransactionType.bridgeApproval, TransactionType.swapApproval].includes( txType as TransactionType, diff --git a/packages/transaction-controller/src/TransactionController.test.ts b/packages/transaction-controller/src/TransactionController.test.ts index ff570c3663f..1b442f74a50 100644 --- a/packages/transaction-controller/src/TransactionController.test.ts +++ b/packages/transaction-controller/src/TransactionController.test.ts @@ -5812,6 +5812,7 @@ describe('TransactionController', () => { expect.objectContaining({ txParams: expect.objectContaining(paramsMock), }), + 'TransactionController#signTransaction - Update after sign', ); expect(transactionMeta.status).toBe(TransactionStatus.approved); @@ -5878,6 +5879,7 @@ describe('TransactionController', () => { expect.objectContaining({ txParams: expect.objectContaining(paramsMock), }), + 'TransactionController#signTransaction - Update after sign', ); }); @@ -6391,7 +6393,7 @@ describe('TransactionController', () => { it('throws if custodial transaction does not exists', async () => { const nonExistentId = 'nonExistentId'; - const newStatus = TransactionStatus.approved; + const newStatus = TransactionStatus.approved as const; const { controller } = setupController(); expect(() => @@ -6405,7 +6407,7 @@ describe('TransactionController', () => { }); it('throws if status is invalid', async () => { - const newStatus = TransactionStatus.approved; + const newStatus = TransactionStatus.approved as const; const { controller } = setupController({ options: { state: { @@ -8116,6 +8118,7 @@ describe('TransactionController', () => { 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 c66ca74a7c5..d14725d7a8a 100644 --- a/packages/transaction-controller/src/TransactionController.ts +++ b/packages/transaction-controller/src/TransactionController.ts @@ -337,6 +337,7 @@ export type TransactionControllerGetTransactionsAction = { * Updates an existing transaction in state. * * @param transactionMeta - The new transaction to store in state. + * @param note - A note or update reason to be logged. */ export type TransactionControllerUpdateTransactionAction = { type: `${typeof controllerName}:updateTransaction`; @@ -410,6 +411,12 @@ export type PendingTransactionOptions = { /** TransactionController constructor options. */ export type TransactionControllerOptions = { + /** @deprecated Whether to disable storing history in transaction metadata. */ + disableHistory: boolean; + + /** @deprecated Explicitly disable transaction metadata history. */ + disableSendFlowHistory: boolean; + /** Whether to disable additional processing on swaps transactions. */ disableSwaps: boolean; @@ -497,6 +504,9 @@ export type TransactionControllerOptions = { testGasFeeFlows?: boolean; trace?: TraceCallback; + /** @deprecated Transaction history limit. */ + transactionHistoryLimit: number; + /** The controller hooks. */ hooks: { /** Additional logic to execute after adding a transaction. */ @@ -899,6 +909,8 @@ export class TransactionController extends BaseController< readonly #trace: TraceCallback; + // readonly #transactionHistoryLimit: number; + /** * Constructs a TransactionController. * @@ -906,6 +918,8 @@ export class TransactionController extends BaseController< */ constructor(options: TransactionControllerOptions) { const { + disableHistory, + disableSendFlowHistory, disableSwaps, getCurrentAccountEIP1559Compatibility, getCurrentNetworkEIP1559Compatibility, @@ -930,6 +944,7 @@ export class TransactionController extends BaseController< state, testGasFeeFlows, trace, + transactionHistoryLimit = 40, } = options; super({ @@ -1737,13 +1752,16 @@ export class TransactionController extends BaseController< * Updates an existing transaction in state. * * @param transactionMeta - The new transaction to store in state. + * @param note - A note or update reason to be logged. */ - updateTransaction(transactionMeta: TransactionMeta) { + updateTransaction(transactionMeta: TransactionMeta, note: string) { const { id: transactionId } = transactionMeta; this.#updateTransactionInternal({ transactionId }, () => ({ ...transactionMeta, })); + + log(`Transaction ${transactionId} updated. ${note}`); } /** @@ -1771,7 +1789,10 @@ export class TransactionController extends BaseController< ...transactionMeta, securityAlertResponse, }; - this.updateTransaction(updatedTransactionMeta); + this.updateTransaction( + updatedTransactionMeta, + `${controllerName}:updatesecurityAlertResponse - securityAlertResponse updated`, + ); } /** @@ -1851,7 +1872,10 @@ export class TransactionController extends BaseController< this.#markNonceDuplicatesDropped(transactionId); // Update external provided transaction with updated gas values and confirmed status. - this.updateTransaction(updatedTransactionMeta); + this.updateTransaction( + updatedTransactionMeta, + `${controllerName}:confirmExternalTransaction - Add external transaction`, + ); this.#onTransactionStatusChange(updatedTransactionMeta); // Intentional given potential duration of process. @@ -2048,7 +2072,10 @@ export class TransactionController extends BaseController< // merge updated previous gas values with existing transaction meta const updatedMeta = merge({}, transactionMeta, transactionPreviousGas); - this.updateTransaction(updatedMeta); + this.updateTransaction( + updatedMeta, + `${controllerName}:updatePreviousGasParams - Previous gas values updated`, + ); return this.#getTransaction(transactionId) as TransactionMeta; } @@ -2159,7 +2186,10 @@ export class TransactionController extends BaseController< transactionMeta: updatedTransaction, }); - this.updateTransaction(updatedTransaction); + this.updateTransaction( + updatedTransaction, + `Update Editable Params for ${txId}`, + ); return this.#getTransaction(txId); } @@ -2332,7 +2362,10 @@ export class TransactionController extends BaseController< delete updatedTransactionMeta.txParams.maxPriorityFeePerGas; } - this.updateTransaction(updatedTransactionMeta); + this.updateTransaction( + updatedTransactionMeta, + `${controllerName}:updateCustodialTransaction - Custodial transaction updated`, + ); if ( status && @@ -2800,7 +2833,10 @@ export class TransactionController extends BaseController< }); } - this.updateTransaction(updatedTransactionMeta); + this.updateTransaction( + updatedTransactionMeta, + 'Generated from user operation', + ); this.messenger.publish('TransactionController:transactionStatusUpdated', { transactionMeta: updatedTransactionMeta, @@ -2955,7 +2991,10 @@ export class TransactionController extends BaseController< params: updatedTransaction.txParams, }); - this.updateTransaction(updatedTransaction); + this.updateTransaction( + updatedTransaction, + 'TransactionController#processApproval - Updated with approval data', + ); } } @@ -3586,7 +3625,10 @@ export class TransactionController extends BaseController< this.messenger.publish(`${controllerName}:transactionDropped`, { transactionMeta: updatedTransactionMeta, }); - this.updateTransaction(updatedTransactionMeta); + this.updateTransaction( + updatedTransactionMeta, + 'TransactionController#setTransactionStatusDropped - Transaction dropped', + ); this.#onTransactionStatusChange(updatedTransactionMeta); } @@ -3739,7 +3781,10 @@ export class TransactionController extends BaseController< const transactionMetaFromHook = cloneDeep(finalTransactionMeta); if (!this.#afterSign(transactionMetaFromHook, signedTx)) { - this.updateTransaction(transactionMetaFromHook); + this.updateTransaction( + transactionMetaFromHook, + 'TransactionController#signTransaction - Update after sign', + ); log('Skipping signed status based on hook'); @@ -3752,7 +3797,10 @@ export class TransactionController extends BaseController< txParams: finalTxParams, }; - this.updateTransaction(transactionMetaWithRsv); + this.updateTransaction( + transactionMetaWithRsv, + 'TransactionController#approveTransaction - Transaction signed', + ); this.#onTransactionStatusChange(transactionMetaWithRsv); @@ -3762,7 +3810,10 @@ export class TransactionController extends BaseController< rawTx, }); - this.updateTransaction(transactionMetaWithRawTx); + this.updateTransaction( + transactionMetaWithRawTx, + 'TransactionController#approveTransaction - RawTransaction added', + ); return rawTx; } diff --git a/packages/transaction-controller/src/TransactionControllerIntegration.test.ts b/packages/transaction-controller/src/TransactionControllerIntegration.test.ts index 3d51207d8be..1d735feac03 100644 --- a/packages/transaction-controller/src/TransactionControllerIntegration.test.ts +++ b/packages/transaction-controller/src/TransactionControllerIntegration.test.ts @@ -273,6 +273,8 @@ const setupController = async ( ); const options: TransactionControllerOptions = { + disableHistory: false, + disableSendFlowHistory: false, disableSwaps: false, isAutomaticGasFeeUpdateEnabled: () => true, getCurrentNetworkEIP1559Compatibility: async ( @@ -293,6 +295,7 @@ const setupController = async ( isResubmitEnabled: () => false, }, sign: async (transaction: TypedTransaction) => transaction, + transactionHistoryLimit: 40, ...givenOptions, }; diff --git a/packages/transaction-controller/src/index.ts b/packages/transaction-controller/src/index.ts index b817f486f90..66a09873d03 100644 --- a/packages/transaction-controller/src/index.ts +++ b/packages/transaction-controller/src/index.ts @@ -77,6 +77,7 @@ export type { SavedGasFees, SecurityAlertResponse, SecurityProviderRequest, + SendFlowHistoryEntry, SimulationBalanceChange, SimulationData, SimulationError, @@ -86,6 +87,8 @@ export type { TransactionBatchRequest, TransactionBatchResult, TransactionError, + TransactionHistory, + TransactionHistoryEntry, TransactionMeta, TransactionParams, TransactionReceipt, diff --git a/packages/transaction-controller/src/types.ts b/packages/transaction-controller/src/types.ts index b12939dbbbe..52c24f34b12 100644 --- a/packages/transaction-controller/src/types.ts +++ b/packages/transaction-controller/src/types.ts @@ -1,12 +1,32 @@ +/* eslint-disable @typescript-eslint/naming-convention */ + import type { AccessList } from '@ethereumjs/tx'; import type { AccountsController } from '@metamask/accounts-controller'; import type EthQuery from '@metamask/eth-query'; import type { GasFeeState } from '@metamask/gas-fee-controller'; import type { NetworkClientId, Provider } from '@metamask/network-controller'; -import type { Hex } from '@metamask/utils'; +import type { Hex, Json } from '@metamask/utils'; +import type { Operation } from 'fast-json-patch'; import type { TransactionControllerMessenger } from './TransactionController'; +/** + * Given a record, ensures that each property matches the `Json` type. + */ +type MakeJsonCompatible = T extends Json + ? T + : { + [K in keyof T]: T[K] extends Json ? T[K] : never; + }; + +/** + * `Json` from `@metamask/utils` is defined as a recursive type alias, but + * `Operation` is defined as an interface, and the two are not compatible with + * each other. Therefore, this is a variant of Operation from `fast-json-patch` + * which is guaranteed to be type-compatible with `Json`. + */ +type JsonCompatibleOperation = MakeJsonCompatible; + /** * Information about a single transaction such as status and block number. */ @@ -231,6 +251,11 @@ export type TransactionMeta = { */ hash?: string; + /** + * @deprecated A history of mutations to TransactionMeta. + */ + history?: TransactionHistory; + /** * Generated UUID associated with this transaction. */ @@ -393,6 +418,12 @@ export type TransactionMeta = { */ selectedGasFeeToken?: Hex; + /** + * @deprecated An array of entries that describe the user's journey through the send flow. + * This is purely attached to state logs for troubleshooting and support. + */ + sendFlowHistory?: SendFlowHistoryEntry[]; + /** * Simulation data for the transaction used to predict its outcome. */ @@ -570,6 +601,19 @@ export type TransactionBatchMeta = { transactions?: NestedTransactionMetadata[]; }; +/** @deprecated An entry in the send flow history. */ +export type SendFlowHistoryEntry = { + /** + * String to indicate user interaction information. + */ + entry: string; + + /** + * Timestamp associated with this entry. + */ + timestamp: number; +}; + /** * Represents the status of a transaction within the wallet. * Each status reflects the state of the transaction internally, @@ -755,6 +799,11 @@ export enum TransactionType { */ predictWithdraw = 'predictWithdraw', + /** + * Deposit funds for Relay quote. + */ + relayDeposit = 'relayDeposit', + /** * When a transaction is failed it can be retried by * resubmitting the same transaction with a higher gas fee. This type is also used @@ -1111,6 +1160,31 @@ export interface SavedGasFees { priorityFee: string; } +/** + * A transaction history operation that includes a note and timestamp. + */ +type ExtendedHistoryOperation = JsonCompatibleOperation & { + note?: string; + timestamp?: number; +}; + +/** + * @deprecated A transaction history entry that includes the ExtendedHistoryOperation as the first element. + */ +export type TransactionHistoryEntry = [ + ExtendedHistoryOperation, + ...JsonCompatibleOperation[], +]; + +/** + * @deprecated A transaction history that includes the transaction meta as the first element. + * And the rest of the elements are the operation arrays that were applied to the transaction meta. + */ +export type TransactionHistory = [ + TransactionMeta, + ...TransactionHistoryEntry[], +]; + /** * Result of inferring the transaction type. */ @@ -1671,6 +1745,9 @@ export type TransactionBatchRequest = { /** Address of an ERC-20 token to pay for the gas fee, if the user has insufficient native balance. */ gasFeeToken?: Hex; + /** Gas limit for the transaction batch if submitted via EIP-7702. */ + gasLimit7702?: Hex; + /** Whether MetaMask will be compensated for the gas fee by the transaction. */ isGasFeeIncluded?: boolean; @@ -2045,6 +2122,9 @@ export type AddTransactionOptions = { /** Response from security validator. */ securityAlertResponse?: SecurityAlertResponse; + /** Entries to add to the `sendFlowHistory`. */ + sendFlowHistory?: SendFlowHistoryEntry[]; + /** Whether to skip the initial gas calculation and rely only on the polling. */ skipInitialGasEstimate?: boolean; diff --git a/packages/transaction-pay-controller/src/strategy/bridge/bridge-submit.ts b/packages/transaction-pay-controller/src/strategy/bridge/bridge-submit.ts index b89a7b6bef3..770ae26b5d1 100644 --- a/packages/transaction-pay-controller/src/strategy/bridge/bridge-submit.ts +++ b/packages/transaction-pay-controller/src/strategy/bridge/bridge-submit.ts @@ -106,6 +106,7 @@ async function submitBridgeTransaction( { transactionId: transaction.id, messenger, + note: 'Add required transaction ID', }, (transactionMeta) => { if (!transactionMeta.requiredTransactionIds) { diff --git a/packages/transaction-pay-controller/src/strategy/relay/relay-submit.test.ts b/packages/transaction-pay-controller/src/strategy/relay/relay-submit.test.ts index 4ccea41bc8c..adce74ba804 100644 --- a/packages/transaction-pay-controller/src/strategy/relay/relay-submit.test.ts +++ b/packages/transaction-pay-controller/src/strategy/relay/relay-submit.test.ts @@ -1,8 +1,4 @@ -import { - ORIGIN_METAMASK, - successfulFetch, - toHex, -} from '@metamask/controller-utils'; +import { ORIGIN_METAMASK, successfulFetch } from '@metamask/controller-utils'; import { TransactionType } from '@metamask/transaction-controller'; import type { TransactionMeta } from '@metamask/transaction-controller'; import type { Hex } from '@metamask/utils'; @@ -60,6 +56,9 @@ const ORIGINAL_QUOTE_MOCK = { }, }, }, + metamask: { + gasLimits: [21000, 21000], + }, request: {}, steps: [ { @@ -186,6 +185,7 @@ describe('Relay Submit Utils', () => { networkClientId: NETWORK_CLIENT_ID_MOCK, origin: ORIGIN_METAMASK, requireApproval: false, + type: TransactionType.relayDeposit, }, ); }); @@ -296,6 +296,9 @@ describe('Relay Submit Utils', () => { expect(addTransactionBatchMock).toHaveBeenCalledTimes(1); expect(addTransactionBatchMock).toHaveBeenCalledWith({ + disable7702: true, + disableHook: false, + disableSequential: false, from: FROM_MOCK, networkClientId: NETWORK_CLIENT_ID_MOCK, origin: ORIGIN_METAMASK, @@ -322,6 +325,7 @@ describe('Relay Submit Utils', () => { to: '0xfedcb', value: '0x4d2', }, + type: TransactionType.relayDeposit, }, ], }); @@ -356,7 +360,7 @@ describe('Relay Submit Utils', () => { expect(addTransactionMock).toHaveBeenCalledTimes(1); expect(addTransactionMock).toHaveBeenCalledWith( expect.objectContaining({ - gas: toHex(123), + gas: '0x5208', value: '0x0', }), expect.anything(), @@ -425,6 +429,7 @@ describe('Relay Submit Utils', () => { { transactionId: ORIGINAL_TRANSACTION_ID_MOCK, messenger, + note: expect.any(String), }, expect.any(Function), ); @@ -468,5 +473,69 @@ describe('Relay Submit Utils', () => { TRANSACTION_META_MOCK.id, ]); }); + + it('adds transaction batch with single gasLimit7702', async () => { + request.quotes[0].original.steps[0].items.push({ + ...request.quotes[0].original.steps[0].items[0], + }); + + request.quotes[0].original.metamask.gasLimits = [42000]; + + await submitRelayQuotes(request); + + expect(addTransactionBatchMock).toHaveBeenCalledTimes(1); + expect(addTransactionBatchMock).toHaveBeenCalledWith( + expect.objectContaining({ + disable7702: false, + disableHook: true, + disableSequential: true, + gasLimit7702: '0xa410', + transactions: [ + expect.objectContaining({ + params: expect.objectContaining({ + gas: undefined, + }), + }), + expect.objectContaining({ + params: expect.objectContaining({ + gas: undefined, + }), + }), + ], + }), + ); + }); + + it('adds transaction batch without gasLimit7702 when multiple gas limits', async () => { + request.quotes[0].original.steps[0].items.push({ + ...request.quotes[0].original.steps[0].items[0], + }); + + request.quotes[0].original.metamask.gasLimits = [21000, 22000]; + + await submitRelayQuotes(request); + + expect(addTransactionBatchMock).toHaveBeenCalledTimes(1); + expect(addTransactionBatchMock).toHaveBeenCalledWith( + expect.objectContaining({ + disable7702: true, + disableHook: false, + disableSequential: false, + gasLimit7702: undefined, + transactions: [ + expect.objectContaining({ + params: expect.objectContaining({ + gas: '0x5208', + }), + }), + expect.objectContaining({ + params: expect.objectContaining({ + gas: '0x55f0', + }), + }), + ], + }), + ); + }); }); }); diff --git a/packages/transaction-pay-controller/src/strategy/relay/relay-submit.ts b/packages/transaction-pay-controller/src/strategy/relay/relay-submit.ts index e3a97487233..30a18d54df2 100644 --- a/packages/transaction-pay-controller/src/strategy/relay/relay-submit.ts +++ b/packages/transaction-pay-controller/src/strategy/relay/relay-submit.ts @@ -70,13 +70,14 @@ async function executeSingleQuote( quote: TransactionPayQuote, messenger: TransactionPayControllerMessenger, transaction: TransactionMeta, -) { +): Promise<{ transactionHash?: Hex }> { log('Executing single quote', quote); updateTransaction( { transactionId: transaction.id, messenger, + note: 'Remove nonce from skipped transaction', }, (tx) => { tx.txParams.nonce = undefined; @@ -93,6 +94,7 @@ async function executeSingleQuote( { transactionId: transaction.id, messenger, + note: 'Intent complete after Relay completion', }, (tx) => { tx.isIntentComplete = true; @@ -180,14 +182,16 @@ async function submitTransactions( messenger: TransactionPayControllerMessenger, ): Promise { const { steps } = quote.original; - const params = steps.flatMap((s) => s.items).map((i) => i.data); - const invalidKind = steps.find((s) => s.kind !== 'transaction')?.kind; + const params = steps.flatMap((step) => step.items).map((item) => item.data); + const invalidKind = steps.find((step) => step.kind !== 'transaction')?.kind; if (invalidKind) { throw new Error(`Unsupported step kind: ${invalidKind}`); } - const normalizedParams = params.map((p) => normalizeParams(p, messenger)); + const normalizedParams = params.map((singleParams) => + normalizeParams(singleParams, messenger), + ); const transactionIds: string[] = []; const { from, sourceChainId, sourceTokenAddress } = quote.request; @@ -215,12 +219,10 @@ async function submitTransactions( { transactionId: parentTransactionId, messenger, + note: 'Add required transaction ID from Relay submission', }, (tx) => { - if (!tx.requiredTransactionIds) { - tx.requiredTransactionIds = []; - } - + tx.requiredTransactionIds ??= []; tx.requiredTransactionIds.push(transactionId); }, ); @@ -245,36 +247,57 @@ async function submitTransactions( })) : undefined; + const { gasLimits } = quote.original.metamask; + if (params.length === 1) { + const transactionParams = { + ...normalizedParams[0], + authorizationList, + gas: toHex(gasLimits[0]), + }; + result = await messenger.call( 'TransactionController:addTransaction', - { ...normalizedParams[0], authorizationList }, + transactionParams, { gasFeeToken, networkClientId, origin: ORIGIN_METAMASK, requireApproval: false, + type: TransactionType.relayDeposit, }, ); } else { + const gasLimit7702 = + gasLimits.length === 1 ? toHex(gasLimits[0]) : undefined; + + const transactions = normalizedParams.map((singleParams, index) => ({ + params: { + data: singleParams.data as Hex, + gas: gasLimit7702 ? undefined : toHex(gasLimits[index]), + maxFeePerGas: singleParams.maxFeePerGas as Hex, + maxPriorityFeePerGas: singleParams.maxPriorityFeePerGas as Hex, + to: singleParams.to as Hex, + value: singleParams.value as Hex, + }, + type: + index === 0 + ? TransactionType.tokenMethodApprove + : TransactionType.relayDeposit, + })); + await messenger.call('TransactionController:addTransactionBatch', { from, + disable7702: !gasLimit7702, + disableHook: Boolean(gasLimit7702), + disableSequential: Boolean(gasLimit7702), gasFeeToken, + gasLimit7702, networkClientId, origin: ORIGIN_METAMASK, overwriteUpgrade: true, requireApproval: false, - transactions: normalizedParams.map((p, i) => ({ - params: { - data: p.data as Hex, - gas: p.gas as Hex, - maxFeePerGas: p.maxFeePerGas as Hex, - maxPriorityFeePerGas: p.maxPriorityFeePerGas as Hex, - to: p.to as Hex, - value: p.value as Hex, - }, - type: i === 0 ? TransactionType.tokenMethodApprove : undefined, - })), + transactions, }); } diff --git a/packages/transaction-pay-controller/src/utils/quotes.ts b/packages/transaction-pay-controller/src/utils/quotes.ts index fc447f5efa8..e27f9208c05 100644 --- a/packages/transaction-pay-controller/src/utils/quotes.ts +++ b/packages/transaction-pay-controller/src/utils/quotes.ts @@ -138,6 +138,7 @@ function syncTransaction({ { transactionId, messenger: messenger as never, + note: 'Update transaction pay data', }, (tx: TransactionMeta) => { tx.batchTransactions = batchTransactions; diff --git a/packages/transaction-pay-controller/src/utils/transaction.test.ts b/packages/transaction-pay-controller/src/utils/transaction.test.ts index 511febb4639..bd6f7ddb6ee 100644 --- a/packages/transaction-pay-controller/src/utils/transaction.test.ts +++ b/packages/transaction-pay-controller/src/utils/transaction.test.ts @@ -198,6 +198,7 @@ describe('Transaction Utils', () => { { transactionId: TRANSACTION_ID_MOCK, messenger: messenger as never, + note: 'Test note', }, (draft) => { draft.txParams.from = '0x456'; @@ -211,6 +212,7 @@ describe('Transaction Utils', () => { from: '0x456', }), }), + 'Test note', ); }); @@ -224,6 +226,7 @@ describe('Transaction Utils', () => { { transactionId: TRANSACTION_ID_MOCK, messenger: messenger as never, + note: 'Test note', }, noop, ), diff --git a/packages/transaction-pay-controller/src/utils/transaction.ts b/packages/transaction-pay-controller/src/utils/transaction.ts index 56208aff616..6ccf4609fb4 100644 --- a/packages/transaction-pay-controller/src/utils/transaction.ts +++ b/packages/transaction-pay-controller/src/utils/transaction.ts @@ -165,15 +165,18 @@ export function waitForTransactionConfirmed( * @param request - Request object. * @param request.transactionId - ID of the transaction to update. * @param request.messenger - Controller messenger. + * @param request.note - Note describing the update. * @param fn - Function that applies updates to the transaction draft. */ export function updateTransaction( { transactionId, messenger, + note, }: { transactionId: string; messenger: TransactionPayControllerMessenger; + note: string; }, fn: (draft: TransactionMeta) => void, ) { @@ -187,7 +190,11 @@ export function updateTransaction( fn(newTransaction); - messenger.call('TransactionController:updateTransaction', newTransaction); + messenger.call( + 'TransactionController:updateTransaction', + newTransaction, + note, + ); } /** From d717276a6603f384906b869f24f53c5f0f126512 Mon Sep 17 00:00:00 2001 From: Vinicius Stevam Date: Thu, 11 Dec 2025 06:43:41 +0000 Subject: [PATCH 07/11] lint --- packages/transaction-controller/src/TransactionController.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/transaction-controller/src/TransactionController.ts b/packages/transaction-controller/src/TransactionController.ts index bca04451ee1..cf955edd722 100644 --- a/packages/transaction-controller/src/TransactionController.ts +++ b/packages/transaction-controller/src/TransactionController.ts @@ -939,8 +939,6 @@ export class TransactionController extends BaseController< */ constructor(options: TransactionControllerOptions) { const { - disableHistory, - disableSendFlowHistory, disableSwaps, getCurrentAccountEIP1559Compatibility, getCurrentNetworkEIP1559Compatibility, @@ -965,7 +963,6 @@ export class TransactionController extends BaseController< state, testGasFeeFlows, trace, - transactionHistoryLimit = 40, } = options; super({ From 87558a8a3a19ed573ab64adf2a0c4e69e1686311 Mon Sep 17 00:00:00 2001 From: Vinicius Stevam Date: Thu, 11 Dec 2025 13:55:55 +0000 Subject: [PATCH 08/11] update changelog --- packages/transaction-controller/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/transaction-controller/CHANGELOG.md b/packages/transaction-controller/CHANGELOG.md index 4cf5b7c0fbc..5e36ec77fe4 100644 --- a/packages/transaction-controller/CHANGELOG.md +++ b/packages/transaction-controller/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- Deprecate `history` and `sendFlowHistory` properties from `TransactionMeta` and `TransactionController` ([#7326](https://github.com/MetaMask/core/pull/7326)) +- Deprecate `history` and `sendFlowHistory` properties from `TransactionMeta` and `TransactionController` options ([#7326](https://github.com/MetaMask/core/pull/7326)) ## [62.6.0] @@ -34,6 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Use gas fee properties from first transaction in EIP-7702 transactions ([#7323](https://github.com/MetaMask/core/pull/7323)) - Bump `@metamask/remote-feature-flag-controller` from `^2.0.1` to `^3.0.0` ([#7309](https://github.com/MetaMask/core/pull/7309) ## [62.4.0] From cfaf78be1c7fd0c429c2fdc1d17ae53f1364ff85 Mon Sep 17 00:00:00 2001 From: Vinicius Stevam <45455812+vinistevam@users.noreply.github.com> Date: Fri, 12 Dec 2025 09:21:28 +0000 Subject: [PATCH 09/11] Update packages/transaction-controller/src/TransactionController.ts Co-authored-by: Matthew Walsh --- packages/transaction-controller/src/TransactionController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/transaction-controller/src/TransactionController.ts b/packages/transaction-controller/src/TransactionController.ts index cf955edd722..d0c49f155ec 100644 --- a/packages/transaction-controller/src/TransactionController.ts +++ b/packages/transaction-controller/src/TransactionController.ts @@ -1848,7 +1848,7 @@ export class TransactionController extends BaseController< ...transactionMeta, })); - log(`Transaction ${transactionId} updated. ${note}`); + log('Transaction updated', {transactionId, note}); } /** From dacc1e3609eef1f6827add9bb72333d2ac74fc4a Mon Sep 17 00:00:00 2001 From: Vinicius Stevam Date: Fri, 12 Dec 2025 09:44:19 +0000 Subject: [PATCH 10/11] applied suggestions --- .../src/TransactionController.test.ts | 24 ++++- .../src/TransactionController.ts | 92 +++++++++++++++++-- .../TransactionControllerIntegration.test.ts | 4 +- .../helpers/IncomingTransactionHelper.test.ts | 2 + .../src/helpers/IncomingTransactionHelper.ts | 12 ++- 5 files changed, 119 insertions(+), 15 deletions(-) diff --git a/packages/transaction-controller/src/TransactionController.test.ts b/packages/transaction-controller/src/TransactionController.test.ts index f208bdf0ad9..dd4667f9ec7 100644 --- a/packages/transaction-controller/src/TransactionController.test.ts +++ b/packages/transaction-controller/src/TransactionController.test.ts @@ -725,6 +725,7 @@ describe('TransactionController', () => { isEIP7702GasFeeTokensEnabled: isEIP7702GasFeeTokensEnabledMock, publicKeyEIP7702: '0x1234', sign: signMock, + transactionHistoryLimit: 40, ...givenOptions, }; @@ -1201,10 +1202,10 @@ describe('TransactionController', () => { expect( transactions.map((transaction) => [transaction.id, transaction.status]), ).toStrictEqual([ - ['123', TransactionStatus.failed], - ['111', TransactionStatus.failed], - ['222', TransactionStatus.confirmed], ['333', TransactionStatus.failed], + ['222', TransactionStatus.confirmed], + ['111', TransactionStatus.failed], + ['123', TransactionStatus.failed], ]); }); @@ -4848,6 +4849,23 @@ describe('TransactionController', () => { ]); }); + it('limits max transactions when adding to state', async () => { + const { controller } = setupController({ + options: { transactionHistoryLimit: 1 }, + }); + + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await (incomingTransactionHelperMock.hub.on as any).mock.calls[0][1]([ + TRANSACTION_META_MOCK, + TRANSACTION_META_2_MOCK, + ]); + + expect(controller.state.transactions).toStrictEqual([ + { ...TRANSACTION_META_2_MOCK, networkClientId: NETWORK_CLIENT_ID_MOCK }, + ]); + }); + it('publishes TransactionController:incomingTransactionsReceived', async () => { const listener = jest.fn(); diff --git a/packages/transaction-controller/src/TransactionController.ts b/packages/transaction-controller/src/TransactionController.ts index cf955edd722..2374bc1e97e 100644 --- a/packages/transaction-controller/src/TransactionController.ts +++ b/packages/transaction-controller/src/TransactionController.ts @@ -19,6 +19,7 @@ import { query, ApprovalType, ORIGIN_METAMASK, + convertHexToDecimal, } from '@metamask/controller-utils'; import type { TraceCallback, TraceContext } from '@metamask/controller-utils'; import EthQuery from '@metamask/eth-query'; @@ -120,6 +121,7 @@ import type { PublishHookResult, GetGasFeeTokensRequest, InternalAccount, + SendFlowHistoryEntry, } from './types'; import { GasFeeEstimateLevel, @@ -424,10 +426,10 @@ export type PendingTransactionOptions = { /** TransactionController constructor options. */ export type TransactionControllerOptions = { - /** @deprecated Whether to disable storing history in transaction metadata. */ + /** @deprecated No longer used — kept only for backward compatibility. */ disableHistory: boolean; - /** @deprecated Explicitly disable transaction metadata history. */ + /** @deprecated No longer used — kept only for backward compatibility. */ disableSendFlowHistory: boolean; /** Whether to disable additional processing on swaps transactions. */ @@ -517,7 +519,7 @@ export type TransactionControllerOptions = { testGasFeeFlows?: boolean; trace?: TraceCallback; - /** @deprecated Transaction history limit. */ + /** Transaction history limit. */ transactionHistoryLimit: number; /** The controller hooks. */ @@ -930,7 +932,7 @@ export class TransactionController extends BaseController< readonly #trace: TraceCallback; - // readonly #transactionHistoryLimit: number; + readonly #transactionHistoryLimit: number; /** * Constructs a TransactionController. @@ -963,6 +965,7 @@ export class TransactionController extends BaseController< state, testGasFeeFlows, trace, + transactionHistoryLimit = 40, } = options; super({ @@ -1035,6 +1038,7 @@ export class TransactionController extends BaseController< this.#sign = sign; this.#testGasFeeFlows = testGasFeeFlows === true; this.#trace = trace ?? (((_request, fn) => fn?.()) as TraceCallback); + this.#transactionHistoryLimit = transactionHistoryLimit; const findNetworkClientIdByChainId = (chainId: Hex): string => { return this.messenger.call( @@ -1118,6 +1122,7 @@ export class TransactionController extends BaseController< isEnabled: this.#incomingTransactionOptions.isEnabled, messenger: this.messenger, remoteTransactionSource: new AccountsApiRemoteTransactionSource(), + trimTransactions: this.#trimTransactionsForState.bind(this), updateTransactions: this.#incomingTransactionOptions.updateTransactions, }); @@ -1923,10 +1928,26 @@ export class TransactionController extends BaseController< ); this.update((state) => { - state.transactions = newTransactions; + state.transactions = this.#trimTransactionsForState(newTransactions); }); } + /** + * @deprecated No longer used. Kept only to avoid breaking changes. It now performs no operations. + * @param transactionID - The ID of the transaction to update. + * @param _currentSendFlowHistoryLength - The length of the current sendFlowHistory array. + * @param _sendFlowHistoryToAdd - The sendFlowHistory entries to add. + * @returns The transactionMeta. + */ + updateTransactionSendFlowHistory( + transactionID: string, + _currentSendFlowHistoryLength: number, + _sendFlowHistoryToAdd: SendFlowHistoryEntry[], + ): TransactionMeta { + // Return the transaction unchanged + return this.#getTransaction(transactionID) as TransactionMeta; + } + /** * Adds external provided transaction to state as confirmed transaction. * @@ -2686,7 +2707,7 @@ export class TransactionController extends BaseController< ({ status }) => status !== TransactionStatus.unapproved, ); this.update((state) => { - state.transactions = transactions; + state.transactions = this.#trimTransactionsForState(transactions); }); } @@ -2935,7 +2956,10 @@ export class TransactionController extends BaseController< #addMetadata(transactionMeta: TransactionMeta): void { validateTxParams(transactionMeta.txParams); this.update((state) => { - state.transactions = [...state.transactions, transactionMeta]; + state.transactions = this.#trimTransactionsForState([ + ...state.transactions, + transactionMeta, + ]); }); } @@ -3587,7 +3611,10 @@ export class TransactionController extends BaseController< this.update((state) => { const { transactions: currentTransactions } = state; - state.transactions = [...finalTransactions, ...currentTransactions]; + state.transactions = this.#trimTransactionsForState([ + ...finalTransactions, + ...currentTransactions, + ]); log( 'Added incoming transactions to state', @@ -3746,6 +3773,53 @@ export class TransactionController extends BaseController< this.#onTransactionStatusChange(updatedTransactionMeta); } + /** + * Trim the amount of transactions that are set on the state. Checks + * if the length of the tx history is longer then desired persistence + * limit and then if it is removes the oldest confirmed or rejected tx. + * Pending or unapproved transactions will not be removed by this + * operation. For safety of presenting a fully functional transaction UI + * representation, this function will not break apart transactions with the + * same nonce, created on the same day, per network. Not accounting for + * transactions of the same nonce, same day and network combo can result in + * confusing or broken experiences in the UI. + * + * @param transactions - The transactions to be applied to the state. + * @returns The trimmed list of transactions. + */ + #trimTransactionsForState( + transactions: TransactionMeta[], + ): TransactionMeta[] { + const nonceNetworkSet = new Set(); + + const txsToKeep = [...transactions] + .sort((a, b) => (a.time > b.time ? -1 : 1)) // Descending time order + .filter((tx) => { + const { chainId, status, txParams, time } = tx; + + if (txParams) { + const key = `${String(txParams.nonce)}-${convertHexToDecimal( + chainId, + )}-${new Date(time).toDateString()}`; + + if (nonceNetworkSet.has(key)) { + return true; + } else if ( + nonceNetworkSet.size < this.#transactionHistoryLimit || + !this.#isFinalState(status) + ) { + nonceNetworkSet.add(key); + return true; + } + } + + return false; + }); + + txsToKeep.reverse(); // Ascending time order + return txsToKeep; + } + /** * Get transaction with provided actionId. * @@ -4547,7 +4621,7 @@ export class TransactionController extends BaseController< ({ id }) => id !== transactionId, ); - state.transactions = transactions; + state.transactions = this.#trimTransactionsForState(transactions); }); } diff --git a/packages/transaction-controller/src/TransactionControllerIntegration.test.ts b/packages/transaction-controller/src/TransactionControllerIntegration.test.ts index 0374c5075cc..7f400d60f27 100644 --- a/packages/transaction-controller/src/TransactionControllerIntegration.test.ts +++ b/packages/transaction-controller/src/TransactionControllerIntegration.test.ts @@ -662,13 +662,13 @@ describe('TransactionController Integration', () => { 'confirmed', ); expect( - transactionController.state.transactions[1].networkClientId, + transactionController.state.transactions[0].networkClientId, ).toBe('sepolia'); expect(transactionController.state.transactions[1].status).toBe( 'confirmed', ); expect( - transactionController.state.transactions[0].networkClientId, + transactionController.state.transactions[1].networkClientId, ).toBe('linea-sepolia'); transactionController.destroy(); }); diff --git a/packages/transaction-controller/src/helpers/IncomingTransactionHelper.test.ts b/packages/transaction-controller/src/helpers/IncomingTransactionHelper.test.ts index f7fc5cf1f29..9cd2f9ecf97 100644 --- a/packages/transaction-controller/src/helpers/IncomingTransactionHelper.test.ts +++ b/packages/transaction-controller/src/helpers/IncomingTransactionHelper.test.ts @@ -44,6 +44,7 @@ const CONTROLLER_ARGS_MOCK: ConstructorParameters< getLocalTransactions: () => [], messenger: MESSENGER_MOCK, remoteTransactionSource: {} as RemoteTransactionSource, + trimTransactions: (transactions) => transactions, }; const TRANSACTION_MOCK: TransactionMeta = { @@ -319,6 +320,7 @@ describe('IncomingTransactionHelper', () => { it('does not if all unique transactions are truncated', async () => { const helper = new IncomingTransactionHelper({ ...CONTROLLER_ARGS_MOCK, + trimTransactions: (): TransactionMeta[] => [], remoteTransactionSource: createRemoteTransactionSourceMock([ TRANSACTION_MOCK, ]), diff --git a/packages/transaction-controller/src/helpers/IncomingTransactionHelper.ts b/packages/transaction-controller/src/helpers/IncomingTransactionHelper.ts index 76aac9d7c4a..180fc3dd034 100644 --- a/packages/transaction-controller/src/helpers/IncomingTransactionHelper.ts +++ b/packages/transaction-controller/src/helpers/IncomingTransactionHelper.ts @@ -53,6 +53,10 @@ export class IncomingTransactionHelper { #timeoutId?: unknown; + readonly #trimTransactions: ( + transactions: TransactionMeta[], + ) => TransactionMeta[]; + readonly #updateTransactions?: boolean; constructor({ @@ -63,6 +67,7 @@ export class IncomingTransactionHelper { isEnabled, messenger, remoteTransactionSource, + trimTransactions, updateTransactions, }: { client?: string; @@ -74,6 +79,7 @@ export class IncomingTransactionHelper { isEnabled?: () => boolean; messenger: TransactionControllerMessenger; remoteTransactionSource: RemoteTransactionSource; + trimTransactions: (transactions: TransactionMeta[]) => TransactionMeta[]; updateTransactions?: boolean; }) { this.hub = new EventEmitter(); @@ -87,6 +93,7 @@ export class IncomingTransactionHelper { this.#isUpdating = false; this.#messenger = messenger; this.#remoteTransactionSource = remoteTransactionSource; + this.#trimTransactions = trimTransactions; this.#updateTransactions = updateTransactions; } @@ -222,7 +229,10 @@ export class IncomingTransactionHelper { uniqueTransactions, ); - const trimmedTransactions = [...uniqueTransactions, ...localTransactions]; + const trimmedTransactions = this.#trimTransactions([ + ...uniqueTransactions, + ...localTransactions, + ]); const uniqueTransactionIds = uniqueTransactions.map((tx) => tx.id); From 0e329182393b41fe80fb7ce5b03051d7d19b6a93 Mon Sep 17 00:00:00 2001 From: Vinicius Stevam Date: Fri, 12 Dec 2025 10:36:03 +0000 Subject: [PATCH 11/11] fix lint --- .../src/TransactionController.ts | 103 +++++++++--------- 1 file changed, 53 insertions(+), 50 deletions(-) diff --git a/packages/transaction-controller/src/TransactionController.ts b/packages/transaction-controller/src/TransactionController.ts index c46e5d24d1d..b191e744707 100644 --- a/packages/transaction-controller/src/TransactionController.ts +++ b/packages/transaction-controller/src/TransactionController.ts @@ -90,6 +90,7 @@ import type { Layer1GasFeeFlow, SavedGasFees, SecurityProviderRequest, + SendFlowHistoryEntry, TransactionParams, TransactionMeta, TransactionReceipt, @@ -121,7 +122,6 @@ import type { PublishHookResult, GetGasFeeTokensRequest, InternalAccount, - SendFlowHistoryEntry, } from './types'; import { GasFeeEstimateLevel, @@ -1853,7 +1853,7 @@ export class TransactionController extends BaseController< ...transactionMeta, })); - log('Transaction updated', {transactionId, note}); + log('Transaction updated', { transactionId, note }); } /** @@ -3439,6 +3439,53 @@ export class TransactionController extends BaseController< this.#onTransactionStatusChange(updatedTransactionMeta); } + /** + * Trim the amount of transactions that are set on the state. Checks + * if the length of the tx history is longer then desired persistence + * limit and then if it is removes the oldest confirmed or rejected tx. + * Pending or unapproved transactions will not be removed by this + * operation. For safety of presenting a fully functional transaction UI + * representation, this function will not break apart transactions with the + * same nonce, created on the same day, per network. Not accounting for + * transactions of the same nonce, same day and network combo can result in + * confusing or broken experiences in the UI. + * + * @param transactions - The transactions to be applied to the state. + * @returns The trimmed list of transactions. + */ + #trimTransactionsForState( + transactions: TransactionMeta[], + ): TransactionMeta[] { + const nonceNetworkSet = new Set(); + + const txsToKeep = [...transactions] + .sort((a, b) => (a.time > b.time ? -1 : 1)) // Descending time order + .filter((tx) => { + const { chainId, status, txParams, time } = tx; + + if (txParams) { + const key = `${String(txParams.nonce)}-${convertHexToDecimal( + chainId, + )}-${new Date(time).toDateString()}`; + + if (nonceNetworkSet.has(key)) { + return true; + } else if ( + nonceNetworkSet.size < this.#transactionHistoryLimit || + !this.#isFinalState(status) + ) { + nonceNetworkSet.add(key); + return true; + } + } + + return false; + }); + + txsToKeep.reverse(); // Ascending time order + return txsToKeep; + } + /** * Determines if the transaction is in a final state. * @@ -3696,7 +3743,10 @@ export class TransactionController extends BaseController< ); this.update((state) => { - state.transactions = [...state.transactions, transactionMeta]; + state.transactions = this.#trimTransactionsForState([ + ...state.transactions, + transactionMeta, + ]); }); return transactionMeta; @@ -3773,53 +3823,6 @@ export class TransactionController extends BaseController< this.#onTransactionStatusChange(updatedTransactionMeta); } - /** - * Trim the amount of transactions that are set on the state. Checks - * if the length of the tx history is longer then desired persistence - * limit and then if it is removes the oldest confirmed or rejected tx. - * Pending or unapproved transactions will not be removed by this - * operation. For safety of presenting a fully functional transaction UI - * representation, this function will not break apart transactions with the - * same nonce, created on the same day, per network. Not accounting for - * transactions of the same nonce, same day and network combo can result in - * confusing or broken experiences in the UI. - * - * @param transactions - The transactions to be applied to the state. - * @returns The trimmed list of transactions. - */ - #trimTransactionsForState( - transactions: TransactionMeta[], - ): TransactionMeta[] { - const nonceNetworkSet = new Set(); - - const txsToKeep = [...transactions] - .sort((a, b) => (a.time > b.time ? -1 : 1)) // Descending time order - .filter((tx) => { - const { chainId, status, txParams, time } = tx; - - if (txParams) { - const key = `${String(txParams.nonce)}-${convertHexToDecimal( - chainId, - )}-${new Date(time).toDateString()}`; - - if (nonceNetworkSet.has(key)) { - return true; - } else if ( - nonceNetworkSet.size < this.#transactionHistoryLimit || - !this.#isFinalState(status) - ) { - nonceNetworkSet.add(key); - return true; - } - } - - return false; - }); - - txsToKeep.reverse(); // Ascending time order - return txsToKeep; - } - /** * Get transaction with provided actionId. *