Skip to content

feat(ramp): headless buy playground and Phase 3 session API#29144

Merged
wachunei merged 4 commits into
mainfrom
poc/headless-buy-phase-3
Apr 22, 2026
Merged

feat(ramp): headless buy playground and Phase 3 session API#29144
wachunei merged 4 commits into
mainfrom
poc/headless-buy-phase-3

Conversation

@wachunei

@wachunei wachunei commented Apr 21, 2026

Copy link
Copy Markdown
Member

Description

This draft PR stacks the Headless Buy proof-of-concept work through Phase 3 (plus Phase 3.1) on top of main. It is intended for incremental review and CI validation before follow-up phases (BuildQuote refactor, Headless Host, routing callbacks).

Scope vs main (full branch)

  • Phase 1 — Dev-only Headless Buy playground (Ramp Settings entry when isInternalBuild), route registration, and a scrollable UI to inspect useRampsController catalog data with accordions, selectors, and summary.
  • Phase 2useHeadlessBuy read-only facade: tokens, providers, payment methods, countries, orders, getQuotes, aggregated loading/errors; playground sandbox simulating an external consumer (hardcoded defaults + accordion overrides, quote details, i18n).
  • Phase 3sessionRegistry (module-level Map for callbacks + params), startHeadlessBuy (creates session, navigates to BuildQuote with headlessSessionId), BuildQuoteParams.headlessSessionId plumbing, playground Start headless buy / Cancel / event log, and tests.

Diff vs previous POC branches (incremental)

Compare Stat (approx.) What it adds
main...poc/headless-buy-phase-1 Phase 1 only Playground screen, route, Settings row, PLAN.md scaffold.
poc/headless-buy-phase-1...poc/headless-buy-phase-2 +~2.3k lines useHeadlessBuy, types, barrel, playground wiring to hook (getQuotes, amount, quotes UI, sandbox, i18n).
poc/headless-buy-phase-2...poc/headless-buy-phase-3 +~1k lines sessionRegistry + tests, startHeadlessBuy + tests, BuildQuote param + nav test, playground session lifecycle UI + tests, PLAN.md updates below.

Full branch vs main: main...poc/headless-buy-phase-3.

PLAN.md changes from Phase 2 → Phase 3

  • Checklist: Phase 3 and 3.1 marked complete; Phase 4 wording updated to useContinueWithQuote(quote, ctx) and reuse by BuildQuote + headless callers; new Phase 5b (quote-first startHeadlessBuy({ quote })); Phase 8 expanded for flow dismissalonClose, closeSession idempotency, and useHeadlessSessionDismissal (BuildQuote now, Headless Host after 4b).
  • Architecture: “Key idea” no longer assumes pre-seeding the controller from the hook; it describes session registry + navigation param only, with Phase 3.1 clarifying no controller writes from useHeadlessBuy.
  • Phase 3 section: Implementation steps reordered to match shipped behavior; explicit note that controller pre-seed was superseded by Phase 3.1.
  • New Phase 3.1 section: Documents the type mismatch (setters expect PaymentMethod / Provider objects, not ids) and catalog hydration race; resolution is params on session only, resolution at destination.
  • New Phase 5b section: Discriminated HeadlessBuyParams, Host behavior for continue-with-quote, tests, and deliverable for devs who already have a Quote from getQuotes.
  • Expanded Phase 8: Table of triggers (user_dismissed, completed, consumer_cancelled, unknown), useHeadlessSessionDismissal, centralized closeSession, tests, and note that today the playground still needs manual cancel until this ships.

Not in this PR yet: BuildQuote does not read headlessSessionId or call session.callbacks — that remains Phase 4–8 per the plan. Consumer cancel() from Phase 3 is the only guaranteed session teardown besides GC.

Changelog

CHANGELOG entry: null

Related issues

Fixes:

Jira: TRAM-3524 | TRAM-3525 | TRAM-3526

Manual testing steps

Feature: Headless Buy playground (internal build)

  Scenario: Open playground from Ramp settings
    Given the app is an internal build with dev Ramp settings visible
    When the user opens Ramp settings and taps the Headless Buy playground entry
    Then the Headless Buy playground screen is shown with catalog accordions and the headless consumer sandbox

  Scenario: Fetch quotes from the sandbox
    Given the user is on the Headless Buy playground
    When the user enters a positive fiat amount and taps Get quotes
    Then quotes are listed in the quotes section without crashing the app

  Scenario: Start headless buy and cancel from the sandbox
    Given the user is on the Headless Buy playground with a valid amount in the sandbox
    When the user taps Start headless buy
    Then the event log records a started session and BuildQuote opens with navigation
    When the user returns to the playground and taps Cancel headless session
    Then the session is cleared and onClose consumer_cancelled is reflected in the event log

Screenshots/Recordings

Before

N/A

After

headlessbuy_sandbox.mp4

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

Medium Risk
Adds new navigation routes, a sizable new dev-only screen, and a new headless buy hook/session registry that touches quote fetching and navigation into the buy flow; mistakes could impact Ramp navigation or quote requests despite UI gating to internal builds.

Overview
Introduces an internal-build-only “Headless Buy playground” reachable from Ramp Settings, plus a new Routes.RAMP.HEADLESS_PLAYGROUND stack screen.

Adds a new useHeadlessBuy facade and supporting headless/ modules, including an in-memory sessionRegistry for storing per-attempt params + non-serializable callbacks and a startHeadlessBuy API that creates a session, navigates into BuildQuote with a threaded headlessSessionId, and supports programmatic cancel via onClose({ reason: 'consumer_cancelled' }).

Plumbs headlessSessionId through BuildQuoteParams, expands i18n strings for the playground UI, and adds comprehensive unit tests covering the Settings entry gating, playground behavior, getQuotes wallet resolution, session registry TTL/CRUD, and headless session navigation/cancel.

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

Add a phased plan under app/components/UI/Ramp/headless/ outlining how
to expose the Unified Buy v2 flow to external consumers via a
useHeadlessBuy hook and a dev-only playground screen, including the
session registry pattern, the Headless Host stack-base solution for the
Transak auth loop, and the order-callback path replacing the order
processing redirect.
Introduces a dev-mode-only playground screen reachable from the Buy/Sell
settings (gated by isInternalBuild) to incrementally prototype the
useHeadlessBuy hook. The screen currently surfaces useRampsController
state in collapsible sections for region, providers, tokens and payment
methods, lets the user select a provider/token, attempts to default to
mUSD on Linea and the Transak native provider when available, and shows
a summary of the active selections.
Introduce a read-only `useHeadlessBuy` hook that wraps `useRampsController`
and exposes the catalog (tokens, providers, paymentMethods, countries),
`userRegion`, `orders`, `getOrderById`, an aggregated `isLoading` / `errors`
surface, and a `getQuotes(params)` helper that resolves the wallet address
from the asset's chain id so external callers can fetch quotes without
pre-seeding controller state.

Wire the playground to the new hook: amount input, country/payment-method
pickers, "Get quotes" button, and a quote list that renders the resolved
payment method name, fee breakdown, reliability score and tag badges. A
clearly delimited "headless consumer simulation" section uses hardcoded
asset/payment/provider IDs that can be overridden from the pickers above
(with Reset links) so the hook is exercised in isolation from the
controller.
Add a module-level session registry that holds non-serializable headless
buy callbacks keyed by sessionId, and a startHeadlessBuy API on
useHeadlessBuy that creates a session and navigates into the existing
BuildQuote screen with the headlessSessionId on params. The screen does
not branch on the param yet — Phase 3 only plumbs the id and validates
the lifecycle end-to-end through the playground's event log.

Includes the Phase 3.1 follow-up: startHeadlessBuy no longer writes to
RampsController. The previous pre-seed called setSelectedPaymentMethod
and setSelectedProvider with raw ids, but those setters take full
PaymentMethod / Provider objects and the catalog can still be loading at
that point. Inputs now live on the HeadlessSession only; the destination
screen resolves them when the catalog is hydrated.

Playground gains a noticeable headless-consumer simulation block with a
"Start headless buy" button, a cancel control and an event log so we can
exercise the lifecycle (started → onOrderCreated / onError / onClose →
ended) without leaving the dev sandbox.

PLAN.md is updated with Phase 3.1 (this fix), an expanded Phase 4
description, a new Phase 5b (quote-first headless start path) and an
expanded Phase 8 that captures the auto-onClose-on-dismissal work and
the closeSession idempotency contract.
@wachunei wachunei self-assigned this Apr 21, 2026
@metamaskbotv2 metamaskbotv2 Bot added the team-money-movement issues related to Money Movement features label Apr 21, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🔍 Smart E2E Test Selection

⏭️ Smart E2E selection skipped - draft PR

All E2E tests pre-selected.

View GitHub Actions results

@wachunei wachunei marked this pull request as ready for review April 21, 2026 19:49
@wachunei wachunei requested review from a team as code owners April 21, 2026 19:50
@sonarqubecloud

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown
Contributor

E2E Fixture Validation — Schema is up to date
12 value mismatches detected (expected — fixture represents an existing user).
View details

@Cal-L Cal-L 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.

LGTM

@wachunei wachunei added this pull request to the merge queue Apr 22, 2026
Merged via the queue into main with commit 96fc866 Apr 22, 2026
232 of 235 checks passed
@wachunei wachunei deleted the poc/headless-buy-phase-3 branch April 22, 2026 19:15
@github-actions github-actions Bot locked and limited conversation to collaborators Apr 22, 2026
@metamaskbotv2 metamaskbotv2 Bot added the release-7.75.0 Issue or pull request that will be included in release 7.75.0 label Apr 22, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

release-7.75.0 Issue or pull request that will be included in release 7.75.0 size-XL team-money-movement issues related to Money Movement features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants