Skip to content

feat(ramp): headless-aware external checkout + shared quote pick (Phase 2)#32912

Draft
saustrie-consensys wants to merge 3 commits into
feat/headless-buy-provider-scope-remote-flagfrom
saustrie/headless-buy-phase2-mobile
Draft

feat(ramp): headless-aware external checkout + shared quote pick (Phase 2)#32912
saustrie-consensys wants to merge 3 commits into
feat/headless-buy-provider-scope-remote-flagfrom
saustrie/headless-buy-phase2-mobile

Conversation

@saustrie-consensys

Copy link
Copy Markdown
Contributor

Description

Headless Buy "All Providers Support" Phase 2 mobile work, stacked on #32890. It consumes a preview build of the core follow-on (MetaMask/core#9414, stacked on MetaMask/core#9409) that promotes the quote pick to a public, pure getSmartSelectedQuote and lays the scope-gated widening groundwork.

This PR keeps the "dumb UI, logic in the controller" boundary: core owns the provider-agnostic ranking/filtering; mobile keeps deeplink schemes, redirect/callback URLs, navigation, session lifecycle, and the error-taxonomy values.

Milestones in this PR:

  • P2.M6 (consume the shared pick). The headless getQuotes facade (useHeadlessBuy) now passes autoSelectProvider: true, so the controller runs its shared, scope-aware getSmartSelectedQuote and places the best quote at success[0]. The UI never ranks or sorts quotes locally. GetQuotesOptions gained autoSelectProvider / preferredProviderIds passthrough.
  • P2.M1 (headless-aware external-browser branch). useContinueWithQuote's external-browser branch is now headless-aware when ctx.headlessSessionId is set:
    • iOS InAppBrowser.openAuth success resolves the provider callback into an order (getOrderFromCallback) and fires onOrderCreated then onClose({ reason: 'completed' }); a bailed/placeholder order is treated as a user exit (onClose({ reason: 'user_dismissed' })).
    • Cancel fires onClose({ reason: 'user_dismissed' }).
    • An open rejection or callback-resolution failure fails the session (QUOTE_FAILED).
    • Android / system-browser open success keeps the session live (success returns via the deeplink path; abandonment is inferred by the existing focus-dismissal machinery) instead of resetting to BuildQuote.
    • Deeplink schemes and redirect URLs stay mobile-side; classification uses the shared isExternalBrowserQuote helper.
  • P2.M4 (custom-action support). Custom-action quotes ride the now-headless-aware external path and terminate through the same callbacks. No mobile-side custom-action exclusion remained to remove (Phase 1 filtering lives in the controller pick, gated on scope).
  • P2.M7 (partial). External-browser failure sites now emit the typed QUOTE_FAILED code via failSession.

Deferred to a follow-up (reported, not done here)

  • P2.M2 / P2.M3 (deeplink return + E1/E2/E3 reconciliation). The success-deeplink-wins rule and the focus-dismissal grace/pre-emption depend on an open product decision (whether the zero-delay focus dismissal needs a deliberate grace delay) and on-device timing/modal-teardown validation that can't be confidently completed here. iOS openAuth success (E1) is already handled by P2.M1.
  • P2.M5 (widen to all) is a runtime flip of the existing moneyHeadlessProviderScope scope flag, not code.
  • Full P2.M7 analytics parity (RAMPS_CHECKOUT_CLOSED / RAMPS_ORDER_FAILED + ramp_surface for the external-browser path) beyond the typed QUOTE_FAILED code.

This PR consumes a preview build of MetaMask/core#9414 for RC testing only and is not mergeable as-is: the core follow-on must be released and this must be swapped to the released range with the previewBuilds block removed first.

Changelog

CHANGELOG entry: null

Dev/RC-gated behind the moneyHeadlessProviderScope flag and dependent on an unreleased core preview build, so it is not end-user-facing yet. Labeled no-changelog.

Related issues

Refs: MetaMask/core#9414
Refs: #32890

Manual testing steps

Requires an RC (or beta) build; the headless flow is hard-forced off in Production and only honors the dev/RC scope toggle on non-production builds.

Feature: Headless Buy external-browser checkout

  Scenario: iOS external provider checkout completes headlessly
    Given the moneyHeadlessProviderScope flag is set to "all" on an RC build
    And I start a headless buy with an external-browser provider quote
    When the in-app auth browser returns a successful order callback
    Then onOrderCreated fires with the order id
    And the session closes with reason "completed"
    And the app does not navigate to the order-details screen

  Scenario: user cancels the external auth browser
    Given a headless buy is continuing with an external-browser quote on iOS
    When the user dismisses the in-app auth browser
    Then the session closes with reason "user_dismissed"
    And no error callback fires

  Scenario: custom-action provider (PayPal) checkout
    Given the flag is "all" on an RC build
    And I start a headless buy with a custom-action provider quote
    When the external checkout returns a successful order
    Then the session terminates via onOrderCreated

Screenshots/Recordings

N/A - logic-only change in the headless behavior layer; the headless flow renders no UI of its own (the consumer renders all visible UI), so there is no before/after to capture.

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.

Made with Cursor

…se 2)

P2.M6: the headless getQuotes facade now passes autoSelectProvider so the
controller's shared getSmartSelectedQuote ranks the quotes and the best lands
at success[0]; the UI never sorts locally.

P2.M1: make useContinueWithQuote's external-browser branch headless-aware.
iOS InAppBrowser.openAuth success resolves the provider callback into an
order and fires onOrderCreated + onClose(completed); cancel fires
onClose(user_dismissed); an open rejection / resolution failure fails the
session (QUOTE_FAILED). Android/system-browser success keeps the session
live for the deeplink return. Deeplink schemes and redirect URLs stay
mobile-side.

P2.M4: custom-action quotes ride the now-headless-aware external path, so
they complete via the same terminal callbacks.
@saustrie-consensys saustrie-consensys added team-money-movement issues related to Money Movement features no-changelog no-changelog Indicates no external facing user changes, therefore no changelog documentation needed labels Jul 7, 2026
@github-actions

github-actions Bot commented Jul 7, 2026

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.

@github-actions github-actions Bot added the size-M label Jul 7, 2026
…#9414)

Point the previewBuilds override at 15.1.0-preview-bbd2df29f so the mobile
Phase 2 work can be RC-tested against the public getSmartSelectedQuote and
scope-gated widening. Preview only; must be swapped to a released version
before merge.
@socket-security

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/​ramps-controller@​15.1.0-preview-685219bbf ⏵ 15.1.0-preview-bbd2df29fN/AN/AN/AN/AN/A

View full report

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

🔍 Smart E2E Test Selection

  • Selected E2E tags: SmokeMoney
  • Selected Performance tags: None (no tests recommended)
  • Risk Level: medium
  • AI Confidence: 88%
click to see 🤖 AI reasoning details

E2E Test Selection:
The changes are focused entirely on the Ramp/fiat on-ramp feature area:

  1. package.json: Updated @metamask/ramps-controller preview version (non-breaking bump). This is a dependency update for the ramps controller, which directly affects ramp/buy flows.

  2. useHeadlessBuy.ts: Added autoSelectProvider: true to the getQuotesRaw call. This changes how quotes are selected in the headless buy flow - the controller now handles ranking instead of the UI.

  3. useContinueWithQuote.ts: Added completeHeadlessExternalOrder - a new callback that handles the headless external browser terminal flow (P2.M1). This resolves provider callbacks into orders and routes them to session callbacks. Also adds handling for headless session management in the external browser path (Android and iOS InAppBrowser paths).

  4. useRampsQuotes.ts: Added autoSelectProvider and preferredProviderIds options to the quotes interface.

These changes affect the core buy/ramp flow: quote fetching, provider selection, and order completion via external browser. The SmokeMoney tag covers ramps flows including buy, quote fetching, and on-ramp flows. No swap confirmations are directly triggered by these changes (they're in the quote/provider selection layer), so SmokeConfirmations and SmokeSwap are not needed unless SmokeMoney tests exercise those paths - but the tag description says to add SmokeSwap/SmokeConfirmations only when Card Add Funds or similar flows execute swaps, which is not the case here.

The changes do NOT affect: accounts, network management, browser, snaps, staking, swaps, or any other feature area.

Performance Test Selection:
The changes are focused on the Ramp/fiat on-ramp feature area - specifically quote selection logic and headless external browser order completion. None of the available performance test tags (@PerformanceAccountList, @PerformanceOnboarding, @PerformanceLogin, @PerformanceSwaps, @PerformanceLaunch, @PerformanceAssetLoading, @PerformancePredict, @PerformancePreps) cover ramp/buy flows. The changes are also logic-level (adding parameters, new callbacks) rather than rendering or loading performance changes, so no performance tests are warranted.

View GitHub Actions results

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-changelog no-changelog Indicates no external facing user changes, therefore no changelog documentation needed size-M team-money-movement issues related to Money Movement features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant