Skip to content

feat(ramp): headless buy fixes#30103

Open
saustrie-consensys wants to merge 11 commits into
mainfrom
saustrie-consensys/headless-buy-phase-9
Open

feat(ramp): headless buy fixes#30103
saustrie-consensys wants to merge 11 commits into
mainfrom
saustrie-consensys/headless-buy-phase-9

Conversation

@saustrie-consensys
Copy link
Copy Markdown
Contributor

@saustrie-consensys saustrie-consensys commented May 13, 2026

Description

Fixes from Goktug's May 12 headless-buy testing thread, plus a small utility MetaMask Pay needs for mid-typing validation.

Independent of Phase 9.5 (#30104) in runtime — #30104 is currently git-stacked on this branch, so GitHub will auto-retarget its base to main when this PR merges. Either can land first.

Fixes from Goktug's May 12 testing

Three issues he filed; the fourth (empty white screen) is scoped to PR #30104.

  • Fix Change inpage/inpage.js -> entry/entry.js #2getQuotes no longer silently eats provider rejections. A 5 EUR amount under the provider's minimum used to return an empty success list and no error, so the consumer was stuck waiting. Now getQuotes throws a typed HeadlessBuyError AND fires onError + onClose on the active session. The error code is LIMIT_EXCEEDED for limit messages and QUOTE_FAILED for everything else (rate-limit messages are NOT mis-classified as limit-exceeded).
  • Fix Implement loading spinner during page loads #3.1 — onOrderCreated now hands the consumer the full order. Was (orderId) => void, now (orderId, order: RampsOrder) => void. Consumers can read order.walletAddress / order.provider.id straight from the callback without a separate lookup.
  • Fix Implement loading spinner during page loads #3.2 — JSDoc clarifying the asymmetric callback contract. onError does NOT fire when an order is created and then later fails (e.g. 3-D Secure rejection on a card). Consumers need to branch on the order's .status, or poll for settlement themselves — useFiatOrderStatus in feat: Add fiat payment confirmation flow with headless ramp integration placeholder #28152 is the canonical pattern (polls RampsController.getOrder directly).

Pre-quote static bounds (Suggestions #1 + #2 — MetaMask Pay)

Originally deferred; MMPay confirmed they want both pieces.

  • Inject inpage js #1 — Expose getProviderBuyLimit from the public headless barrel. Lets a consumer check whether a typed-in amount is in-bounds for (provider, currency, paymentMethod) without a network call — handy for disabling a "Get quote" button while the user types.
  • Change inpage/inpage.js -> entry/entry.js #2getQuotes runs that same check before paying the network round-trip. If every requested provider × payment-method has known bounds that reject the amount, it rejects synchronously with LIMIT_EXCEEDED. A single "passes" or "unknown bounds" candidate keeps the network call alive — same conservative posture UB2 takes.

Out of scope / deferred:

  • Fix Inject inpage js #1 (empty white screen) → Phase 9.5 PR feat(ramp): make HeadlessHost invisible (Phase 9.5) #30104.
  • getUserLimits swallow at useTransakRouting.ts:241 — not in Goktug's filed complaints; tracked separately.
  • The Deposit feature's identically-named navigateToOrderProcessingCallback is unrelated (different session machinery) — intentionally skipped.
  • Consumer-side updates in MetaMask Pay (useFiatConfirm / useFiatOrderStatus consuming the new onOrderCreated arg + getProviderBuyLimit) — lives in their own PRs.

Changelog

CHANGELOG entry: null

(internal API addition — no end-user-facing change in this PR; MMPay's user-visible loading UI lands in their downstream PRs.)

Related issues

Fixes: https://consensyssoftware.atlassian.net/jira/software/c/projects/TRAM/boards/1568?assignee=712020%3Afd12f7ea-d9e1-4a0a-8a26-36804c9e11c9&selectedIssue=TRAM-3530

Replaces the relevant Phase 9 scope of the combined PR #29930 (close after this + #30104 are open).

Parallel Phase 9.5 PR: #30104 (HeadlessHost goes invisible). No runtime merge-order dependency; #30104 is git-stacked on this branch.

Slack — Goktug's May 12 testing thread: https://consensys.slack.com/archives/C0AK3NXRM7W/p1778577403631269

Manual testing steps

Feature: Headless buy fixes from Goktug's May 12 testing thread

  Scenario: 5 EUR provider rejection surfaces to consumer (Fix #2 — Goktug complaint #2)
    Given an active headless session via `startHeadlessBuy`
    When the consumer calls `getQuotes` with a fiat amount below the provider's minimum
    Then `onError` fires with `{ code: 'LIMIT_EXCEEDED', details: { providerErrors: [...], source: 'network-reject' } }`
    And `onClose` fires with `{ reason: 'unknown' }`
    And the session is removed from the registry
    And the consumer's `await getQuotes(...)` catch also receives the same structured error

  Scenario: onOrderCreated fires with the order snapshot (Fix #3.1)
    Given a card order created with the Transak test card 4242 4242 4242 4242
    When `onOrderCreated(orderId, order)` fires
    Then the second arg is a valid `RampsOrder` with `walletAddress` and `provider.id`
    And the consumer can branch on `order.status` or poll `RampsController.getOrder` for settlement

  Scenario: Pre-quote static bounds short-circuit (Suggestion #2)
    Given a Provider catalog where `transak-native`'s minAmount for EUR + card is 10
    When the consumer calls `getQuotes({ amount: 5, currency: 'EUR', providerIds: ['transak-native'], paymentMethodIds: ['debit-credit-card'] })`
    Then `getQuotes` rejects synchronously with `{ code: 'LIMIT_EXCEEDED', details: { source: 'static-bounds', rejections: [...] } }`
    And no network call to `RampsController.getQuotes` was made

  Scenario: Pre-quote skip when at least one candidate accepts (Suggestion #2)
    Given two providers — transak (min 10 EUR) and moonpay (min 1 EUR), both card-supporting
    When the consumer calls `getQuotes({ amount: 5, providerIds: ['transak', 'moonpay'], paymentMethodIds: ['debit-credit-card'] })`
    Then the pre-flight does NOT short-circuit (moonpay would accept)
    And the network call proceeds normally

Screenshots/Recordings

N/A — internal API addition. No user-facing UI change in this PR.

Before

N/A

After

N/A

Pre-merge author checklist

Performance checks (if applicable)

  • I've tested on Android
  • I've tested with a power user scenario
  • I've instrumented key operations with Sentry traces for production performance metrics

(N/A for this phase — internal API addition. The next consumer-facing change lands in MMPay's downstream PR.)

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
Expands the headless public callback API and changes useHeadlessBuy.getQuotes behavior to throw and auto-fail active sessions, which could break existing consumers and alter control flow around quote fetching and session lifecycle.

Overview
Headless buy now surfaces quote failures instead of silently returning empty results: useHeadlessBuy.getQuotes throws typed errors (LIMIT_EXCEEDED vs QUOTE_FAILED), and when a headless session is active it routes these through failSession to trigger onError + onClose and end the session.

Adds a pre-network static bounds check using provider catalog limits; if all requested provider×payment-method candidates have known bounds that reject the amount, getQuotes short-circuits with LIMIT_EXCEEDED. getProviderBuyLimit is also re-exported from the public headless barrel for consumer-side validation.

Widens the headless session callback signature to onOrderCreated(orderId, order) and plumbs the order snapshot through the headless success paths (Checkout callback flow and useTransakRouting), updating the playground and tests accordingly, plus clarifying JSDoc about post-creation order status behavior.

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

saustrie-consensys and others added 2 commits May 13, 2026 06:16
Phase 9 (MMPay TPC dependency)
  - New imperative `awaitOrderTerminalState(orderId, { timeoutMs?, pollIntervalMs?, walletAddress? })`
    in `headless/orderTerminalState.ts` resolves with the `RampsOrder` once
    its status reaches `Completed | Failed | Cancelled | IdExpired`. Drives
    a redux subscription as the fast path and self-polls
    `RampsController.getOrder` as the slow path so it is not coupled to
    the unified order processor's `<FiatOrders />` mount lifecycle.
  - New imperative `getOrder` and `refreshOrder(stringOrOrder)` siblings
    in the same module — controllers can call them without going through
    React. `useHeadlessBuy()` exposes thin passthroughs on the same names
    for React consumers; `getOrderById` is preserved (deprecated) for
    back-compat.
  - Two typed errors with Hermes-safe `Object.setPrototypeOf` fixes:
    `OrderTerminalStateTimeoutError` and `RefreshOrderUnresolvableError`.
  - Playground gets an Order tracking panel after `onOrderCreated` —
    surfaces `orderId`, current status (live from `getOrder`), and
    Refresh / Await terminal state actions wired to the new API.

CHANGELOG entry: null
SonarQube flagged the playground for cognitive complexity (36 > 30) and
three nested ternaries in the await-status badge rendering. Extracting
`OrderTrackingPanel` as its own component pushes the complexity back
under the threshold and clears the nested ternaries by routing
color/text selection through `getAwaitBadgeColor` / `getAwaitBadgeText`
helpers. No behaviour changes.
@saustrie-consensys saustrie-consensys requested a review from a team as a code owner May 13, 2026 13:22
@metamaskbotv2 metamaskbotv2 Bot added the team-money-movement issues related to Money Movement features label May 13, 2026
@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.

@github-actions github-actions Bot added the pr-not-ready-for-e2e Skip E2E and block merging. Remove this label once the PR is ready to run the E2E tests. label May 13, 2026
@metamaskbotv2 metamaskbotv2 Bot added the INVALID-PR-TEMPLATE PR's body doesn't match template label May 13, 2026
Comment thread app/components/UI/Ramp/headless/types.ts Outdated
@codecov-commenter
Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.93750% with 18 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.75%. Comparing base (3751d9a) to head (4257d6a).
⚠️ Report is 119 commits behind head on main.

Files with missing lines Patch % Lines
...mp/Views/HeadlessPlayground/HeadlessPlayground.tsx 76.00% 8 Missing and 4 partials ⚠️
.../components/UI/Ramp/headless/orderTerminalState.ts 93.05% 2 Missing and 3 partials ⚠️
app/components/UI/Ramp/headless/useHeadlessBuy.ts 83.33% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #30103      +/-   ##
==========================================
+ Coverage   81.54%   81.75%   +0.20%     
==========================================
  Files        5343     5388      +45     
  Lines      142128   143619    +1491     
  Branches    32411    32803     +392     
==========================================
+ Hits       115899   117410    +1511     
+ Misses      18299    18185     -114     
- Partials     7930     8024      +94     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Four fixes addressing complaints #2 (5 EUR provider rejection hang) and #3
(3-D Secure failure handling) from the May 12 testing thread. Complaint #1
(empty white screen) is scoped to the Phase 9.5 PR.

- Fix #2: inspect `quotesResponse.error[]` in `getQuotes` and throw a
  structured `HeadlessBuyError` so the 5 EUR scenario surfaces to the
  consumer. Routes through the active session via `failSession` (Design B)
  when one exists so `onError` + `onClose` fire alongside the awaited
  rejection.
- Fix #3.1: widen `onOrderCreated` to `(orderId, order)` and update both
  fire sites (aggregator widget in Checkout.tsx; shared
  `navigateToOrderProcessingCallback` in useTransakRouting.ts) plus its two
  callers. Lets MMPay read `order.walletAddress` without an extra
  `getOrder` round-trip.
- Fix #3.2: load-bearing JSDoc on `onOrderCreated`, `awaitOrderTerminalState`,
  and `AwaitOrderTerminalStateOptions.timeoutMs` covering the asymmetric-
  callback contract (`onError` does not fire for created-then-failed
  orders), the creation-snapshot freshness caveat, the unbounded-timeout
  warning, and the recommended `{ walletAddress: order.walletAddress,
  timeoutMs: 5 * 60 * 1000 }` pattern.
- Fix #3.3: add `AwaitOrderTerminalStatePrerequisitesError` (Hermes-safe
  via `Object.setPrototypeOf`) and a runtime pre-flight check in
  `awaitOrderTerminalState`. Rejects synchronously when the order isn't in
  redux AND `options.walletAddress` is not provided, converting a silent
  unbounded hang into an actionable error for the consumer's catch block.

Slack thread:
https://consensys.slack.com/archives/C0AK3NXRM7W/p1778577403631269

Test coverage: 255 tests across `app/components/UI/Ramp/{headless,hooks,
Views/Checkout,Views/HeadlessPlayground}` — all green. New test cases
cover provider-rejection classification (LIMIT_EXCEEDED vs QUOTE_FAILED,
rate-limit false-positive guard, SDK code preference over message
parsing), each fire site's new 2nd-arg passthrough, and the Fix #3.3
pre-flight reject + Hermes-safe cross-module `instanceof`.

Out of scope (deferred): Fix #1 (transparent stack wrapper, Phase 9.5
PR's deferral list), `getUserLimits` swallow follow-up, `getProviderBuyLimit`
belt-and-braces validation, Deposit feature (its identically-named
`navigateToOrderProcessingCallback` is not wired to the headless session
registry), and consumer-side TPC updates (TPC's own repo).
Four small clarifications from the post-implementation review of
f585359. No behavior changes; tests unaffected (184 still green).

- useHeadlessBuy.ts: document `getActiveSessionId()` semantics in the
  Fix #2 Design B routing block — calling `getQuotes` while an unrelated
  session is active will tear that session down. Safe today because of the
  single-live-session invariant enforced by `startHeadlessBuy`'s auto-cancel,
  but worth flagging for any future multi-session API.
- types.ts: tighten the `onOrderCreated` JSDoc — clarify that the headless
  *session* terminates on order creation, not the underlying *order* (which
  may still be non-terminal). The original phrasing risked the exact
  misunderstanding Fix #3.2 is meant to prevent.
- orderTerminalState.ts: reword the prerequisite-error message from "not
  in redux" to "not yet in redux" — acknowledges the addOrder-flush race
  the JSDoc already documents.
- useTransakRouting.test.ts: tighten the bank-transfer fire-site assertion
  from `expect.objectContaining` to exact `refreshedOrder` identity, to
  match the native-card assertion. Catches any future drift that wraps or
  derives the order before passing it to `onOrderCreated`.
@metamaskbotv2 metamaskbotv2 Bot removed the INVALID-PR-TEMPLATE PR's body doesn't match template label May 13, 2026
Two follow-ups to the Phase 9 fix bundle that close the remaining "Fix #2
follow-up" gap from the deferral list. Mirrors UB2's `useProviderLimits`
behaviour for headless consumers (notably MMPay's `TransactionPayController`).

- Suggestion #1: re-export `getProviderBuyLimit` (and its `ProviderBuyLimit`
  type) from `app/components/UI/Ramp/headless/index.ts`. Lets consumers do
  their own static bounds checks without importing from an internal utility
  path. Useful for e.g. disabling a "Get quote" button as the user types.
- Suggestion #2: inside `useHeadlessBuy`'s `getQuotes`, run a no-network
  pre-flight check before the existing `getQuotesRaw` call. When
  `params.providerIds`, `params.paymentMethodIds`, and a fiat currency are
  all resolvable, and every `(provider × paymentMethodId)` candidate has
  known bounds that reject `params.amount`, short-circuit immediately with
  `LIMIT_EXCEEDED` instead of paying the network round-trip. Routes the
  rejection through `failSession` (Design B) when a session is active,
  mirroring the post-network rejection path so consumer UX is symmetric.
  Adds `currency?: string` to `HeadlessGetQuotesParams` so consumers can
  override the userRegion default when needed.

Decision rule: short-circuit only when *every* candidate has known
out-of-bounds. A single "passes" or "unknown bounds" candidate keeps the
network call alive — preserves today's behaviour of letting the network
decide when we can't be certain. Matches UB2's posture.

Skip conditions (let the network decide):
- `amount <= 0` or non-finite (UB2 parity — `useProviderLimits` returns
  `null` for non-positive amounts so the UI shows no error mid-typing).
- `providerIds` missing/empty, `paymentMethodIds` missing/empty, fiat
  currency unresolvable, or the providers catalog hasn't loaded yet.
- Any candidate's provider isn't in the catalog (unknown — let the
  network return the provider error; Fix #2's post-network path catches it).

`details.source` discriminator added to both LIMIT_EXCEEDED paths
(`static-bounds` for pre-flight, `network-reject` for post-network). Lets
consumers branch on which layer caught the rejection without sniffing for
the presence of `providerErrors` vs `rejections`.

Test coverage: 53 tests in `describe('getQuotes', ...)` — happy paths,
both rejection paths, Design B routing, boundary equality (amount ===
minAmount / === maxAmount → in-bounds), mix of known-reject + known-accept
candidates, NaN, amount === 0, empty arrays, missing currency, missing
provider catalog, `provider.limits === undefined`, an explicit
`getProviderBuyLimit` index re-export check. 200 jest tests across
`app/components/UI/Ramp/{headless,hooks,Views/Checkout}` all green.
`yarn tsc --noEmit`, `yarn eslint`, `yarn prettier --check` all clean.
Comment thread app/components/UI/Ramp/Views/HeadlessPlayground/HeadlessPlayground.tsx Outdated
Comment thread app/components/UI/Ramp/headless/useHeadlessBuy.ts Outdated
…gbot)

The `@deprecated` JSDoc on `getOrderById` claimed it "forwards to the same
implementation" as `getOrder`. That's false: the two methods use different
selectors with different scoping.

- `getOrderById` (via `useRampsOrders`) reads through
  `selectRampsOrdersForSelectedAccountGroup` — account-group-scoped, only
  returns orders whose `walletAddress` belongs to the current account
  group's wallets.
- `getOrder` (via `orderTerminalState.ts`) reads through the unscoped
  `selectRampsOrders` selector — returns any matching order regardless of
  which account group owns it.

A consumer following the deprecation guidance and switching from
`getOrderById` to `getOrder` would silently lose account-group filtering
(could surface an order from a different account they're not currently
viewing).

Behaviour itself is deliberate: Phase 9's `getOrder` is designed for non-
React contexts (e.g. MetaMask Pay's `TransactionPayController`) that don't
have a "selected account group" concept and would lose visibility into
their own tracked orders if it scoped. So this commit is documentation-
only — it makes the two contracts explicit so consumers know which one
they need. Updated:
- `types.ts` `HeadlessBuyResult.getOrderById` and `.getOrder` JSDocs.
- `orderTerminalState.ts` imperative `getOrder` JSDoc.

Filed by Cursor Bugbot (commit 4257d6a) on PR #30103.
Both filed against commit b2eb3fa after the Suggestion #1 + #2 push.

1) HeadlessPlayground: pass `walletAddress` to `awaitOrderTerminalState`.
   The "Await terminal state" button was calling `awaitOrderTerminalState`
   with only `{ timeoutMs }`. Combined with Fix #3.3's pre-flight check,
   tapping it during the brief window between `onOrderCreated` firing and
   `addOrder` flushing the order to redux would reject with
   `AwaitOrderTerminalStatePrerequisitesError`. The playground is supposed
   to be the reference implementation for the JSDoc-recommended pattern,
   so it now demonstrates that pattern: capture `order` from the widened
   `onOrderCreated(orderId, order)` signature (Fix #3.1), persist it in
   state, and pass `order.walletAddress` to `awaitOrderTerminalState`.
   Eliminates the race entirely.

2) `getQuotes` classification: aggregate-some across all provider errors
   leaked verdicts across providers. If provider A returned a buy-limit
   code and provider B returned a rate-limit code, the previous code
   computed `sdkCodeSaysLimit = true` AND `sdkCodeSaysRateRequest = true`,
   then evaluated `sdkCodeSaysLimit && !sdkCodeSaysRateRequest` → `false`
   → fell through to `QUOTE_FAILED` even though A clearly hit a buy bound.
   Now classifies per provider: a provider counts as a buy-limit signal
   only when ITS code says limit AND NOT rate/request — verdicts no
   longer leak across providers. Added a regression test that mixes
   `AMOUNT_LIMIT_EXCEEDED` (transak) with `RATE_LIMIT_EXCEEDED` (moonpay)
   and asserts the result is still `LIMIT_EXCEEDED`.

Tests: 126 jest tests across the two touched suites all green.
`yarn tsc --noEmit`, `yarn eslint`, `yarn prettier --check` clean.
Comment thread app/components/UI/Ramp/headless/useHeadlessBuy.ts Outdated
…cov)

Three pieces of bot feedback addressed in one commit:

1) Cursor Bugbot (Medium): classification leaked across providers on the
   message-regex fallback path. The previous fix only handled the SDK
   `code`-based path per-provider; the regex still ran on a
   `combinedMessage` joined across all provider errors. If provider A
   returned "Below minimum buy amount" (no SDK code) and provider B
   returned "Rate limit exceeded" (no SDK code), the combined message
   contained both "minimum" AND "rate" — flipping the result to
   `QUOTE_FAILED` even though A clearly hit a buy bound. Refactored to a
   single per-provider `some()` loop that classifies each provider's own
   `code` OR `message` independently. Added a regression test.

2) SonarCloud (Major, typescript:S6759): `OrderTrackingPanelProps` had
   mutable props. React-component prop interfaces should be read-only —
   marked all seven fields `readonly`.

3) Codecov: the existing "unrecognized providerId" test used
   `providers: []` which short-circuits at the catalog-empty guard before
   reaching the unknown-provider branch inside the candidate loop. Added
   a sibling test where the catalog has SOME entries (just not the
   requested one) so the `if (!provider)` branch and its
   `bounds: undefined` path actually execute. Closes the
   `useHeadlessBuy.ts:134-137` coverage gap.

Tests: 193 jest tests across the touched suites all green.
`yarn tsc --noEmit`, `yarn eslint`, `yarn prettier --check` clean.
Comment thread app/components/UI/Ramp/Views/HeadlessPlayground/HeadlessPlayground.tsx Outdated
Phase 9 added orderTerminalState.ts, which re-implements two methods
that already exist in useRampsOrders (getOrderById and refreshOrder)
plus a new awaitOrderTerminalState polling primitive. The deviation
from Pedro's original facade pattern (Phase 2, #29144) was motivated
by the assumption that a non-React, cross-account-group consumer
needed an imperative API. No such consumer exists in Headless Buy's
consumer set — the one known consumer (useFiatConfirm in #28152)
uses only startHeadlessBuy, and order status display
(useFiatOrderStatus in the same PR) polls
Engine.context.RampsController.getOrder directly via setInterval.
This commit reverts the deviation.

Removed:
- orderTerminalState.ts + tests (re-implementations of useRampsOrders
  primitives, motivated by a phantom non-React consumer)
- useHeadlessBuy passthroughs (getOrder, refreshOrder,
  awaitOrderTerminalState)
- Playground OrderTrackingPanel + testIDs + styles
- i18n order_tracking_* keys (en.json only — never propagated)
- Fix #3.3 pre-flight error (only existed to harden the removed
  function)
- Phase 9 deprecation JSDoc on the pre-existing getOrderById

Kept:
- Fix #2 (provider-rejection routing through onError)
- Fix #3.1 (onOrderCreated(orderId, order) widening)
- Fix #3.2 (asymmetric-callback JSDoc + per-path freshness table,
  pared)
- Suggestion #1 (getProviderBuyLimit re-export)
- Suggestion #2 (pre-quote static bounds short-circuit)
- Analytics + deeplink utilities
- sessionRegistry Fix #2 routing
- Pedro's pre-existing getOrderById on useHeadlessBuy (with
  Phase 9's deprecation JSDoc reverted to its pre-Phase-9 form)

Note for future maintainers:
- The Hermes-safe Object.setPrototypeOf pattern for typed errors is
  no longer exercised in the headless module's tests. Reintroduce
  the pattern if a future typed error is added.
- Fix #3.1 propagation coverage at the playground layer is removed,
  but the propagation itself remains covered by Checkout /
  useTransakRouting fire-site tests.
@saustrie-consensys saustrie-consensys changed the title feat(ramp): expose order observation API (Phase 9) feat(ramp): headless buy fixes from May 12 testing May 14, 2026
@saustrie-consensys saustrie-consensys changed the title feat(ramp): headless buy fixes from May 12 testing feat(ramp): headless buy fixes May 14, 2026
@saustrie-consensys saustrie-consensys removed the pr-not-ready-for-e2e Skip E2E and block merging. Remove this label once the PR is ready to run the E2E tests. label May 14, 2026
@saustrie-consensys saustrie-consensys self-assigned this May 14, 2026
@github-actions github-actions Bot added size-L and removed size-XL labels May 14, 2026
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

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 1 potential issue.

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 5a02bbd. Configure here.

Comment thread app/components/UI/Ramp/hooks/useTransakRouting.ts Outdated
@github-actions
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:
All 12 changed files are within the Ramp/fiat on-ramp subsystem (app/components/UI/Ramp/). The changes include:

  1. API signature change: onOrderCreated callback widened from (orderId: string) to (orderId: string, order: RampsOrder) — a breaking change for headless buy consumers.
  2. New static bounds pre-check in useHeadlessBuy.getQuotes: validates amount against provider limits before making network calls, with LIMIT_EXCEEDED error classification.
  3. Provider error classification in getQuotes: distinguishes LIMIT_EXCEEDED vs QUOTE_FAILED from network responses.
  4. useTransakRouting: Updated to pass full order object to onOrderCreated in both the bank transfer and WebView paths.
  5. Checkout.tsx: Updated headless session callback to pass rampsOrder as second argument.
  6. index.ts: Exports getProviderBuyLimit utility.

These changes directly affect the on-ramp/buy flow (unified buy, Transak routing, checkout WebView), which is covered by SmokeMoney tests (specifically onramp-unified-buy.spec.ts, deeplink-to-buy-flow.spec.ts, deeplink-to-sell-flow.spec.ts). No other subsystems (confirmations, accounts, network, swaps, etc.) are affected. The headless playground is a dev/test tool and not covered by E2E tests directly.

Performance Test Selection:
The changes are confined to the Ramp/fiat on-ramp subsystem and involve API signature changes, error classification logic, and callback updates. These are not performance-sensitive changes — they don't affect UI rendering, list components, app startup, login flows, or any of the performance-critical paths measured by the available performance test tags.

View GitHub Actions results

@sonarqubecloud
Copy link
Copy Markdown

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

Labels

size-L team-money-movement issues related to Money Movement features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement loading spinner during page loads Change inpage/inpage.js -> entry/entry.js

2 participants