Skip to content

feat: block MetaMask Pay submissions without quotes and handle no-op quotes cp-8.3.0#33194

Merged
dan437 merged 11 commits into
mainfrom
fix/mm-pay-block-conversions-without-quotes
Jul 15, 2026
Merged

feat: block MetaMask Pay submissions without quotes and handle no-op quotes cp-8.3.0#33194
dan437 merged 11 commits into
mainfrom
fix/mm-pay-block-conversions-without-quotes

Conversation

@dan437

@dan437 dan437 commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Description

Mobile side of MetaMask/core#9484, which makes "no conversion needed" an explicit no-op quote and requires quotes at publish time for MetaMask Pay transactions.

  • Bump @metamask/transaction-pay-controller to ^25.0.0 (includes the no-op quote change) and @metamask/transaction-controller to 69.0.0 (required by the new pay controller).
  • validateRequiredQuote now also requires quotes for post-quote withdraw types when the post-quote flag is enabled; direct routes are allowed only when the controller state confirms them.
  • validateRequiredQuote also covers pay deposit and conversion types (perps deposits, predict deposits, mUSD conversion): they may submit without a quote only when the user pays with the required token itself and no conversion is pending, or the deposit is funded by a fiat on-ramp order.
  • New blocking legs in useNoPayTokenQuotesAlert: withdraw flows where the pay config or destination token is not set on the controller yet, and pay deposit/conversion flows where neither a payment token nor a fiat payment method is set.
  • No-op quotes (strategy none) are filtered everywhere: fee, duration, and step UI, metrics, and the publish guard. A no-op quote never counts as an executable quote; direct routes are validated from controller state instead, keeping the publish guard consistent with the confirmation alerts.
  • Metrics: add mm_pay_quote_skipped; strategy and step totals count only executable quotes.

Changelog

CHANGELOG entry: Improved quote validation for MetaMask Pay deposits, conversions and withdrawals

Related issues

Refs: MetaMask/core#9484

Manual testing steps

Feature: MetaMask Pay quote validation

  Scenario: user withdraws to a different destination token
    Given the user has a Predict, Perps, or Money account balance
    When the user enters an amount and a quote is available
    Then the confirmation completes with the selected destination token

  Scenario: user withdraws to the same token and chain
    Given the user selects the source token as the destination
    Then the withdrawal completes as a direct transfer

  Scenario: user deposits or converts paying with another token
    Given the user selects a payment token on a different chain
    When a quote is available
    Then the transaction completes with the conversion

  Scenario: user deposits or converts paying with the required token
    Given the user selects the required token itself as the payment token
    Then the transaction completes as a direct transfer

  Scenario: user deposits paying with fiat
    Given the user selects a fiat payment method
    Then the confirmation is not blocked by the payment token alert

Screenshots/Recordings

N/A

Before

N/A

After

N/A

Pre-merge author checklist

Performance checks (if applicable)

  • I've tested on Android
    • Ideally on a mid-range device; emulator is acceptable
  • I've tested with a power user scenario
    • Use these power-user SRPs to import wallets with many accounts and tokens
  • I've instrumented key operations with Sentry traces for production performance metrics

For performance guidelines and tooling, see the Performance Guide.

Pre-merge reviewer checklist

  • I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed).
  • I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.

Note

High Risk
Changes transaction publish and confirmation blocking for MetaMask Pay funding paths; incorrect direct-route exceptions could still allow unfunded submissions or block legitimate flows.

Overview
Bumps @metamask/transaction-pay-controller to ^25.0.0 (no-op quotes for direct routes) and aligns mobile with stricter publish-time and confirmation-time Pay guards.

No-op quotes (strategy: none) are treated as non-executable everywhere: the quote selector filters them for UI, validateRequiredQuote and metrics only count executable quotes, and mm_pay_quote_skipped is set when no-op quotes were present.

Publish (validateRequiredQuote) now also applies to pay-token-required deposits/conversions (perps/predict/mUSD, etc.) and post-quote withdraws when the feature flag is on. Submission without an executable quote is allowed only for validated direct routes (required token, no pending conversion), fiat orders with orderId, or post-quote withdraws with isPostQuote and no pending sourceAmounts—including withdraws with no destination token selected.

Confirmation adds blocking NoPayTokenQuotes cases: withdraws with token selection enabled before Pay config is initialised (!isPostQuote), and pay-token-required types with no payment token and no fiat method—mirroring publish rules so users cannot confirm timing races that used to submit unfunded txs.

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

@github-actions

Copy link
Copy Markdown
Contributor

CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes.

@dan437 dan437 added the team-confirmations Push issues to confirmations team label Jul 13, 2026
@metamask-ci

metamask-ci Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

PR template — items to address before "Ready for review"

Warnings — informational, address before merging:

  • Pre-merge author checklist has unchecked items (e.g. "I've tested on Android"). Every box must be consciously checked — see docs/readme/ready-for-review.md.

See docs/readme/ready-for-review.md for the full Definition of Ready for Review.

@dan437 dan437 changed the title feat: block MetaMask Pay submissions without quotes and handle no-op quotes feat: block MetaMask Pay submissions without quotes and handle no-op quotes cp-8.3.0 Jul 14, 2026
@dan437 dan437 marked this pull request as ready for review July 14, 2026 12:40
@dan437 dan437 requested a review from a team as a code owner July 14, 2026 12:40
@github-actions github-actions Bot added the risk:medium AI analysis: medium risk label Jul 14, 2026
dan437 added 4 commits July 14, 2026 14:54
…oller to 69.0.0

The new controller stores a no-op quote when a route needs no conversion.
Use the TransactionPayStrategy.None enum directly now that it is available.
…ersions-without-quotes

# Conflicts:
#	package.json
#	yarn.lock
…ests

The publish guard now counts only executable quotes, matching the
confirmation alerts which already filter no-op quotes. Direct routes are
validated from controller state. Adds tests for the publish guard, the
new alert legs, and the no-op quote metrics.
@github-actions github-actions Bot added size-L risk:high AI analysis: high risk and removed size-M risk:medium AI analysis: medium risk labels Jul 14, 2026
@socket-security

socket-security Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatednpm/​@​metamask/​transaction-pay-controller@​24.0.1 ⏵ 25.0.09910081 +1100 +1100

View full report

@socket-security

socket-security Bot commented Jul 14, 2026

Copy link
Copy Markdown

Warning

MetaMask internal reviewing guidelines:

  • Do not ignore-all
  • Each alert has instructions on how to review if you don't know what it means. If lost, ask your Security Liaison or the supply-chain group
  • Copy-paste ignore lines for specific packages or a group of one kind with a note on what research you did to deem it safe.
    @SocketSecurity ignore npm/PACKAGE@VERSION
Action Severity Alert  (click "▶" to expand/collapse)
Warn Low
Potential code anomaly (AI signal): npm @metamask/transaction-pay-controller is 62.0% likely to have a medium risk anomaly

Notes: No overt malware primitives (eval, obfuscated payloads, command execution) are present in this snippet. However, it signs typed data via KeyringController and then posts signature/authorization material to step.post.endpoint using successfulFetch, where the endpoint is derived from quote config without visible allowlisting. It also submits high-privilege delegation/authorization data to a server relay and drives TransactionController:addTransaction/addTransactionBatch with overwriteUpgrade and other powerful options. This creates a plausible supply-chain attack surface if the quote/step configuration can be attacker-controlled; signed artifacts could be exfiltrated to arbitrary URLs. Impact and likelihood depend on validation outside this module.

Confidence: 0.62

Severity: 0.60

From: package.jsonnpm/@metamask/transaction-pay-controller@25.0.0

ℹ Read more on: This package | This alert | What is an AI-detected potential code anomaly?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: An AI system found a low-risk anomaly in this package. It may still be fine to use, but you should check that it is safe before proceeding.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@metamask/transaction-pay-controller@25.0.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

pull Bot pushed a commit to Reality2byte/core that referenced this pull request Jul 14, 2026
MetaMask#9484)

## Explanation

`TransactionPayController` stores an empty quotes array both when no
conversion is needed and when a quote could not be fetched, so at
publish time the two cases cannot be told apart.

This PR makes the distinction explicit:

- Store a no-op quote (`TransactionPayStrategy.None`) when a payment
token is selected but the route needs no conversion. The no-op quote is
built inside `getQuotes` when there are no quote requests, and is
skipped when `isQuoteRequired` is set, since an empty request list then
means source amounts could not be calculated.
- No-op quotes are not routable: the quote refresher skips them, they
are excluded from totals and the external-sign flag, and
`TransactionPayPublishHook` filters them out before selecting a
strategy.
- Clients can rely on `quotes.length > 0` whenever MetaMask Pay is
engaged, and enforce quote presence at publish time on their side.

## References

- Mobile adoption: MetaMask/metamask-mobile#33194

## Checklist

- [x] I've updated the test suite for new or updated code as appropriate
- [x] 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)
- [x] 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]
> **Medium Risk**
> Changes quote shape and publish-time behavior (breaking for quote
consumers) in the transaction pay path, but logic is guarded with
filters and broad tests rather than touching auth or funds directly.
> 
> **Overview**
> Direct MetaMask Pay routes (payment token selected, no conversion) now
persist a **no-op quote** with strategy `none` instead of an empty
`quotes` array, so **“no conversion needed”** is distinguishable from
**“quote still missing.”**
> 
> `getQuotes` builds that marker via `buildNoOpQuote` when there are no
quote requests and `isQuoteRequired` is false; fiat-only empty cases and
quote-required flows still clear or omit it.
**`TransactionPayStrategy.None` is not
routable**—`isTransactionPayStrategy` excludes it, the publish hook
skips execution, quote refresh/polling ignores only-no-op state, and fee
totals / `hasQuotes` sync use **executable** quotes only.
> 
> **Breaking:** downstream clients that treat any entry in `quotes` as a
real pay quote must ignore or handle `strategy: 'none'`.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
30646eb. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Withdraws with no preferred or last-used token intentionally leave
paymentToken unset and default to a direct, same-token transfer
(see getBestToken). The withdraw-not-initialised alert and the publish
guard both required a payment token to be set even in this safe case,
blocking legitimate withdraws that never engage the token picker.

Both now only require isPostQuote (and, for the guard, no pending
conversion) to allow direct submission. The real danger case, a
different destination token chosen but not yet quoted, stays covered
by the existing sourceAmounts-based checks.

Also deduplicates yarn.lock entries left behind by the earlier merge.
…ersions-without-quotes

# Conflicts:
#	yarn.lock
@bschorchit bschorchit linked an issue Jul 15, 2026 that may be closed by this pull request

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

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 cefe5d2. Configure here.

Comment thread app/util/transactions/hooks/index.ts
Comment thread app/util/transactions/hooks/index.ts
…check

Fiat-funded pay deposits (perps/predict/mUSD) auto-submit right after
the on-ramp order is created, before any pay token is ever selected on
the controller, so the pay-token-required publish guard always threw
for them. Also fix a checksum-vs-lowercase mismatch that could mark an
optional conversion as required and block a valid direct submission.
@github-actions

Copy link
Copy Markdown
Contributor

🔍 Smart E2E Test Selection

  • Selected E2E tags: SmokeAccounts, SmokeConfirmations, SmokeNetworkAbstractions, SmokeNetworkExpansion, SmokeSwap, SmokeStake, SmokeWalletPlatform, SmokeMoney, SmokePerps, SmokeMultiChainAPI, SmokePredictions, SmokeSeedlessOnboarding, SmokeBrowser, SmokeSnaps
  • Selected Performance tags: @PerformancePreps, @PerformancePredict
  • Risk Level: high
  • AI Confidence: 100%
click to see 🤖 AI reasoning details

E2E Test Selection:
Hard rule (controller-version-update): @MetaMask controller package version updated in package.json: @metamask/transaction-pay-controller. Running all tests.

Performance Test Selection:
The changes affect transaction submission validation and quote filtering for perpsDeposit/perpsDepositAndOrder and predictDeposit/predictDepositAndOrder transaction types. The new alert conditions in useNoPayTokenQuotesAlert and the enhanced validateRequiredQuote logic could impact the timing and flow of Perps and Predictions deposit flows, which are measured by @PerformancePreps and @PerformancePredict performance tests. The major version bump of transaction-pay-controller (v24→v25) could also introduce performance regressions in these flows.

View GitHub Actions results

@sonarqubecloud

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown
Contributor

⚡ Performance Test Results

ℹ️ Performance test results are currently non-blocking and will not block this PR.

All tests passed · 5 tests · 1 device

📱 Devices tested (1)

Android: Google Pixel 8 Pro (v14.0)

✅ Passed Tests (5)
Test Platform Device Duration Team Recording
Perps open position and close it Android Google Pixel 8 Pro (v14.0) 14.37s @mm-perps-engineering-team 📹 Watch
Predict Available Balance - Complete Flow Performance Android Google Pixel 8 Pro (v14.0) 1.20s @team-predict 📹 Watch
Predict Deposit - Complete Flow Performance Android Google Pixel 8 Pro (v14.0) 10.64s @team-predict 📹 Watch
Perps add funds Android Google Pixel 8 Pro (v14.0) 9.32s @mm-perps-engineering-team 📹 Watch
Predict Market Details - Complete Flow Performance Android Google Pixel 8 Pro (v14.0) 3.56s @team-predict 📹 Watch

Branch: fix/mm-pay-block-conversions-without-quotes · Build: Normal · Commit: 5bdb2bd · View full run

@dan437 dan437 enabled auto-merge July 15, 2026 12:16
@dan437 dan437 requested a review from pedronfigueiredo July 15, 2026 12:58
@dan437 dan437 added this pull request to the merge queue Jul 15, 2026
Merged via the queue into main with commit 234a682 Jul 15, 2026
209 checks passed
@dan437 dan437 deleted the fix/mm-pay-block-conversions-without-quotes branch July 15, 2026 14:04
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 15, 2026
@metamask-ci metamask-ci Bot added the release-8.4.0 Issue or pull request that will be included in release 8.4.0 label Jul 15, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

release-8.4.0 Issue or pull request that will be included in release 8.4.0 risk:high AI analysis: high risk size-L team-confirmations Push issues to confirmations team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: MM Pay submitting empty quotes

2 participants