Repository navigation
feat(transaction-pay-controller): atomic quoting for subsidized max Money Account deposits - #10224
Conversation
…oney Account deposits
…ame-token gas budget
|
@metamaskbot publish-preview |
|
Preview builds have been published. Learn how to use preview builds in other projects. Expand for full list of packages and versions. |
# Conflicts: # packages/transaction-pay-controller/CHANGELOG.md
|
@metamaskbot publish-preview |
|
Preview builds have been published. Learn how to use preview builds in other projects. Expand for full list of packages and versions. |
# Conflicts: # packages/transaction-pay-controller/CHANGELOG.md
|
@metamaskbot publish-preview |
|
Preview builds have been published. Learn how to use preview builds in other projects. Expand for full list of packages and versions. |
|
@metamaskbot publish-preview |
|
Preview builds have been published. Learn how to use preview builds in other projects. Expand for full list of packages and versions. |
|
@metamaskbot publish-preview |
|
Preview builds have been published. Learn how to use preview builds in other projects. Expand for full list of packages and versions. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ 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 5fd8ff1. Configure here.
| ...transaction, | ||
| nestedTransactions, | ||
| requiredAssets, | ||
| }; |
There was a problem hiding this comment.
Stale txParams in atomic max quotes
High Severity
Atomic max preparation rewrites nested calldata and requiredAssets on a clone, but leaves txParams on the pre-max deposit amount. Quote-time getDelegationTransaction and later execute validation still read that stale txParams, so the embedded vault call can lock the original amount while the paired transfer uses the source-budget size.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 5fd8ff1. Configure here.
…#36412) ## Description Use directional Relay fixed-spread routes and the Core atomic-max feature gate to choose the initial quote for Max Money Account deposits. - Leave `atomic` unset (Core defaults to atomic) only when atomic max is enabled for the transaction and the selected source-to-destination route matches `confirmations_relay_fixed_spread`. - Set `atomic: false` for Max deposits when the gate is absent/disabled or the route does not match. This preserves non-atomic Max support when Core does not permit embedded Max transactions. - Match Core flag semantics: a matching top-level or nested transaction-type override takes precedence over `default`; an absent default is `false`. - Refresh the hint when eligibility or the selected route changes while Max is active. - Clear the Max-specific override when selecting a partial or manual amount. Keep non-Money-Account flows unchanged. The route match predicts subsidy; Core remains responsible for verifying the returned quote and selecting the final execution mode. ### Quote behavior With atomic max enabled, for Relay execute routes: - Matching route and subsidized response: one atomic `EXACT_OUTPUT` quote, using the source budget adjusted to destination decimals for the 1:1 subsidized stablecoin route. - Non-atomic hint and unsubsidized response: one `EXACT_INPUT` quote, with the vault transaction handled in the separate second leg. - Atomic hint but unsubsidized response: Core retries with non-atomic `EXACT_INPUT`. - Non-atomic hint but subsidized response: Core promotes to atomic `EXACT_OUTPUT` using the returned output amount. When the atomic-max gate is disabled, a subsidized quote remains non-atomic; subsidy alone does not enable the feature. Atomic preparation/fetch errors are not silently retried as non-atomic quotes. ## Configuration Enable atomic max for Money Account deposits under the existing `confirmations_pay_extended` flag: ```json { "payStrategies": { "relay": { "atomicMaxEnabled": { "default": false, "transactionTypes": { "moneyAccountDeposit": true } } } } } ``` Merge this into the existing flag value without removing other settings. `transactionTypes` is a type-to-boolean object, **not an array**. Nested `moneyAccountDeposit` transactions inside a parent `batch` are supported. A matching directional route in `confirmations_relay_fixed_spread` is also needed for the client to hint atomic-first execution. ## Dependency / Draft Status Depends on MetaMask/core#10224. This PR now integrates `@metamask/transaction-pay-controller` through `previewBuilds`, using `29.0.1-preview-d3f9964be`, with the corresponding lockfile changes. The preview contains the Core atomic-max implementation; the base dependency declaration remains `^28.0.2`. Keep this PR in draft until integrated live-flow validation is complete. Replace the preview with the appropriate published Core release before production rollout. ## Testing - Four targeted suites passed locally: **193 tests**, covering the custom-amount hook, confirmations selectors, fixed-spread route matching, and payment-override clearing. - Added coverage for absent/disabled gates, default and per-type enablement, nested transaction matching, active-Max gate/route changes, and Max/manual transitions. - Changed-file LSP diagnostics: clean. - Changed-file ESLint: no errors; one existing React Compiler warning remains. Formatting passed. - Direct comparison against the installed Core preview confirmed Mobile/Core gate parity across six configuration cases. - Read-only inspection of a running simulator confirmed the tested USDC-to-mUSD route matched, but an array-shaped `transactionTypes` override left the gate disabled and produced `EXACT_INPUT`. Evaluating the corrected object enabled the gate without changing runtime state. - Live `EXACT_OUTPUT` quoting with the corrected configuration and end-to-end deposit execution remain unverified. ## References - https://consensyssoftware.atlassian.net/browse/CONF-1949 - MetaMask/core#10224 <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes Max deposit quoting hints and transaction-pay config, which can alter Relay quote mode (atomic vs non-atomic) for money-account deposits when feature flags are enabled. > > **Overview** > **Money Account deposit Max** no longer always forces `atomic: false`. The custom-amount hook now reads the relay **atomic-max** gate (`confirmations_pay_extended.payStrategies.relay.atomicMaxEnabled`) and whether the pay token → destination pair matches **`confirmations_relay_fixed_spread`**. When both apply, it leaves `atomic` unset so Core can default to atomic quoting; otherwise Max still sets `atomic: false`. > > A new **`selectRelayAtomicMaxEnabled`** selector resolves the gate with per–transaction-type overrides (including nested batch deposits), defaulting to off when absent. An effect **re-applies** the hint while Max stays on if the gate or route eligibility changes. > > **`@metamask/transaction-pay-controller`** is bumped to **^29.1.0** (lockfile updated). Tests cover gate on/off, route match/mismatch, live flag toggles, and clearing atomic on partial/manual amounts. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 4937716. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
## 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>


Explanation
Enable atomic
EXACT_OUTPUTRelay execution for subsidized max deposits, with vault/transaction calls embedded instead of submitted as a separate second leg.Atomic max quoting
atomic: true(or omitted) requests atomicEXACT_OUTPUTfirst. The destination amount comes from the source-token budget rescaled to destination decimals, rounding down, for fixed-spread subsidized stablecoin routes that exchange 1:1 without additional fees. It does not reuse the pre-Max required amount or require a discovery quote.EXACT_INPUTquote for separate second-leg execution.atomic: false, start with non-atomicEXACT_INPUT. If subsidized, use its output amount to rebuild calldata and required assets on a clone before requesting atomicEXACT_OUTPUT.isMaxAmountthroughout and leave the original transaction unchanged while preparing atomic calls. Non-max behavior is unchanged.Feature flag and error handling
Eligibility is controlled by
confirmations_pay_extended.payStrategies.relay.atomicMaxEnabled, replacing the earlieratomicMaxPromotionEnabledname. Its shape remains{ default?: boolean; transactionTypes?: Partial<Record<TransactionType, boolean>> }. It defaults to disabled, and configured overrides match top-level or nested transaction types. There is no hardcoded Money Account eligibility fallback.When disabled, existing max transaction-embedding restrictions still apply; clients requesting a non-atomic max deposit should set
atomic: false.Atomic promotion failures retain the
Atomic promotion failedprefix through standard quote-error handling and pay-strategy fallback. The special terminal-error branch in the quote orchestrator is removed. Atomic preparation/fetch errors are not silently converted into non-atomic retries.Max routing lives in
relay-max.ts. Legacy gas-station handling remains intact; removing it is deferred to a separate PR. No public API signatures changed.Client coordination
Companion draft: metamask-mobile#36412. Clients can use the
atomichint to reflect expected route subsidy. Correct atomic hints now avoid the initial discovery quote. Client package integration and live route validation remain pending.Validation Status
Screenshot & recordings
Exact Input for Max ARB token:

Exact output for Max USDC token:

Recording:
Simulator.Screen.Recording.-.iPhone.17.Pro.Max.-.2026-09-14.at.14.57.04.mov
References
Checklist
Note
Medium Risk
Changes max-deposit quoting, amount embedding, validation, and submit routing for subsidized Relay routes; mistakes could mis-size deposits or leave users without quotes, but behavior is feature-flag gated and heavily tested.
Overview
Adds subsidized max Relay deposits that can run as atomic
EXACT_OUTPUTquotes with embedded transaction calls, behindpayStrategies.relay.atomicMaxEnabled(off by default, with per-TransactionTypeoverrides).Max quoting now routes through
getRelayMaxQuote: when the flag is on, the clientatomichint drives whether to request atomic max first (source budget rescaled to destination decimals, rounded down) or start with non-atomicEXACT_INPUTand promote to atomic when Relay returns a subsidized quote. Promotion rebuilds calldata viagetAmountDataon a cloned transaction without mutating the original. Unsubsidized atomic attempts fall back to non-atomic max; promotion/validation failures surface asQuoteErrorwith anAtomic promotion failedprefix so orchestration can fall back to other pay strategies and clear stale quotes on refresh.Relay quoting loosens the “max + embedded tx” guard when atomic max is enabled, validates promoted atomic max quotes in a separate batch (wrapping validation errors as promotion failures), and submit skips the separate Money Account vault step when the promoted quote already inlines the deposit calls in Relay execute.
Reviewed by Cursor Bugbot for commit 5fd8ff1. Bugbot is set up for automated code reviews on this repo. Configure here.