Commit 5d16844
authored
fix(ramps): Single-owner React Query fetching for providers, tokens, and payment methods (#28224)
## **Description**
Payment methods, providers, and tokens were being fetched redundantly
from two independent paths (controller `fireAndForget` + React Query),
causing 2–3 duplicate API calls per user action with a ~10s spinner.
This PR makes the mobile client the single fetch owner for all ramp
data. React Query handles providers and payment methods with proper
caching and invalidation. Tokens are fetched directly from
RampsBootstrap. Screens that don't need payment methods no longer
trigger fetches.
Changes:
1. **`RampsBootstrap.tsx`** — Now fetches all three data sources at app
root: `useRampsProviders` (React Query), `useRampsPaymentMethods` (React
Query), and `getTokens` (direct controller call on region change). This
follows the agreed flow: app loads → fetch providers and tokens →
provider selected → fetch payment methods.
2. **`useRampsProviders.ts`** — Reads provider list from React Query
cache (not controller state). Invalidates all ramp queries on region
change to force fresh fetches. Passes full `Provider` object to
`setSelectedProvider` for auto-selection (avoids crash when controller
state is empty).
3. **`useRampsPaymentMethods.ts`** — Query key reduced to `[regionCode,
providerId]` only. Token/fiat are passed to the API call but not the
key, so token changes don't trigger refetches. Passes full
`PaymentMethod` object to controller (avoids crash when controller state
is empty).
4. **`paymentMethods.ts` (query config)** — `staleTime: 5min`. Query key
only includes `regionCode` and `providerId`.
5. **`providers.ts` (query config)** — `staleTime: 15min`. Removed
`forceRefresh: true` from queryFn (controller's own cache handles it).
6. **`SettingsModal.tsx`** — Uses `useRampsProviders` instead of
`useRampsController` (no more payment methods fetch on settings screen).
7. **`TokenNotAvailableModal.tsx`** — Uses `useRampsProviders` +
`useRampsTokens` instead of `useRampsController`.
8. **`RegionSelector.tsx`** — Uses `useRampsUserRegion` +
`useRampsCountries` instead of `useRampsController`.
9. **`PaymentSelectionModal.tsx`** — Filters out payment methods with no
available quote once quotes load, preventing dead-end options (e.g.
Apple Pay shown but no provider returns a quote for it).
10. **Tests updated** — Removed `tokenSupportedByProvider` test, updated
query key and staleTime assertions.
## **Changelog**
CHANGELOG entry: Fixed duplicate payment method, provider, and token API
calls during buy flow; React Query is now the single fetch owner for
ramp data
## **Related issues**
Fixes:
[TRAM-3398](https://consensyssoftware.atlassian.net/browse/TRAM-3398)
Depends on core PR:
[MetaMask/core#8354](MetaMask/core#8354)
(removes `fireAndForget` calls from controller)
## **Manual testing steps**
```gherkin
Feature: Single-owner fetching for ramp data
Scenario: App load fetches providers, tokens, and payment methods
Given user opens the app (password screen)
When the app loads
Then getProviders, getTokens, and getPaymentMethods each fire once
And providers, tokens, and payment methods are populated in state
Scenario: Token selection does not trigger payment methods fetch
Given user is on the token selection screen
When user selects a token (e.g. Ethereum)
Then getPaymentMethods is NOT called
And the BuildQuote screen loads with cached payment methods
Scenario: Provider change triggers a single payment methods fetch
Given user is on the BuildQuote screen with a provider selected
When user changes the provider (e.g. Transak -> Coinbase)
Then getPaymentMethods fires exactly once for the new provider
And the payment method pill auto-switches to Debit or Credit
Scenario: Region switch refreshes all data
Given user is on the settings screen
When user changes region (e.g. France -> Finland -> France)
Then getProviders and getPaymentMethods fire for each new region
And switching back to a previous region also triggers fresh fetches
Scenario: Settings screen does not trigger payment methods fetch
Given user navigates to the Buy & Sell settings screen
Then getPaymentMethods is NOT called
And no unnecessary API calls appear in the debug dashboard
Scenario: Payment selection modal hides dead-end options
Given user taps the payment method pill
When quotes load for all payment methods
Then payment methods with no available quote are filtered out
```
## **Screenshots/Recordings**
### **Before**
<!-- 2-3 getPaymentMethods calls per token selection visible in debug
dashboard -->
### **After**
<!-- Single getPaymentMethods call only on provider change; no calls on
settings/navigation -->
## **Pre-merge author checklist**
- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile
Coding
Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I've included tests if applicable
- [x] I've documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I've applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
## **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.
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Changes ramp buy-flow data fetching/caching and controller interaction
patterns (query keys, invalidation, and provider/payment-method
selection), which could affect availability/performance across regions
and providers. UI impact is limited but touches core buy-flow bootstrap
and selection logic.
>
> **Overview**
> **Makes the mobile client the single fetch owner for ramps buy data.**
`RampsBootstrap` now preloads providers (with side effects), payment
methods, and triggers token fetching on region availability so
downstream screens don’t cause redundant requests.
>
> **Reworks React Query behavior for providers and payment methods.**
`useRampsProviders` reads the provider list from the React Query cache,
adds optional `enableSideEffects` to avoid duplicate
invalidation/auto-selection, and invalidates all `ramps` queries on
region changes. `useRampsPaymentMethods` simplifies the query to be
provider-scoped (query key is `regionCode` + `providerId`, 5-min
`staleTime`) and updates controller setters to accept full objects/null.
>
> **UI behavior tweaks and hook decoupling.** `PaymentSelectionModal`
now hides payment methods that have no non-custom-action quote once
quotes load, showing the “no payment methods available” state instead.
Several screens (`SettingsModal`, `TokenNotAvailableModal`,
`RegionSelector`) switch from `useRampsController` to narrower hooks
(`useRampsProviders`, `useRampsTokens`, `useRampsUserRegion`,
`useRampsCountries`) to prevent unrelated fetches. Dependency bump
updates `@metamask/ramps-controller` to `^13.0.0`.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
75f85ca. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->1 parent 0711cca commit 5d16844
20 files changed
Lines changed: 343 additions & 254 deletions
File tree
- app/components/UI/Ramp
- Views
- Modals
- PaymentSelectionModal
- SettingsModal
- TokenNotAvailableModal
- Settings/RegionSelector
- hooks
- queries
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
3 | 6 | | |
4 | 7 | | |
5 | 8 | | |
6 | 9 | | |
| 10 | + | |
7 | 11 | | |
8 | 12 | | |
9 | 13 | | |
| |||
15 | 19 | | |
16 | 20 | | |
17 | 21 | | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
18 | 66 | | |
19 | 67 | | |
20 | 68 | | |
21 | 69 | | |
22 | 70 | | |
23 | 71 | | |
24 | | - | |
25 | | - | |
| 72 | + | |
26 | 73 | | |
27 | 74 | | |
28 | 75 | | |
29 | 76 | | |
30 | | - | |
31 | | - | |
| 77 | + | |
32 | 78 | | |
33 | 79 | | |
34 | 80 | | |
35 | | - | |
36 | | - | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
37 | 85 | | |
| 86 | + | |
| 87 | + | |
38 | 88 | | |
39 | 89 | | |
40 | 90 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
1 | 3 | | |
2 | 4 | | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
3 | 8 | | |
4 | 9 | | |
5 | 10 | | |
| |||
11 | 16 | | |
12 | 17 | | |
13 | 18 | | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
14 | 22 | | |
15 | 23 | | |
16 | 24 | | |
17 | | - | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
18 | 38 | | |
19 | 39 | | |
20 | 40 | | |
| |||
Lines changed: 7 additions & 3 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
362 | 362 | | |
363 | 363 | | |
364 | 364 | | |
365 | | - | |
| 365 | + | |
366 | 366 | | |
367 | 367 | | |
368 | 368 | | |
| |||
381 | 381 | | |
382 | 382 | | |
383 | 383 | | |
384 | | - | |
385 | | - | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
386 | 390 | | |
387 | 391 | | |
Lines changed: 13 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
219 | 219 | | |
220 | 220 | | |
221 | 221 | | |
222 | | - | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
223 | 234 | | |
224 | 235 | | |
225 | 236 | | |
| |||
235 | 246 | | |
236 | 247 | | |
237 | 248 | | |
238 | | - | |
| 249 | + | |
239 | 250 | | |
240 | 251 | | |
241 | 252 | | |
| |||
Lines changed: 2 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
121 | 121 | | |
122 | 122 | | |
123 | 123 | | |
124 | | - | |
125 | | - | |
| 124 | + | |
| 125 | + | |
126 | 126 | | |
127 | 127 | | |
128 | 128 | | |
| |||
Lines changed: 2 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
28 | | - | |
| 28 | + | |
29 | 29 | | |
30 | 30 | | |
31 | 31 | | |
| |||
53 | 53 | | |
54 | 54 | | |
55 | 55 | | |
56 | | - | |
| 56 | + | |
57 | 57 | | |
58 | 58 | | |
59 | 59 | | |
| |||
Lines changed: 7 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
58 | 58 | | |
59 | 59 | | |
60 | 60 | | |
61 | | - | |
62 | | - | |
| 61 | + | |
| 62 | + | |
63 | 63 | | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
64 | 69 | | |
65 | 70 | | |
66 | 71 | | |
| |||
Lines changed: 4 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
24 | | - | |
| 24 | + | |
| 25 | + | |
25 | 26 | | |
26 | 27 | | |
27 | 28 | | |
| |||
48 | 49 | | |
49 | 50 | | |
50 | 51 | | |
51 | | - | |
| 52 | + | |
| 53 | + | |
52 | 54 | | |
53 | 55 | | |
54 | 56 | | |
| |||
0 commit comments