Skip to content

fix(transaction-controller): Skip resimulation check when failing incomplete transactions at boot - #9821

Merged
OGPoyraz merged 4 commits into
mainfrom
ogp/fix-tc-fail-transaction
Aug 11, 2026
Merged

fix(transaction-controller): Skip resimulation check when failing incomplete transactions at boot#9821
OGPoyraz merged 4 commits into
mainfrom
ogp/fix-tc-fail-transaction

Conversation

@OGPoyraz

@OGPoyraz OGPoyraz commented Aug 11, 2026

Copy link
Copy Markdown
Member

Explanation

When TransactionController is constructed, #onBootCleanup() runs #failIncompleteTransactions() to transition any signed or approved transactions to failed. This calls #failTransaction(), which internally calls #updateTransactionInternal().

The problem is that #updateTransactionInternal() calls this.#isSimulationEnabled() for its resimulation check. In mobile, the isSimulationEnabled callback calls PreferencesController:getState via the messenger — but this is a controller initialization order problem: TransactionController is constructed inside Wallet during Engine.init, and at that point PreferencesController has not yet registered its action handler on the root messenger. The PreferencesController is a mobile-only controller that does not exist in the @metamask/wallet package, so the Wallet has no way to guarantee its availability during TransactionController construction.

This causes #failTransaction to throw, and the catch block creates a fallback TransactionMeta with status: failed but never writes it back to state. The transaction remains signed in persisted state permanently, surviving every app restart.

The fix adds skipResimulateCheck: true to the #updateTransactionInternal call inside #failTransaction. There is no reason to check whether a transaction needs resimulation when the intent is to mark it as failed.

References

  • Discovered via MetaMask Mobile: a batch transaction on Arbitrum (0xa4b1) stuck in signed status indefinitely across app restarts

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've communicated my changes to consumers by updating changelogs for packages I've changed
  • I've introduced breaking changes in this PR and have prepared draft pull requests for clients and consumer packages to resolve them

Note

Low Risk
Narrow change to the failure update path at boot with a targeted test; no API or simulation behavior changes for normal transaction flows.

Overview
Fixes startup cleanup so signed/approved transactions left over from a prior session are reliably marked failed instead of staying stuck in signed.

#failTransaction now passes skipResimulateCheck: true into #updateTransactionInternal, so boot-time failure no longer runs the resimulation path that calls isSimulationEnabled. That avoids throws when the callback depends on controllers (e.g. mobile PreferencesController) that are not registered yet during TransactionController construction—previously the error could prevent the failed status from being persisted.

Adds a regression test where isSimulationEnabled throws and both signed and approved txs still end up failed, plus an Unreleased changelog entry.

Reviewed by Cursor Bugbot for commit 0935188. Bugbot is set up for automated code reviews on this repo. Configure here.

@OGPoyraz OGPoyraz changed the title Fix signed transaction failures fix: Fix prevent removing signed transaction failures Aug 11, 2026
@OGPoyraz OGPoyraz changed the title fix: Fix prevent removing signed transaction failures fix(transaction-controller): Skip resimulation check when failing incomplete transactions at boot Aug 11, 2026
@OGPoyraz
OGPoyraz marked this pull request as ready for review August 11, 2026 12:26
@OGPoyraz
OGPoyraz requested review from a team as code owners August 11, 2026 12:26
@OGPoyraz
OGPoyraz force-pushed the ogp/fix-tc-fail-transaction branch from c8f1d20 to 4d1fd5d Compare August 11, 2026 12:32
@matthewwalsh0
matthewwalsh0 self-requested a review August 11, 2026 12:57
@OGPoyraz
OGPoyraz added this pull request to the merge queue Aug 11, 2026
Merged via the queue into main with commit a0691d5 Aug 11, 2026
136 checks passed
@OGPoyraz
OGPoyraz deleted the ogp/fix-tc-fail-transaction branch August 11, 2026 13:00
This was referenced Aug 11, 2026
pull Bot pushed a commit to Reality2byte/core that referenced this pull request Aug 11, 2026
## Explanation

Release `1187.0.0` with a patch version bump for:

- **`@metamask/transaction-controller`** `69.5.1` → `69.5.2`

### `@metamask/transaction-controller@69.5.2`

#### Changed

- Bump `@metamask/accounts-controller` from `^39.0.7` to `^39.1.0`
([MetaMask#9807](MetaMask#9807))

#### Fixed

- Skip resimulation check when failing incomplete transactions at
startup, preventing a crash when `isSimulationEnabled` depends on
controllers not yet registered
([MetaMask#9821](MetaMask#9821))

### Dependency updates

17 packages had their `@metamask/transaction-controller` dependency
range updated to `^69.5.2` with corresponding changelog entries under
`[Unreleased]`:

`@metamask/assets-controller`, `@metamask/assets-controllers`,
`@metamask/bridge-controller`, `@metamask/bridge-status-controller`,
`@metamask/client-utils`, `@metamask/eip-5792-middleware`,
`@metamask/gator-permissions-controller`,
`@metamask/money-account-utils`,
`@metamask/network-enablement-controller`,
`@metamask/phishing-controller`, `@metamask/profile-metrics-controller`,
`@metamask/shield-controller`,
`@metamask/smart-transactions-controller`,
`@metamask/subscription-controller`,
`@metamask/transaction-pay-controller`,
`@metamask/user-operation-controller`, `@metamask/wallet`

## References

- [MetaMask#9807](MetaMask#9807) — Bump
`@metamask/accounts-controller`
- [MetaMask#9821](MetaMask#9821) — Skip
resimulation check when failing incomplete transactions at startup

## Checklist

- [ ] I've updated the test suite for new or updated code as appropriate
- [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or
updated code as appropriate
- [x] I've communicated my changes to consumers by [updating changelogs
for packages I've
changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md)
- [ ] I've introduced [breaking
changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md)
in this PR and have prepared draft pull requests for clients and
consumer packages to resolve them

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Dependency and release-metadata only; behavioral change is limited to
the patch-level transaction-controller fix already shipped in 69.5.2.
> 
> **Overview**
> **Monorepo release `1187.0.0`** bumps the root version from `1186.0.0`
and publishes **`@metamask/transaction-controller` `69.5.2`** (from
`69.5.1`).
> 
> Downstream packages only change **`package.json`** dependency ranges
to `^69.5.2`, **`yarn.lock`**, and **`[Unreleased]` changelog** lines
(17 consumers such as `wallet`, bridge, assets, EIP-5792 middleware,
etc.). There is no application source change in this diff.
> 
> Consumers of **`69.5.2`** get the fixes documented on that release:
skip the resimulation check when failing incomplete transactions at
startup (avoids a crash when `isSimulationEnabled` touches controllers
not registered yet), plus the `accounts-controller` bump noted in that
package’s changelog.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
90794b1. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants