Skip to content

feat(transaction-controller): add approval-time sponsorship/signing hooks - #10109

Merged
pedronfigueiredo merged 4 commits into
mainfrom
pnf/canonical-transaction-approval-preparation-handoff-implementation
Sep 22, 2026
Merged

pedronfigueiredo merged 4 commits into
mainfrom
pnf/canonical-transaction-approval-preparation-handoff-implementation

Conversation

@pedronfigueiredo

@pedronfigueiredo pedronfigueiredo commented Sep 4, 2026 •

Copy link
Copy Markdown
Contributor

Related pull requests

Summary

  • Add optional approval-time isSponsored and shouldSign hooks to TransactionController, with named object results for future extensibility.
  • Default omitted hooks to non-sponsored transactions with local signing.
  • Deprecate the legacy isGasFeeSponsored and isExternalSign lifecycle properties while retaining them in public types for compatibility; isGasFeeSponsored remains available as migration metadata.
  • When simulation is enabled, refresh gas-fee-token and sponsorship availability after approval preparation and expose it as TransactionMeta.isGasFeeSponsoredAvailable before sponsorship policy runs.
  • Reserve a nonce and sign locally only for non-sponsored transactions when shouldSign returns true.
  • Preserve existing nonces when local signing is skipped and include newly reserved nonces in the approved status event.
  • Require a client publish hook when local signing is skipped. Unsigned publication is owned by that hook, and the controller does not fall back to eth_sendRawTransaction with an empty payload or require the hook to return a transaction hash.

Public API

hooks?: {
  afterAdd?: AfterAddHook;
  beforeCheckPendingTransaction?: (
    transactionMeta: TransactionMeta,
  ) => Promise<boolean>;
  beforePublish?: (
    transactionMeta: TransactionMeta,
  ) => Promise<boolean>;
  beforeSign?: BeforeSignHook;
  isSponsored?: (request: {
    transactionMeta: TransactionMeta;
  }) => Promise<{ isSponsored: boolean }>;
  publish?: (
    transactionMeta: TransactionMeta,
  ) => Promise<{ transactionHash: string }>;
  publishBatch?: PublishBatchHook;
  shouldSign?: (request: {
    transactionMeta: TransactionMeta;
  }) => Promise<{ shouldSign: boolean }>;
}

The new hooks are optional. Without them, transactions retain the existing non-sponsored, locally signed behavior.

Approval behavior

  1. Mark the transaction as approved and run beforeSign preparation.
  2. Run the extracted approval-preparation helper, which refreshes gas-fee tokens and isGasFeeSponsoredAvailable when simulation is enabled (or selected-token preflight requires it), then performs the gas-fee-token preflight.
  3. Call isSponsored (default: false).
  4. If sponsored, skip shouldSign, nonce reservation, and local signing.
  5. Otherwise call shouldSign (default: true); reserve a nonce and sign only when it returns true.
  6. Publish the approved status after any local nonce reservation, then continue through signing and publication.
  7. If local signing is skipped, require and defer to a publish hook. Do not invoke the provider fallback with the unsigned 0x sentinel; the hook may return without a transaction hash.

beforeSign always runs, including when local signing is skipped. Existing nonces are retained on externally prepared transactions. Hook outcomes are transient policy results and are not persisted as transaction metadata.

Validation

  • yarn workspace @metamask/transaction-controller run test
  • yarn workspace @metamask/transaction-controller run build
  • yarn eslint <changed TypeScript files>
  • yarn prettier --check <changed TypeScript files>
  • yarn changelog:validate

The package test suite, package build, focused ESLint, changed-TypeScript formatting checks, changelog validation, and branch diff check pass.


Note

High Risk
Breaking changes to core transaction approval, nonce, signing, and publish behavior affect every client that relied on deprecated sponsorship/external-sign metadata or simulation-driven sponsorship.

Overview
Breaking: Sponsorship and local signing are no longer driven by isGasFeeSponsored, isExternalSign, or simulation’s old isGasFeeSponsored flag. Integrators must use optional approval-time isSponsored and shouldSign hooks (defaults: not sponsored, sign locally).

On approval, the controller runs beforeSign, refreshes gas-fee tokens and sets TransactionMeta.isGasFeeSponsoredAvailable when simulation (or token preflight) requires it, then evaluates the hooks. Sponsored flows skip shouldSign, nonce reservation, and local signing. When shouldSign is false, existing nonces are kept, beforeSign still runs, and publication requires a publish hook—the controller will not fall back to eth_sendRawTransaction with an empty payload.

Simulation/gas-fee helpers now expose sponsorship availability (isGasFeeSponsoredAvailable) instead of treating simulation as the lifecycle policy. Legacy fields remain on types as deprecated migration metadata only. Tests and changelog document the new behavior, including batch gas-fee poller updates and stricter publish paths.

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

jpuri
jpuri previously approved these changes Sep 4, 2026
Comment thread packages/transaction-controller/src/utils/prepare-transaction-for-approval.ts Outdated
@pedronfigueiredo pedronfigueiredo changed the title feat(transaction-controller): prepare transactions for approval feat(transaction-controller): add approval-time sponsorship/signing hooks Sep 9, 2026

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/transaction-controller/src/TransactionController.ts
Comment thread packages/transaction-controller/src/TransactionController.ts

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/transaction-controller/src/TransactionController.ts
@pedronfigueiredo
pedronfigueiredo force-pushed the pnf/canonical-transaction-approval-preparation-handoff-implementation branch 2 times, most recently from c78867c to bd526cb Compare September 10, 2026 09:58
@pedronfigueiredo
pedronfigueiredo requested review from a team as code owners September 10, 2026 10:52
@pedronfigueiredo
pedronfigueiredo force-pushed the pnf/canonical-transaction-approval-preparation-handoff-implementation branch from c6c60e0 to b0c25e6 Compare September 16, 2026 09:28
Comment thread packages/bridge-status-controller/src/strategy/batch-strategy.ts
Comment thread packages/transaction-controller/src/TransactionController.ts Outdated
Comment thread packages/transaction-controller/src/TransactionController.ts
Comment thread packages/transaction-controller/src/types.ts Outdated
Comment thread packages/transaction-controller/src/types.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 83fe8f8. Configure here.

Comment thread packages/transaction-controller/src/TransactionController.ts Outdated
Comment thread packages/transaction-controller/src/TransactionController.ts
matthewwalsh0
matthewwalsh0 previously approved these changes Sep 21, 2026
Comment thread packages/transaction-controller/src/TransactionController.ts Outdated
Comment thread packages/transaction-controller/src/TransactionController.ts Outdated
Comment thread packages/transaction-controller/src/TransactionController.ts Outdated
@pedronfigueiredo
pedronfigueiredo added this pull request to the merge queue Sep 21, 2026
@pedronfigueiredo
pedronfigueiredo removed this pull request from the merge queue due to a manual request Sep 21, 2026
matthewwalsh0
matthewwalsh0 previously approved these changes Sep 22, 2026
@pedronfigueiredo
pedronfigueiredo force-pushed the pnf/canonical-transaction-approval-preparation-handoff-implementation branch from a352664 to 71e88f2 Compare September 22, 2026 14:23
@pedronfigueiredo
pedronfigueiredo added this pull request to the merge queue Sep 22, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 22, 2026
@pedronfigueiredo
pedronfigueiredo added this pull request to the merge queue Sep 22, 2026
Merged via the queue into main with commit 3cc3f5e Sep 22, 2026
112 checks passed
@pedronfigueiredo
pedronfigueiredo deleted the pnf/canonical-transaction-approval-preparation-handoff-implementation branch September 22, 2026 15:28
@OGPoyraz OGPoyraz mentioned this pull request Sep 23, 2026
1 of 4 tasks
pull Bot pushed a commit to Reality2byte/core that referenced this pull request Sep 23, 2026
## Explanation

Release `1275.0.0` with a **major** version bump for
`@metamask/transaction-controller` and a **minor** version bump for
`@metamask/transaction-pay-controller`.

- **`@metamask/transaction-controller`** `70.1.0` → `71.0.0` (major)
- **`@metamask/transaction-pay-controller`** `29.0.2` → `29.1.0` (minor)

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

**Breaking:** Move sponsorship and signing decisions to optional
approval-time `isSponsored` and `shouldSign` hooks
([MetaMask#10109](MetaMask#10109))
- The hooks default to non-sponsored and local signing when omitted.
Sponsored transactions skip the `shouldSign` hook and local signing,
while transactions that skip local signing retain an existing nonce and
require a publish hook.
- `isGasFeeSponsored` and `isExternalSign` remain in the public types as
deprecated compatibility properties but no longer control the
transaction lifecycle; `isGasFeeSponsored` remains available as
migration metadata.
- Add `TransactionMeta.isGasFeeSponsoredAvailable` and refresh it during
approval preparation when simulation is enabled so sponsorship hooks
receive current availability without overriding simulation preferences.

Other changes:
- Add `membershipSubscription` transaction type
([MetaMask#10340](MetaMask#10340))
- Bump `bn.js` from `^5.2.1` to `^5.2.5`
([MetaMask#10362](MetaMask#10362))

### `@metamask/transaction-pay-controller@29.1.0`

- Support subsidized max Relay deposits using atomic `EXACT_OUTPUT`
quotes with transaction calls embedded, gated by
`payStrategies.relay.atomicMaxEnabled`
([MetaMask#10224](MetaMask#10224))
- Bump `bn.js` from `^5.2.1` to `^5.2.5`
([MetaMask#10362](MetaMask#10362))
- Bump `immer` from `^9.0.6` to `^9.0.21`
([MetaMask#10331](MetaMask#10331))

### Dependency updates

16 packages had their `@metamask/transaction-controller` dependency
range updated to `^71.0.0` with `package.json` changes under this
release branch. Changelog entries for those packages will be generated
by commenting `@metamaskbot update-changelogs` on this PR.

## References

- [MetaMask#10109](MetaMask#10109) — Move
sponsorship and signing decisions to optional approval-time hooks
- [MetaMask#10340](MetaMask#10340) — Add
`membershipSubscription` transaction type
- [MetaMask#10224](MetaMask#10224) — Support
subsidized max Relay deposits using atomic `EXACT_OUTPUT` quotes

## 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]
> **High Risk**
> The major transaction-controller release changes core transaction
approval, sponsorship, and signing behavior across many downstream
packages that depend on it.
> 
> **Overview**
> Release **`1275.0.0`** cuts published versions for
**`@metamask/transaction-controller`** `71.0.0` (major) and
**`@metamask/transaction-pay-controller`** `29.1.0` (minor), and bumps
the monorepo root version accordingly.
> 
> The transaction-controller release documents **breaking**
approval-time behavior: sponsorship and signing now flow through
optional **`isSponsored`** / **`shouldSign`** hooks instead of
**`isGasFeeSponsored`** / **`isExternalSign`**, plus a new
**`membershipSubscription`** transaction type. This PR mostly
**propagates** that major by setting dependent packages’
`@metamask/transaction-controller` ranges to **`^71.0.0`**, updating
**`yarn.lock`**, and recording bump entries in package changelogs
(including transaction-pay’s dependency bump under `29.1.0`).
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
b93ad54. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
pull Bot pushed a commit to Reality2byte/core that referenced this pull request Sep 24, 2026
…Mask#10420)

## Summary

Revert MetaMask#10109 using `git revert` (merge commit
`3cc3f5e74cefa3da0287d2f377c4fbc802f822a8`). This restores the previous
transaction approval, sponsorship, nonce, signing, and publication
behavior and removes the approval-time `isSponsored` / `shouldSign`
hooks and `isGasFeeSponsoredAvailable` field.

The only revert conflict was in the transaction-controller changelog.
The 71.0.0 entry was retained as historical documentation for the
already-published release; no unrelated changelog changes were made.

## Validation

- `yarn workspace @metamask/transaction-controller run test`
- `yarn build`
- `yarn workspace @metamask/transaction-controller run
changelog:validate`
- Focused ESLint and Prettier checks on changed TypeScript files
- `git diff --cached --check` before committing

## Coordination / risk

This reverses a published public API and transaction lifecycle behavior.
Confirm whether Extension
([#46060](MetaMask/metamask-extension#46060))
and Mobile
([#35754](MetaMask/metamask-mobile#35754))
depend on the hooks, and coordinate any necessary client changes before
merging.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **High Risk**
> Reverts a published transaction lifecycle and public hook API; clients
using `isSponsored`/`shouldSign` or `isGasFeeSponsoredAvailable` must
change before merge.
> 
> **Overview**
> **Reverts approval-time `isSponsored` / `shouldSign` hooks** and the
`isGasFeeSponsoredAvailable` field, restoring the prior sponsorship and
signing model driven by transaction metadata and simulation.
> 
> On approval, the controller again **always reserves a nonce and runs
the sign path** (unless `isExternalSign`), with `beforeSign` folded into
`#signTransaction` instead of running before hook-based policy.
Sponsored / externally signed flows use **`isGasFeeSponsored` and
`isExternalSign`** set from add-transaction options or from
`getGasFeeTokens` (`isGasFeeSponsored` from simulation sponsorship),
which skips local signing and can clear the nonce via `getNextNonce` and
`checkGasFeeTokenBeforePublish`.
> 
> **Gas fee token plumbing** renames sponsorship availability to
`isGasFeeSponsored`, drops approval-time gas-fee refresh in
`#prepareTransactionBeforeApproval`, and restores publishing that may
broadcast even when `rawTx` is empty. **`hooks` is required** on
controller options (no optional hook defaults for sponsorship/signing).
Tests drop hook lifecycle coverage and add assertions for the restored
metadata behavior.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
6b6ccd4. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
pull Bot pushed a commit to Reality2byte/core that referenced this pull request Sep 24, 2026
## Explanation

Prepare monorepo release **1285.0.0** for
`@metamask/transaction-controller@72.0.0` and all **19 direct workspace
dependents**. `perps-controller` is now included for the consumer-facing
HIP-3 snapshot fix merged in MetaMask#10429, not merely for its development
dependencies; `earn-controller` also has an existing production
dependency bump. This branch incorporates current `main`, including
MetaMask#10429 and MetaMask#10419. No source-code changes are introduced by this PR
relative to current `main`.

### Versions in this release

| Package | Version | Rationale |
| --- | --- | --- |
| `transaction-controller` | **72.0.0** | Breaking revert of
approval-time sponsorship/signing hooks (MetaMask#10420) |
| `subscription-controller` | **10.0.0** | Existing Unreleased breaking
subscription type changes (MetaMask#10305, MetaMask#10416) |
| `wallet` | **15.0.0** | Its exported default instance/state types
expose the breaking subscription and transaction-controller changes |
| `transaction-pay-controller` | **29.2.0** | Additional supported
pay-strategy flows and server-quote behavior (MetaMask#10342) |
| `assets-controller` | 16.1.1 | Dependency updates and previously
merged fixes |
| `assets-controllers` | 112.0.3 | Dependency updates |
| `bridge-controller` | 81.3.1 | Dependency updates |
| `bridge-status-controller` | 76.3.1 | Dependency updates |
| `client-utils` | 3.0.3 | Dependency update |
| `earn-controller` | 13.0.1 | Existing production
`account-tree-controller` bump; transaction-controller is dev-only here
|
| `eip-5792-middleware` | 4.0.1 | Dependency updates |
| `gator-permissions-controller` | 6.0.1 | Dependency updates |
| `money-account-utils` | 2.0.1 | Dependency updates |
| `network-enablement-controller` | 7.0.1 | Dependency updates |
| `perps-controller` | **18.0.1** | Fix Terminal v3 HIP-3 snapshot
validation (MetaMask#10429); patch bump |
| `phishing-controller` | 18.1.1 | Dependency update |
| `profile-metrics-controller` | 5.1.1 | Dependency updates |
| `shield-controller` | 7.0.2 | Dependency updates |
| `smart-transactions-controller` | 27.0.2 | Dependency updates |
| `user-operation-controller` | 42.0.1 | Dependency updates |

Dependent packages have their Unreleased changelog entries moved to
their new release sections. Additional *non-released* transitive
dependents receive compatible workspace ranges
(`money-account-upgrade-controller`,
`network-connection-banner-controller`, `signature-controller`, and
unpublished `wallet-cli`); the three published packages receive
Unreleased dependency changelog entries. `yarn.lock` is synchronized.
Changes to `perps-controller`'s development dependencies are
intentionally omitted from its changelog.

**Coordination before merge:** The transaction-controller hook revert
and the subscription-controller's existing breaking type changes affect
consumers. Confirm Extension and Mobile migrations before publishing
these three major versions. This is a release candidate, not a published
release.

## References

- Breaking revert: MetaMask#10420; original published API: MetaMask#10109.
- EIP-7702 performance change: MetaMask#10397.
- Perps HIP-3 snapshot fix: MetaMask#10429.
- Subscription breaking changes: MetaMask#10305, MetaMask#10416.
- Client adoption PRs to coordinate: [Extension
#46060](MetaMask/metamask-extension#46060),
[Mobile #35754](MetaMask/metamask-mobile#35754).
They adopt the removed hooks; they are **not** migrations for this
revert.

## Validation

- `yarn build`
- `yarn workspace @metamask/transaction-controller run test`
- `yarn workspace @metamask/subscription-controller run test`
- `yarn workspace @metamask/transaction-pay-controller run test`
- `yarn workspace @metamask/wallet run test`
- `yarn workspace @metamask/perps-controller run test` (after
incorporating MetaMask#10429)
- `yarn changelog:validate`, `yarn constraints`, `yarn install
--immutable`, `yarn dedupe --check`
- `yarn lint:dependencies` (after removing stale generated
bridge-controller `dist` output)
- `git diff --check`

## Checklist

- [x] Changelogs describe the breaking changes in
transaction-controller, subscription-controller, and wallet.
- [x] Direct workspace dependents are included, with perps now having a
consumer-facing fix (MetaMask#10429).
- [ ] Extension and Mobile consumers have been coordinated for these
breaking changes before merging this release.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **High Risk**
> Publishes breaking majors for transaction signing/sponsorship and
subscription types that require coordinated Extension/Mobile migrations
before consumers upgrade `@metamask/wallet` or
`@metamask/transaction-controller`.
> 
> **Overview**
> Prepares **monorepo release 1285.0.0** by bumping package versions,
moving Unreleased changelog notes into release sections, aligning
workspace dependency ranges, and refreshing `yarn.lock`. There are **no
application source edits** in this diff—only release metadata for work
already on `main`.
> 
> The release centers on **`@metamask/transaction-controller@72.0.0`**,
which **reverts** the approval-time `isSponsored` / `shouldSign` hooks
in favor of metadata-driven **`isGasFeeSponsored`** and
**`isExternalSign`**, drops related exports and
`isGasFeeSponsoredAvailable`, and again **requires** a `hooks` object
(use `hooks: {}` when empty).
**`@metamask/subscription-controller@10.0.0`** ships existing breaking
subscription/payment-token type changes (optional period fields,
vault-share detection via `isVaultShareToken`, crypto payment failure
types). **`@metamask/wallet@15.0.0`** bumps those majors plus related
dependents.
> 
> Nineteen direct workspace dependents get compatible semver bumps
(mostly patch dependency realignments to
`@metamask/transaction-controller@^72.0.0`). Notable published notes
include **`@metamask/perps-controller@18.0.1`** (HIP-3 Terminal snapshot
validation fix) and **`@metamask/transaction-pay-controller@29.2.0`**
(server pay-strategy quoting/submit fixes). Unpublished/transitive
packages (`wallet-cli`, `money-account-upgrade-controller`, etc.) get
range updates without always cutting new versions.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
6017d49. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Baptiste Marchand <75846779+baptiste-marchand@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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.

4 participants