feat(ramps): refactors order state into the controller#26596
feat(ramps): refactors order state into the controller#26596georgeweiler merged 28 commits intomainfrom
Conversation
|
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. |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
app/components/UI/Ramp/Aggregator/Views/OrdersList/OrdersList.tsx
Outdated
Show resolved
Hide resolved
app/components/UI/Ramp/Aggregator/Views/OrdersList/OrdersList.tsx
Outdated
Show resolved
Hide resolved
## Explanation
<!--
Thanks for your contribution! Take a moment to answer these questions so
that reviewers have the information they need to properly understand
your changes:
* What is the current state of things and why does it need to change?
* What is the solution your changes offer and how does it work?
* Are there any changes whose purpose might not obvious to those
unfamiliar with the domain?
* If your primary goal was to update one package but you found you had
to update another one along the way, why did you do so?
* If you had to upgrade a dependency, why did you do so?
-->
The ramps controller currently has no concept of orders -- it can fetch
them from the V2 API via `getOrder()` and `getOrderFromCallback()`, but
doesn't store, track, or poll them. The mobile app stores V2 orders in
Redux as `FiatOrder` objects (the legacy aggregator type), which
requires a wasteful round-trip: the V2 API returns a clean `RampsOrder`,
`rampsOrderToFiatOrder()` converts it to `FiatOrder` for Redux, then
every consumer casts `order.data as RampsOrder` to get the original data
back.
This PR makes the controller the authority for V2 orders:
**State**: Adds `orders: RampsOrder[]` to `RampsControllerState` with
`persist: true`. Orders are stored as the native V2 type -- no wrapper,
no conversion.
**Order management**: Adds `addOrder(order)` and
`removeOrder(providerOrderId)` methods. `getOrder()` now also updates
the order in state if it already exists there, keeping the persisted
data fresh on every fetch.
**Polling**: Adds `startOrderPolling()` / `stopOrderPolling()`. The
controller polls pending orders (status `PENDING`, `CREATED`, `UNKNOWN`,
`PRECREATED`) at a 30-second interval with exponential backoff on errors
and respect for `pollingSecondsMinimum` from the API response. Polling
metadata (`lastTimeFetched`, `errorCount`) is tracked in a private map,
keeping `RampsOrder` as a clean API response type. `destroy()` stops
polling automatically.
**Domain event**: Adds `RampsController:orderStatusChanged` event,
published when a polled order's status transitions. Payload is `{ order:
RampsOrder, previousStatus: RampsOrderStatus }`. Mobile subscribes to
this at init time for notifications and analytics -- same pattern as
`TransactionController:transactionConfirmed`.
**Type addition**: Adds `OrderPaymentDetail` type to `RampsOrder` for
bank transfer instruction fields (account number, IBAN, routing number,
etc.) that providers like Transak attach to manual payment orders. This
is a provider-agnostic type with the same shape as
`TransakOrderPaymentMethod` but defined in `RampsService` to avoid
cross-service coupling.
## References
<!--
Are there any issues that this pull request is tied to?
Are there other links that reviewers should consult to understand these
changes better?
Are there client or consumer pull requests to adopt any breaking
changes?
For example:
* Fixes #12345
* Related to #67890
-->
MetaMask/metamask-mobile#26596
## 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)
- [ ] 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**
> Adds new persisted `orders` state plus background polling/timer logic
and a new event, which could affect performance and order-update
correctness if polling/backoff or merge semantics are wrong.
>
> **Overview**
> **Adds first-class V2 order tracking to `RampsController`.** The
controller state now persists `orders: RampsOrder[]`, and `getOrder()`
will merge API responses into any matching in-state order to keep cached
fields (e.g. `paymentDetails`).
>
> **Introduces order CRUD + polling + status eventing.** New
`addOrder`/`removeOrder` APIs manage orders by `providerOrderId`,
`startOrderPolling`/`stopOrderPolling` periodically refresh *pending*
orders with backoff and `pollingSecondsMinimum`, and a new
`RampsController:orderStatusChanged` event is published on status
transitions.
>
> **Extends order typing and tests.** `RampsOrder` gains optional
`paymentDetails: OrderPaymentDetail[]`, exports are updated, changelog
updated, and the test suite adds extensive coverage for merging,
polling, backoff, terminal statuses, and event publication.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
8469a69. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
| return [statusColor, statusText]; | ||
| } | ||
|
|
||
| function DisplayOrderListItem({ item }: { item: DisplayOrder }) { |
There was a problem hiding this comment.
👆 V2 and Legacy orders in harmony
| ); | ||
|
|
||
| const renderItem = ({ item }: { item: FiatOrder }) => ( | ||
| const handleItemPress = useCallback( |
|
@cursoragent review this PR |
|
@cursoragent fix this test app/core/Engine/Engine.test.ts |
|
Fixed the failing What I did:
The test should now pass since the initial state fixture matches the actual controller state. |
The RampsController now manages V2 order state via an orders array. Update the initial background state fixture to include this field so the 'matches initial state fixture' test passes. Co-authored-by: George Weiler <georgejweiler@gmail.com>
app/components/UI/Ramp/Aggregator/Views/OrdersList/OrdersList.tsx
Outdated
Show resolved
Hide resolved
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.
🔍 Smart E2E Test Selection
click to see 🤖 AI reasoning detailsE2E Test Selection: Performance Test Selection: |




Description
Moves V2 ramp order state management from Redux (
FiatOrder) into theRampsController, eliminating the conversion layer where cleanRampsOrderAPI data was mangled into a legacy shape and immediately cast back.This PR will allow for future PRs such as a headless ramps experience where callers can subscribe to order updates:
#26728
or implementing websocket connections for real-time order status updates:
MetaMask/core#8075
Please reference loom videos below for a demo of this PR.
What changed:
Order creation (Checkout + Transak native):
addOrder(rampsOrder)stores directly in controller state. No morecreateInitialFiatOrder(),rampsOrderToFiatOrder(), ordispatch(addFiatOrder()). Transak deposit orders mergepaymentDetailsfrom theTransakDepositOrderonto theRampsOrderbefore storing, preserving inline bank transfer details.Order detail screen:
OrderDetails+OrderContentreadRampsOrderfrom controller state viauseRampsOrdershook. No Redux, noprocessFiatOrder, nouseIntervalpolling. Pull-to-refresh callsrefreshOrder(). Controller polling keeps data fresh automatically.Bank details screen:
V2BankDetailsreads order lifecycle from controller state and fetches deposit-specific data (payment details) fromTransakService. Cancel and confirm useproviderOrderIddirectly.Orders list:
OrdersListmerges legacyFiatOrder[]from Redux with V2RampsOrder[]from controller state viaDisplayOrderprojection. Legacy orders route to the aggregator detail screen; V2 orders route to the new controller-native detail screen.Side effects:
ramps-controller-init.tssubscribes toRampsController:orderStatusChangedfor notification and analytics handlers, and starts controller order polling on init. Follows the same pattern asTransactionController:transactionConfirmedhandlers.Messenger:
RampsControllerInitMessengerdelegates theorderStatusChangedevent so init-level subscriptions work.What stays untouched: Legacy
AGGREGATOR,DEPOSIT, and older orders remain in Redux. Their processors, detail screens, types, and polling are unchanged.Changelog
CHANGELOG entry: null
Related issues
Core PR that added orders MetaMask/core#8045
Fixes:
Manual testing steps
Screenshots/Recordings
Before
After
Loom video of the controller state being polled, created and updated for Aggregator order (Moonpay)
https://www.loom.com/share/70658e66e55c444cadc02aad407c3da2
Loom video of the controller state being polled, created and updated for Native order (Transak)
https://www.loom.com/share/2c6d5941cbfa48c4be7cc36000e40e89
Pre-merge author checklist
Pre-merge reviewer checklist
Note
Medium Risk
Updates unified ramp checkout/order list navigation and switches V2 order creation/storage to controller state, which could impact order visibility and detail routing for buy/deposit flows. Risk is mitigated by updated unit tests, but regressions could affect transaction history UX.
Overview
V2 ramp orders are now stored and read from
RampsControllerinstead of being converted into legacyFiatOrderRedux state.CheckoutusesuseRampsOrders(getOrderFromCallback,addOrder) and alwaysresets intoRoutes.RAMP.RAMPS_ORDER_DETAILSwithshowCloseButton, even when order IDs arenull.Orders list UI and navigation were reworked to merge legacy Redux orders with controller V2 orders.
OrdersListnow projects both into a unifiedDisplayOrderlist (mergeDisplayOrders), renders new list rows, and routes presses by source/provider (legacy aggregator ->OrderDetails, V2 ->RampsOrderDetails, deposit -> newDepositOrderDetails, created deposit -> deposit flow).Adds
Routes.DEPOSIT.ORDER_DETAILSto the main navigator and updates provider selection to consider completed controller orders when computing previously-used providers. Tests/snapshots were updated accordingly, and V2 toast wiring now passesstatus(typed asRampsOrderStatus) instead of legacystate.Written by Cursor Bugbot for commit f7c7f73. This will update automatically on new commits. Configure here.