@metamask/ramps-controller 4.1.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [5.0.0]
11
+
12
+ ### Added
13
+
14
+ - Add `hydrateState()` method to fetch providers and tokens for user region ([#7707](https://github.com/MetaMask/core/pull/7707))
15
+ - Add `countries` state to RampsController with 24 hour TTL caching ([#7707](https://github.com/MetaMask/core/pull/7707))
16
+ - Add `SupportedActions` type for `{ buy: boolean; sell: boolean }` support info
17
+ - Add `selectedToken` state and `setSelectedToken()` method to RampsController ([#7734](https://github.com/MetaMask/core/pull/7734))
18
+ - Add `RampsEnvironment.Local` option to RampsService for local development ([#7734](https://github.com/MetaMask/core/pull/7734))
19
+
20
+ ### Changed
21
+
22
+ - Reorganize `init()` to only fetch geolocation and countries; remove token and provider fetching ([#7707](https://github.com/MetaMask/core/pull/7707))
23
+ - **BREAKING:** Change `Country.supported` and `State.supported` from `boolean` to `SupportedActions` object. The API now returns buy/sell support info in a single call.
24
+ - **BREAKING:** Remove `action` parameter from `getCountries()`. Countries are no longer fetched separately for buy/sell actions.
25
+ - **BREAKING:** Rename `preferredProvider` to `selectedProvider` and `setPreferredProvider()` to `setSelectedProvider()` in RampsController ([#7734](https://github.com/MetaMask/core/pull/7734))
26
+ - **BREAKING:** Change `getPaymentMethods(options)` to `getPaymentMethods(region, options)` with region as first parameter ([#7734](https://github.com/MetaMask/core/pull/7734))
27
+
10
28
  ## [4.1.0]
11
29
 
12
30
  ### Added
@@ -94,7 +112,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
94
112
  - Add `OnRampService` for interacting with the OnRamp API
95
113
  - Add geolocation detection via IP address lookup
96
114
 
97
- [Unreleased]: https://github.com/MetaMask/core/compare/@metamask/ramps-controller@4.1.0...HEAD
115
+ [Unreleased]: https://github.com/MetaMask/core/compare/@metamask/ramps-controller@5.0.0...HEAD
116
+ [5.0.0]: https://github.com/MetaMask/core/compare/@metamask/ramps-controller@4.1.0...@metamask/ramps-controller@5.0.0
98
117
  [4.1.0]: https://github.com/MetaMask/core/compare/@metamask/ramps-controller@4.0.0...@metamask/ramps-controller@4.1.0
99
118
  [4.0.0]: https://github.com/MetaMask/core/compare/@metamask/ramps-controller@3.0.0...@metamask/ramps-controller@4.0.0
100
119
  [3.0.0]: https://github.com/MetaMask/core/compare/@metamask/ramps-controller@2.1.0...@metamask/ramps-controller@3.0.0
@@ -10,7 +10,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
10
10
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
11
11
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
12
  };
13
- var _RampsController_instances, _RampsController_requestCacheTTL, _RampsController_requestCacheMaxSize, _RampsController_pendingRequests, _RampsController_removeRequestState, _RampsController_updateRequestState;
13
+ var _RampsController_instances, _RampsController_requestCacheTTL, _RampsController_requestCacheMaxSize, _RampsController_pendingRequests, _RampsController_removeRequestState, _RampsController_cleanupState, _RampsController_updateRequestState;
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.RampsController = exports.getDefaultRampsControllerState = exports.controllerName = void 0;
16
16
  const base_controller_1 = require("@metamask/base-controller");
@@ -32,7 +32,13 @@ const rampsControllerMetadata = {
32
32
  includeInStateLogs: true,
33
33
  usedInUi: true,
34
34
  },
35
- preferredProvider: {
35
+ selectedProvider: {
36
+ persist: false,
37
+ includeInDebugSnapshot: true,
38
+ includeInStateLogs: true,
39
+ usedInUi: true,
40
+ },
41
+ countries: {
36
42
  persist: true,
37
43
  includeInDebugSnapshot: true,
38
44
  includeInStateLogs: true,
@@ -50,6 +56,12 @@ const rampsControllerMetadata = {
50
56
  includeInStateLogs: true,
51
57
  usedInUi: true,
52
58
  },
59
+ selectedToken: {
60
+ persist: false,
61
+ includeInDebugSnapshot: true,
62
+ includeInStateLogs: true,
63
+ usedInUi: true,
64
+ },
53
65
  paymentMethods: {
54
66
  persist: false,
55
67
  includeInDebugSnapshot: true,
@@ -80,9 +92,11 @@ const rampsControllerMetadata = {
80
92
  function getDefaultRampsControllerState() {
81
93
  return {
82
94
  userRegion: null,
83
- preferredProvider: null,
95
+ selectedProvider: null,
96
+ countries: [],
84
97
  providers: [],
85
98
  tokens: null,
99
+ selectedToken: null,
86
100
  paymentMethods: [],
87
101
  selectedPaymentMethod: null,
88
102
  requests: {},
@@ -205,7 +219,6 @@ class RampsController extends base_controller_1.BaseController {
205
219
  if (pending) {
206
220
  return pending.promise;
207
221
  }
208
- // Check cache validity (unless force refresh)
209
222
  if (!options?.forceRefresh) {
210
223
  const cached = this.state.requests[cacheKey];
211
224
  if (cached && !(0, RequestCache_1.isCacheExpired)(cached, ttl)) {
@@ -274,96 +287,6 @@ class RampsController extends base_controller_1.BaseController {
274
287
  getRequestState(cacheKey) {
275
288
  return this.state.requests[cacheKey];
276
289
  }
277
- /**
278
- * Updates the user's region by fetching geolocation.
279
- * This method calls the RampsService to get the geolocation.
280
- *
281
- * @param options - Options for cache behavior.
282
- * @returns The user region object.
283
- */
284
- async updateUserRegion(options) {
285
- // If a userRegion already exists and forceRefresh is not requested,
286
- // return it immediately without fetching geolocation.
287
- // This ensures that once a region is set (either via geolocation or manual selection),
288
- // it will not be overwritten by subsequent geolocation fetches.
289
- if (this.state.userRegion && !options?.forceRefresh) {
290
- return this.state.userRegion;
291
- }
292
- // When forceRefresh is true, clear the existing region and region-dependent state before fetching
293
- if (options?.forceRefresh) {
294
- this.update((state) => {
295
- state.userRegion = null;
296
- state.tokens = null;
297
- state.providers = [];
298
- state.paymentMethods = [];
299
- state.selectedPaymentMethod = null;
300
- });
301
- }
302
- const cacheKey = (0, RequestCache_1.createCacheKey)('updateUserRegion', []);
303
- const regionCode = await this.executeRequest(cacheKey, async () => {
304
- const result = await this.messenger.call('RampsService:getGeolocation');
305
- return result;
306
- }, options);
307
- if (!regionCode) {
308
- this.update((state) => {
309
- state.userRegion = null;
310
- state.tokens = null;
311
- state.providers = [];
312
- state.paymentMethods = [];
313
- state.selectedPaymentMethod = null;
314
- });
315
- return null;
316
- }
317
- const normalizedRegion = regionCode.toLowerCase().trim();
318
- try {
319
- const countries = await this.getCountries('buy', options);
320
- const userRegion = findRegionFromCode(normalizedRegion, countries);
321
- if (userRegion) {
322
- this.update((state) => {
323
- const regionChanged = state.userRegion?.regionCode !== userRegion.regionCode;
324
- state.userRegion = userRegion;
325
- // Clear region-dependent state when region changes
326
- if (regionChanged) {
327
- state.tokens = null;
328
- state.providers = [];
329
- state.paymentMethods = [];
330
- state.selectedPaymentMethod = null;
331
- }
332
- });
333
- // Fetch providers for the new region
334
- if (userRegion.regionCode) {
335
- try {
336
- await this.getProviders(userRegion.regionCode, options);
337
- }
338
- catch {
339
- // Provider fetch failed - error state will be available via selectors
340
- }
341
- }
342
- return userRegion;
343
- }
344
- // Region not found in countries data
345
- this.update((state) => {
346
- state.userRegion = null;
347
- state.tokens = null;
348
- state.providers = [];
349
- state.paymentMethods = [];
350
- state.selectedPaymentMethod = null;
351
- });
352
- return null;
353
- }
354
- catch {
355
- // If countries fetch fails, we can't create a valid UserRegion
356
- // Return null to indicate we don't have valid country data
357
- this.update((state) => {
358
- state.userRegion = null;
359
- state.tokens = null;
360
- state.providers = [];
361
- state.paymentMethods = [];
362
- state.selectedPaymentMethod = null;
363
- });
364
- return null;
365
- }
366
- }
367
290
  /**
368
291
  * Sets the user's region manually (without fetching geolocation).
369
292
  * This allows users to override the detected region.
@@ -375,106 +298,129 @@ class RampsController extends base_controller_1.BaseController {
375
298
  async setUserRegion(region, options) {
376
299
  const normalizedRegion = region.toLowerCase().trim();
377
300
  try {
378
- const countries = await this.getCountries('buy', options);
301
+ const { countries } = this.state;
302
+ if (!countries || countries.length === 0) {
303
+ __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_cleanupState).call(this);
304
+ throw new Error('No countries found. Cannot set user region without valid country information.');
305
+ }
379
306
  const userRegion = findRegionFromCode(normalizedRegion, countries);
380
- if (userRegion) {
381
- this.update((state) => {
382
- state.userRegion = userRegion;
307
+ if (!userRegion) {
308
+ __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_cleanupState).call(this);
309
+ throw new Error(`Region "${normalizedRegion}" not found in countries data. Cannot set user region without valid country information.`);
310
+ }
311
+ // Only cleanup state if region is actually changing
312
+ const regionChanged = normalizedRegion !== this.state.userRegion?.regionCode;
313
+ // Set the new region atomically with cleanup to avoid intermediate null state
314
+ this.update((state) => {
315
+ if (regionChanged) {
316
+ state.selectedProvider = null;
317
+ state.selectedToken = null;
383
318
  state.tokens = null;
384
319
  state.providers = [];
385
320
  state.paymentMethods = [];
386
321
  state.selectedPaymentMethod = null;
387
- });
388
- // Fetch providers for the new region
389
- try {
390
- await this.getProviders(userRegion.regionCode, options);
391
322
  }
392
- catch {
393
- // Provider fetch failed - error state will be available via selectors
394
- }
395
- return userRegion;
396
- }
397
- // Region not found in countries data
398
- this.update((state) => {
399
- state.userRegion = null;
400
- state.tokens = null;
401
- state.providers = [];
402
- state.paymentMethods = [];
403
- state.selectedPaymentMethod = null;
323
+ state.userRegion = userRegion;
404
324
  });
405
- throw new Error(`Region "${normalizedRegion}" not found in countries data. Cannot set user region without valid country information.`);
325
+ // Only trigger fetches if region changed or if data is missing
326
+ if (regionChanged || !this.state.tokens) {
327
+ this.triggerGetTokens(userRegion.regionCode, 'buy', options);
328
+ }
329
+ if (regionChanged || this.state.providers.length === 0) {
330
+ this.triggerGetProviders(userRegion.regionCode, options);
331
+ }
332
+ return userRegion;
406
333
  }
407
334
  catch (error) {
408
- // If the error is "not found", re-throw it
409
- // Otherwise, it's from countries fetch failure
410
- if (error instanceof Error && error.message.includes('not found')) {
411
- throw error;
412
- }
413
- // Countries fetch failed
414
- this.update((state) => {
415
- state.userRegion = null;
416
- state.tokens = null;
417
- state.providers = [];
418
- state.paymentMethods = [];
419
- state.selectedPaymentMethod = null;
420
- });
421
- throw new Error('Failed to fetch countries data. Cannot set user region without valid country information.');
335
+ __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_cleanupState).call(this);
336
+ throw error;
422
337
  }
423
338
  }
424
339
  /**
425
- * Sets the user's preferred provider.
426
- * This allows users to set their preferred ramp provider.
340
+ * Sets the user's selected provider by ID, or clears the selection.
341
+ * Looks up the provider from the current providers in state and automatically
342
+ * fetches payment methods for that provider.
427
343
  *
428
- * @param provider - The provider object to set.
344
+ * @param providerId - The provider ID (e.g., "/providers/moonpay"), or null to clear.
345
+ * @throws If region is not set, providers are not loaded, or provider is not found.
429
346
  */
430
- setPreferredProvider(provider) {
347
+ setSelectedProvider(providerId) {
348
+ if (providerId === null) {
349
+ this.update((state) => {
350
+ state.selectedProvider = null;
351
+ state.paymentMethods = [];
352
+ state.selectedPaymentMethod = null;
353
+ });
354
+ return;
355
+ }
356
+ const regionCode = this.state.userRegion?.regionCode;
357
+ if (!regionCode) {
358
+ throw new Error('Region is required. Cannot set selected provider without valid region information.');
359
+ }
360
+ const { providers } = this.state;
361
+ if (!providers || providers.length === 0) {
362
+ throw new Error('Providers not loaded. Cannot set selected provider before providers are fetched.');
363
+ }
364
+ const provider = providers.find((prov) => prov.id === providerId);
365
+ if (!provider) {
366
+ throw new Error(`Provider with ID "${providerId}" not found in available providers.`);
367
+ }
431
368
  this.update((state) => {
432
- state.preferredProvider = provider;
369
+ state.selectedProvider = provider;
370
+ state.paymentMethods = [];
371
+ state.selectedPaymentMethod = null;
372
+ });
373
+ // fetch payment methods for the new provider
374
+ // this is needed because you can change providers without changing the token
375
+ // (getPaymentMethods will use state as its default)
376
+ this.triggerGetPaymentMethods(regionCode, {
377
+ provider: provider.id,
433
378
  });
434
379
  }
435
380
  /**
436
381
  * Initializes the controller by fetching the user's region from geolocation.
437
382
  * This should be called once at app startup to set up the initial region.
438
- * After the region is set, tokens are fetched and saved to state.
439
383
  *
440
384
  * If a userRegion already exists (from persistence or manual selection),
441
- * this method will skip geolocation fetch and only fetch tokens if needed.
385
+ * this method will skip geolocation fetch and use the existing region.
442
386
  *
443
387
  * @param options - Options for cache behavior.
444
388
  * @returns Promise that resolves when initialization is complete.
445
389
  */
446
390
  async init(options) {
447
- const userRegion = await this.updateUserRegion(options).catch(() => {
448
- // User region fetch failed - error state will be available via selectors
449
- return null;
450
- });
451
- if (userRegion) {
452
- try {
453
- await this.getTokens(userRegion.regionCode, 'buy', options);
454
- }
455
- catch {
456
- // Token fetch failed - error state will be available via selectors
457
- }
458
- try {
459
- await this.getProviders(userRegion.regionCode, options);
460
- }
461
- catch {
462
- // Provider fetch failed - error state will be available via selectors
463
- }
391
+ await this.getCountries(options);
392
+ let regionCode = this.state.userRegion?.regionCode;
393
+ regionCode ?? (regionCode = await this.messenger.call('RampsService:getGeolocation'));
394
+ if (!regionCode) {
395
+ throw new Error('Failed to fetch geolocation. Cannot initialize controller without valid region information.');
396
+ }
397
+ await this.setUserRegion(regionCode, options);
398
+ }
399
+ hydrateState(options) {
400
+ const regionCode = this.state.userRegion?.regionCode;
401
+ if (!regionCode) {
402
+ throw new Error('Region code is required. Cannot hydrate state without valid region information.');
464
403
  }
404
+ this.triggerGetTokens(regionCode, 'buy', options);
405
+ this.triggerGetProviders(regionCode, options);
465
406
  }
466
407
  /**
467
- * Fetches the list of supported countries for a given ramp action.
408
+ * Fetches the list of supported countries.
409
+ * The API returns countries with support information for both buy and sell actions.
410
+ * The countries are saved in the controller state once fetched.
468
411
  *
469
- * @param action - The ramp action type ('buy' or 'sell').
470
412
  * @param options - Options for cache behavior.
471
413
  * @returns An array of countries.
472
414
  */
473
- async getCountries(action = 'buy', options) {
474
- const cacheKey = (0, RequestCache_1.createCacheKey)('getCountries', [action]);
475
- return this.executeRequest(cacheKey, async () => {
476
- return this.messenger.call('RampsService:getCountries', action);
415
+ async getCountries(options) {
416
+ const cacheKey = (0, RequestCache_1.createCacheKey)('getCountries', []);
417
+ const countries = await this.executeRequest(cacheKey, async () => {
418
+ return this.messenger.call('RampsService:getCountries');
477
419
  }, options);
420
+ this.update((state) => {
421
+ state.countries = countries;
422
+ });
423
+ return countries;
478
424
  }
479
425
  /**
480
426
  * Fetches the list of available tokens for a given region and action.
@@ -510,6 +456,45 @@ class RampsController extends base_controller_1.BaseController {
510
456
  });
511
457
  return tokens;
512
458
  }
459
+ /**
460
+ * Sets the user's selected token by asset ID.
461
+ * Looks up the token from the current tokens in state and automatically
462
+ * fetches payment methods for that token.
463
+ *
464
+ * @param assetId - The asset identifier in CAIP-19 format (e.g., "eip155:1/erc20:0x..."), or undefined to clear.
465
+ * @throws If region is not set, tokens are not loaded, or token is not found.
466
+ */
467
+ setSelectedToken(assetId) {
468
+ if (!assetId) {
469
+ this.update((state) => {
470
+ state.selectedToken = null;
471
+ state.paymentMethods = [];
472
+ state.selectedPaymentMethod = null;
473
+ });
474
+ return;
475
+ }
476
+ const regionCode = this.state.userRegion?.regionCode;
477
+ if (!regionCode) {
478
+ throw new Error('Region is required. Cannot set selected token without valid region information.');
479
+ }
480
+ const { tokens } = this.state;
481
+ if (!tokens) {
482
+ throw new Error('Tokens not loaded. Cannot set selected token before tokens are fetched.');
483
+ }
484
+ const token = tokens.allTokens.find((tok) => tok.assetId === assetId) ??
485
+ tokens.topTokens.find((tok) => tok.assetId === assetId);
486
+ if (!token) {
487
+ throw new Error(`Token with asset ID "${assetId}" not found in available tokens.`);
488
+ }
489
+ this.update((state) => {
490
+ state.selectedToken = token;
491
+ state.paymentMethods = [];
492
+ state.selectedPaymentMethod = null;
493
+ });
494
+ this.triggerGetPaymentMethods(regionCode, {
495
+ assetId: token.assetId,
496
+ });
497
+ }
513
498
  /**
514
499
  * Fetches the list of providers for a given region.
515
500
  * The providers are saved in the controller state once fetched.
@@ -555,57 +540,81 @@ class RampsController extends base_controller_1.BaseController {
555
540
  * Fetches the list of payment methods for a given context.
556
541
  * The payment methods are saved in the controller state once fetched.
557
542
  *
543
+ * @param region - User's region code (e.g. "fr", "us-ny").
558
544
  * @param options - Query parameters for filtering payment methods.
559
- * @param options.region - User's region code. If not provided, uses the user's region from controller state.
560
545
  * @param options.fiat - Fiat currency code (e.g., "usd"). If not provided, uses the user's region currency.
561
546
  * @param options.assetId - CAIP-19 cryptocurrency identifier.
562
547
  * @param options.provider - Provider ID path.
563
- * @param options.forceRefresh - Whether to bypass cache.
564
- * @param options.ttl - Custom TTL for this request.
565
548
  * @returns The payment methods response containing payments array.
566
549
  */
567
- async getPaymentMethods(options) {
568
- const regionToUse = options.region ?? this.state.userRegion?.regionCode;
569
- const fiatToUse = options.fiat ?? this.state.userRegion?.country?.currency;
570
- if (!regionToUse) {
550
+ async getPaymentMethods(region, options) {
551
+ const regionCode = region ?? this.state.userRegion?.regionCode ?? null;
552
+ const fiatToUse = options?.fiat ?? this.state.userRegion?.country?.currency ?? null;
553
+ const assetIdToUse = options?.assetId ?? this.state.selectedToken?.assetId ?? '';
554
+ const providerToUse = options?.provider ?? this.state.selectedProvider?.id ?? '';
555
+ if (!regionCode) {
571
556
  throw new Error('Region is required. Either provide a region parameter or ensure userRegion is set in controller state.');
572
557
  }
573
558
  if (!fiatToUse) {
574
559
  throw new Error('Fiat currency is required. Either provide a fiat parameter or ensure userRegion is set in controller state.');
575
560
  }
576
- const normalizedRegion = regionToUse.toLowerCase().trim();
561
+ const normalizedRegion = regionCode.toLowerCase().trim();
577
562
  const normalizedFiat = fiatToUse.toLowerCase().trim();
578
563
  const cacheKey = (0, RequestCache_1.createCacheKey)('getPaymentMethods', [
579
564
  normalizedRegion,
580
565
  normalizedFiat,
581
- options.assetId,
582
- options.provider,
566
+ assetIdToUse,
567
+ providerToUse,
583
568
  ]);
584
569
  const response = await this.executeRequest(cacheKey, async () => {
585
570
  return this.messenger.call('RampsService:getPaymentMethods', {
586
571
  region: normalizedRegion,
587
572
  fiat: normalizedFiat,
588
- assetId: options.assetId,
589
- provider: options.provider,
573
+ assetId: assetIdToUse,
574
+ provider: providerToUse,
590
575
  });
591
- }, { forceRefresh: options.forceRefresh, ttl: options.ttl });
576
+ }, options);
592
577
  this.update((state) => {
593
- state.paymentMethods = response.payments;
594
- // Only clear selected payment method if it's no longer in the new list
595
- // This preserves the selection when cached data is returned (same context)
596
- if (state.selectedPaymentMethod &&
597
- !response.payments.some((pm) => pm.id === state.selectedPaymentMethod?.id)) {
598
- state.selectedPaymentMethod = null;
578
+ const currentAssetId = state.selectedToken?.assetId ?? '';
579
+ const currentProviderId = state.selectedProvider?.id ?? '';
580
+ const tokenSelectionUnchanged = assetIdToUse === currentAssetId;
581
+ const providerSelectionUnchanged = providerToUse === currentProviderId;
582
+ // this is a race condition check to ensure that the selected token and provider in state are the same as the tokens we're requesting for
583
+ // ex: if the user rapidly changes the token or provider, the in-flight payment methods might not be valid
584
+ // so this check will ensure that the payment methods are still valid for the token and provider that were requested
585
+ if (tokenSelectionUnchanged && providerSelectionUnchanged) {
586
+ state.paymentMethods = response.payments;
587
+ // this will auto-select the first payment method if the selected payment method is not in the new payment methods
588
+ const currentSelectionStillValid = response.payments.some((pm) => pm.id === state.selectedPaymentMethod?.id);
589
+ if (!currentSelectionStillValid) {
590
+ state.selectedPaymentMethod = response.payments[0] ?? null;
591
+ }
599
592
  }
600
593
  });
601
594
  return response;
602
595
  }
603
596
  /**
604
- * Sets the user's selected payment method.
597
+ * Sets the user's selected payment method by ID.
598
+ * Looks up the payment method from the current payment methods in state.
605
599
  *
606
- * @param paymentMethod - The payment method to select, or null to clear.
600
+ * @param paymentMethodId - The payment method ID (e.g., "/payments/debit-credit-card"), or null to clear.
601
+ * @throws If payment methods are not loaded or payment method is not found.
607
602
  */
608
- setSelectedPaymentMethod(paymentMethod) {
603
+ setSelectedPaymentMethod(paymentMethodId) {
604
+ if (!paymentMethodId) {
605
+ this.update((state) => {
606
+ state.selectedPaymentMethod = null;
607
+ });
608
+ return;
609
+ }
610
+ const { paymentMethods } = this.state;
611
+ if (!paymentMethods || paymentMethods.length === 0) {
612
+ throw new Error('Payment methods not loaded. Cannot set selected payment method before payment methods are fetched.');
613
+ }
614
+ const paymentMethod = paymentMethods.find((pm) => pm.id === paymentMethodId);
615
+ if (!paymentMethod) {
616
+ throw new Error(`Payment method with ID "${paymentMethodId}" not found in available payment methods.`);
617
+ }
609
618
  this.update((state) => {
610
619
  state.selectedPaymentMethod = paymentMethod;
611
620
  });
@@ -615,16 +624,6 @@ class RampsController extends base_controller_1.BaseController {
615
624
  // These fire-and-forget methods are for use in React effects.
616
625
  // Errors are stored in state and available via selectors.
617
626
  // ============================================================
618
- /**
619
- * Triggers a user region update without throwing.
620
- *
621
- * @param options - Options for cache behavior.
622
- */
623
- triggerUpdateUserRegion(options) {
624
- this.updateUserRegion(options).catch(() => {
625
- // Error stored in state
626
- });
627
- }
628
627
  /**
629
628
  * Triggers setting the user region without throwing.
630
629
  *
@@ -639,11 +638,10 @@ class RampsController extends base_controller_1.BaseController {
639
638
  /**
640
639
  * Triggers fetching countries without throwing.
641
640
  *
642
- * @param action - The ramp action type ('buy' or 'sell').
643
641
  * @param options - Options for cache behavior.
644
642
  */
645
- triggerGetCountries(action = 'buy', options) {
646
- this.getCountries(action, options).catch(() => {
643
+ triggerGetCountries(options) {
644
+ this.getCountries(options).catch(() => {
647
645
  // Error stored in state
648
646
  });
649
647
  }
@@ -673,16 +671,14 @@ class RampsController extends base_controller_1.BaseController {
673
671
  /**
674
672
  * Triggers fetching payment methods without throwing.
675
673
  *
674
+ * @param region - User's region code (e.g., "us", "fr", "us-ny").
676
675
  * @param options - Query parameters for filtering payment methods.
677
- * @param options.region - User's region code. If not provided, uses userRegion from state.
678
676
  * @param options.fiat - Fiat currency code. If not provided, uses userRegion currency.
679
677
  * @param options.assetId - CAIP-19 cryptocurrency identifier.
680
678
  * @param options.provider - Provider ID path.
681
- * @param options.forceRefresh - Whether to bypass cache.
682
- * @param options.ttl - Custom TTL for this request.
683
679
  */
684
- triggerGetPaymentMethods(options) {
685
- this.getPaymentMethods(options).catch(() => {
680
+ triggerGetPaymentMethods(region, options) {
681
+ this.getPaymentMethods(region, options).catch(() => {
686
682
  // Error stored in state
687
683
  });
688
684
  }
@@ -693,6 +689,16 @@ _RampsController_requestCacheTTL = new WeakMap(), _RampsController_requestCacheM
693
689
  const requests = state.requests;
694
690
  delete requests[cacheKey];
695
691
  });
692
+ }, _RampsController_cleanupState = function _RampsController_cleanupState() {
693
+ this.update((state) => {
694
+ state.userRegion = null;
695
+ state.selectedProvider = null;
696
+ state.selectedToken = null;
697
+ state.tokens = null;
698
+ state.providers = [];
699
+ state.paymentMethods = [];
700
+ state.selectedPaymentMethod = null;
701
+ });
696
702
  }, _RampsController_updateRequestState = function _RampsController_updateRequestState(cacheKey, requestState) {
697
703
  const maxSize = __classPrivateFieldGet(this, _RampsController_requestCacheMaxSize, "f");
698
704
  const ttl = __classPrivateFieldGet(this, _RampsController_requestCacheTTL, "f");