From 56de8165790185b0c8599e4393b28dbd0d2df14a Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Mon, 13 Apr 2026 01:49:05 +0100 Subject: [PATCH 1/5] feat: skip simulation when enforced simulations container is active - Check containerTypes for EnforcedSimulations to skip balance change simulation - Re-check containerTypes in update callback to handle race conditions - Use fresh state in callback to avoid stale closure when toggling off --- packages/transaction-controller/CHANGELOG.md | 1 + .../src/TransactionController.test.ts | 31 +++++++++++++++++++ .../src/TransactionController.ts | 18 +++++++++-- 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/packages/transaction-controller/CHANGELOG.md b/packages/transaction-controller/CHANGELOG.md index aa09687e4c2..144fac96125 100644 --- a/packages/transaction-controller/CHANGELOG.md +++ b/packages/transaction-controller/CHANGELOG.md @@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Skip simulation when transaction `containerTypes` includes `EnforcedSimulations` ([#8431](https://github.com/MetaMask/core/pull/8431)) - Bump `@metamask/accounts-controller` from `^37.1.1` to `^37.2.0` ([#8363](https://github.com/MetaMask/core/pull/8363)) - Bump `@metamask/messenger` from `^1.0.0` to `^1.1.1` ([#8364](https://github.com/MetaMask/core/pull/8364), [#8373](https://github.com/MetaMask/core/pull/8373)) diff --git a/packages/transaction-controller/src/TransactionController.test.ts b/packages/transaction-controller/src/TransactionController.test.ts index a505259940e..ea4c95a0dea 100644 --- a/packages/transaction-controller/src/TransactionController.test.ts +++ b/packages/transaction-controller/src/TransactionController.test.ts @@ -2345,6 +2345,37 @@ describe('TransactionController', () => { }); }); + it('skips simulation when containerTypes includes EnforcedSimulations', async () => { + const { controller } = setupController(); + + const { transactionMeta } = await controller.addTransaction( + { + from: ACCOUNT_MOCK, + to: ACCOUNT_MOCK, + }, + { + networkClientId: NETWORK_CLIENT_ID_MOCK, + }, + ); + + await flushPromises(); + + expect(getBalanceChangesMock).toHaveBeenCalledTimes(1); + + shouldResimulateMock.mockReturnValue({ + blockTime: 123, + resimulate: true, + }); + + await controller.updateEditableParams(transactionMeta.id, { + containerTypes: [TransactionContainerType.EnforcedSimulations], + }); + + await flushPromises(); + + expect(getBalanceChangesMock).toHaveBeenCalledTimes(1); + }); + describe('with beforeSign hook', () => { it('calls beforeSign hook', async () => { const beforeSignHook = jest.fn().mockResolvedValueOnce({}); diff --git a/packages/transaction-controller/src/TransactionController.ts b/packages/transaction-controller/src/TransactionController.ts index 5493d4c6e5b..96b5ec9b7c7 100644 --- a/packages/transaction-controller/src/TransactionController.ts +++ b/packages/transaction-controller/src/TransactionController.ts @@ -122,7 +122,6 @@ import type { TransactionBatchMeta, AfterSimulateHook, BeforeSignHook, - TransactionContainerType, GetSimulationConfig, AddTransactionOptions, PublishHookResult, @@ -131,6 +130,7 @@ import type { } from './types'; import { GasFeeEstimateLevel, + TransactionContainerType, TransactionEnvelopeType, TransactionType, TransactionStatus, @@ -4253,6 +4253,7 @@ export class TransactionController extends BaseController< ): Promise { const { chainId, + containerTypes, id: transactionId, nestedTransactions, networkClientId, @@ -4272,7 +4273,10 @@ export class TransactionController extends BaseController< let isGasFeeSponsored = false; const isBalanceChangesSkipped = - this.#skipSimulationTransactionIds.has(transactionId); + this.#skipSimulationTransactionIds.has(transactionId) || + Boolean( + containerTypes?.includes(TransactionContainerType.EnforcedSimulations), + ); if (this.#isSimulationEnabled() && !isBalanceChangesSkipped) { const balanceChangesResult = await this.#trace( @@ -4336,7 +4340,15 @@ export class TransactionController extends BaseController< txMeta.isGasFeeSponsored = isGasFeeSponsored; txMeta.gasUsed = gasUsed; - if (!isBalanceChangesSkipped) { + const skipBalanceChanges = + this.#skipSimulationTransactionIds.has(transactionId) || + Boolean( + txMeta.containerTypes?.includes( + TransactionContainerType.EnforcedSimulations, + ), + ); + + if (!skipBalanceChanges) { txMeta.simulationData = simulationData; } }, From 1ee02df9723905445bfbe7a52ea9fdb312ec97fb Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Tue, 14 Apr 2026 09:46:47 +0100 Subject: [PATCH 2/5] refactor: extract #isBalanceChangesSkipped to remove duplication --- .../src/TransactionController.ts | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/packages/transaction-controller/src/TransactionController.ts b/packages/transaction-controller/src/TransactionController.ts index 96b5ec9b7c7..b78422231e7 100644 --- a/packages/transaction-controller/src/TransactionController.ts +++ b/packages/transaction-controller/src/TransactionController.ts @@ -4241,6 +4241,15 @@ export class TransactionController extends BaseController< return transactionMeta; } + #isBalanceChangesSkipped(transactionMeta: TransactionMeta): boolean { + return ( + this.#skipSimulationTransactionIds.has(transactionMeta.id) || + transactionMeta.containerTypes?.includes( + TransactionContainerType.EnforcedSimulations, + ) === true + ); + } + async #updateSimulationData( transactionMeta: TransactionMeta, { @@ -4253,7 +4262,6 @@ export class TransactionController extends BaseController< ): Promise { const { chainId, - containerTypes, id: transactionId, nestedTransactions, networkClientId, @@ -4273,10 +4281,7 @@ export class TransactionController extends BaseController< let isGasFeeSponsored = false; const isBalanceChangesSkipped = - this.#skipSimulationTransactionIds.has(transactionId) || - Boolean( - containerTypes?.includes(TransactionContainerType.EnforcedSimulations), - ); + this.#isBalanceChangesSkipped(transactionMeta); if (this.#isSimulationEnabled() && !isBalanceChangesSkipped) { const balanceChangesResult = await this.#trace( @@ -4340,15 +4345,7 @@ export class TransactionController extends BaseController< txMeta.isGasFeeSponsored = isGasFeeSponsored; txMeta.gasUsed = gasUsed; - const skipBalanceChanges = - this.#skipSimulationTransactionIds.has(transactionId) || - Boolean( - txMeta.containerTypes?.includes( - TransactionContainerType.EnforcedSimulations, - ), - ); - - if (!skipBalanceChanges) { + if (!this.#isBalanceChangesSkipped(txMeta)) { txMeta.simulationData = simulationData; } }, From 48f68d5f7fc7d97a9ec6ef21c27f8183c02b46d0 Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Tue, 14 Apr 2026 11:11:28 +0100 Subject: [PATCH 3/5] fix: restore changelog entry in Unreleased section after rebase --- packages/transaction-controller/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/transaction-controller/CHANGELOG.md b/packages/transaction-controller/CHANGELOG.md index 144fac96125..42ff6146e43 100644 --- a/packages/transaction-controller/CHANGELOG.md +++ b/packages/transaction-controller/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Skip simulation when transaction `containerTypes` includes `EnforcedSimulations` ([#8431](https://github.com/MetaMask/core/pull/8431)) - Preserve `submittedTime` if already set by a publish hook instead of overwriting it ([#8439](https://github.com/MetaMask/core/pull/8439)) ## [64.1.0] From 665eb92d5ba5b6985ca5fcc15198f0ac85705255 Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Tue, 14 Apr 2026 13:58:20 +0100 Subject: [PATCH 4/5] fix: remove duplicate changelog entry from released section --- packages/transaction-controller/CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/transaction-controller/CHANGELOG.md b/packages/transaction-controller/CHANGELOG.md index 42ff6146e43..6d1dc983f24 100644 --- a/packages/transaction-controller/CHANGELOG.md +++ b/packages/transaction-controller/CHANGELOG.md @@ -30,7 +30,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- Skip simulation when transaction `containerTypes` includes `EnforcedSimulations` ([#8431](https://github.com/MetaMask/core/pull/8431)) - Bump `@metamask/accounts-controller` from `^37.1.1` to `^37.2.0` ([#8363](https://github.com/MetaMask/core/pull/8363)) - Bump `@metamask/messenger` from `^1.0.0` to `^1.1.1` ([#8364](https://github.com/MetaMask/core/pull/8364), [#8373](https://github.com/MetaMask/core/pull/8373)) From b1b0199344332668aa99d7b118e5202fb45d7acf Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Tue, 14 Apr 2026 14:15:21 +0100 Subject: [PATCH 5/5] fix: move changelog entry to Unreleased section --- packages/transaction-controller/CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/transaction-controller/CHANGELOG.md b/packages/transaction-controller/CHANGELOG.md index 6d1dc983f24..6985ad72b9e 100644 --- a/packages/transaction-controller/CHANGELOG.md +++ b/packages/transaction-controller/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `MantleLayer1GasFeeFlow` with `tokenRatio` conversion for accurate MNT-denominated gas estimates for Mantle and MantleSepolia ([#8386](https://github.com/MetaMask/core/pull/8386)) +### Changed + +- Skip simulation when transaction `containerTypes` includes `EnforcedSimulations` ([#8431](https://github.com/MetaMask/core/pull/8431)) + ## [64.2.0] ### Added @@ -19,7 +23,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- Skip simulation when transaction `containerTypes` includes `EnforcedSimulations` ([#8431](https://github.com/MetaMask/core/pull/8431)) - Preserve `submittedTime` if already set by a publish hook instead of overwriting it ([#8439](https://github.com/MetaMask/core/pull/8439)) ## [64.1.0]