Skip to content

Commit acacfde

Browse files
authored
fix(ramps): Remove dual-path fetching from RampsController, let client own data lifecycle (#8354)
## Explanation Payment methods, providers, and tokens were being fetched redundantly from two independent paths: the controller's `fireAndForget` calls (inside `setSelectedToken`, `setSelectedProvider`, and `setUserRegion`) and React Query on the mobile side. This caused 2–3 duplicate API calls per user action, with a ~10s spinner on the `/payments` endpoint due to cold DB connection pooling. This PR removes all `fireAndForget` data-fetching side effects from the controller. The mobile client (React Query + RampsBootstrap) is now the single owner of when providers, tokens, and payment methods are fetched. The controller only manages state updates and selections. Changes: 1. **`#runInit` (geolocation fix)** — `forceRefresh` no longer overrides a persisted `userRegion` with the geolocation endpoint. Geolocation is only used to seed the initial region when `userRegion` is null. 2. **`setSelectedToken`** — Removed `fireAndForget(getPaymentMethods(...))` and `resetResource('paymentMethods')`. Token change no longer triggers payment methods fetch or clears payment methods state. Payment methods are provider-scoped, not token-scoped. 3. **`setSelectedProvider`** — Removed `fireAndForget(getPaymentMethods(...))`, `resetResource('paymentMethods')`, and `tokenSupportedByProvider` gate. Now accepts a full `Provider` object (not just ID) to avoid dependency on `state.providers.data` being populated. Silently ignores when provider ID is not found instead of throwing. 4. **`setUserRegion`** — Removed `fireAndForget(getTokens(...))` and `fireAndForget(getProviders(...))`. The mobile client handles all data fetching via React Query (providers, payment methods) and direct controller calls (tokens) from RampsBootstrap. 5. **`setSelectedPaymentMethod`** — Now accepts a full `PaymentMethod` object (not just ID) to avoid dependency on `state.paymentMethods.data` being populated. Silently sets `null` when payment method ID is not found instead of throwing. 6. **`getPaymentMethods` response handler** — Always selects the first (highest-scored) payment method when new data arrives, preventing dead-end states where a payment method with no quotes stays selected after provider switch. 7. **`#fireAndForget`** — Removed (no remaining callers). 8. **`executeRequest` generation counter** — Added `#pendingResourceGeneration` map to prevent stale in-flight requests from corrupting `isLoading` state. When `setUserRegion` resets dependent resource counts, the generation is bumped. Orphaned finally blocks from the previous generation skip their decrement instead of prematurely clearing `isLoading`. ## Link to metamask-mobile Depends on mobile PR: [MetaMask/metamask-mobile#28224](MetaMask/metamask-mobile#28224) ## References [TRAM-3398](https://consensyssoftware.atlassian.net/browse/TRAM-3398) ## Changelog CHANGELOG entry: See `packages/ramps-controller/CHANGELOG.md` Unreleased section. ## 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** > Behavior changes in `RampsController` selection/region flows (no longer auto-fetching or clearing dependent data) could affect consumers that relied on controller-side side effects. Adds new stale-request invalidation for `executeRequest`, which impacts loading/error state handling across resources. > > **Overview** > **Shifts ramps data lifecycle ownership to the client** by removing controller-side `fireAndForget` fetching from `setUserRegion`, `setSelectedToken`, and `setSelectedProvider`, and by no longer clearing `paymentMethods` on token/provider changes. > > `setSelectedProvider` and `setSelectedPaymentMethod` now accept either an ID or a full object and **stop throwing when backing resource data isn’t loaded/not found** (silently leaving selection `null`). `init` is fixed to **never override a persisted `userRegion` with geolocation**, even on `forceRefresh`. > > Request tracking is hardened by adding a per-resource generation counter so stale in-flight requests can’t incorrectly decrement ref-counted `isLoading` after dependent-resource resets; tests are updated/added to cover these races and the new selection semantics. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 7073fe5. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 04bc584 commit acacfde

4 files changed

Lines changed: 418 additions & 257 deletions

File tree

packages/ramps-controller/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
- Bump `@metamask/controller-utils` from `^11.19.0` to `^11.20.0` ([#8344](https://github.com/MetaMask/core/pull/8344))
1313
- Bump `@metamask/messenger` from `^1.0.0` to `^1.1.1` ([#8364](https://github.com/MetaMask/core/pull/8364), [#8373](https://github.com/MetaMask/core/pull/8373))
14+
- Removed controller-side data fetching (`fireAndForget`) from `setSelectedToken`, `setSelectedProvider`, and `setUserRegion`; all ramp data fetching is now driven by the client ([#8354](https://github.com/MetaMask/core/pull/8354))
15+
- `setSelectedProvider` and `setSelectedPaymentMethod` accept a full object in addition to an ID string; no longer throw when data is not loaded ([#8354](https://github.com/MetaMask/core/pull/8354))
16+
17+
### Fixed
18+
19+
- `init` no longer overrides a persisted `userRegion` with the geolocation endpoint response ([#8354](https://github.com/MetaMask/core/pull/8354))
1420

1521
## [12.1.0]
1622

packages/ramps-controller/src/RampsController-method-action-types.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -70,16 +70,17 @@ export type RampsControllerSetUserRegionAction = {
7070
};
7171

7272
/**
73-
* Sets the user's selected provider by ID, or clears the selection.
74-
* Looks up the provider from the current providers in state and automatically
75-
* fetches payment methods for that provider.
73+
* Sets the user's selected provider.
7674
*
77-
* @param providerId - The provider ID (e.g., "/providers/moonpay"), or null to clear.
75+
* Accepts either a Provider object (stored directly) or a provider ID
76+
* string (looked up from state). The object form is preferred when the
77+
* caller already has the full data (e.g. from React Query cache).
78+
*
79+
* @param providerOrId - A Provider object, a provider ID string (e.g., "/providers/moonpay"), or null to clear.
7880
* @param options - Optional settings for the selection.
7981
* @param options.autoSelected - When true, marks the provider as system-guessed
8082
* (soft selection). The UI will silently auto-switch on token conflict instead
8183
* of showing the "Token Not Available" modal. Defaults to false.
82-
* @throws If region is not set, providers are not loaded, or provider is not found.
8384
*/
8485
export type RampsControllerSetSelectedProviderAction = {
8586
type: `RampsController:setSelectedProvider`;
@@ -177,11 +178,14 @@ export type RampsControllerGetPaymentMethodsAction = {
177178
};
178179

179180
/**
180-
* Sets the user's selected payment method by ID.
181-
* Looks up the payment method from the current payment methods in state.
181+
* Sets the user's selected payment method.
182+
*
183+
* Accepts either a payment method ID (looked up from state) or a full
184+
* PaymentMethod object (stored directly). The object form is preferred
185+
* when the caller already has the full data (e.g. from React Query cache),
186+
* as it avoids depending on controller state being populated.
182187
*
183-
* @param paymentMethodId - The payment method ID (e.g., "/payments/debit-credit-card"), or null to clear.
184-
* @throws If payment methods are not loaded or payment method is not found.
188+
* @param paymentMethodOrId - A PaymentMethod object, a payment method ID string, or undefined/null to clear.
185189
*/
186190
export type RampsControllerSetSelectedPaymentMethodAction = {
187191
type: `RampsController:setSelectedPaymentMethod`;

0 commit comments

Comments
 (0)