@metamask/ramps-controller 4.0.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.
@@ -9,9 +9,9 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
9
9
  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");
10
10
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
11
  };
12
- var _RampsController_instances, _RampsController_requestCacheTTL, _RampsController_requestCacheMaxSize, _RampsController_pendingRequests, _RampsController_removeRequestState, _RampsController_updateRequestState;
12
+ var _RampsController_instances, _RampsController_requestCacheTTL, _RampsController_requestCacheMaxSize, _RampsController_pendingRequests, _RampsController_removeRequestState, _RampsController_cleanupState, _RampsController_updateRequestState;
13
13
  import { BaseController } from "@metamask/base-controller";
14
- import { DEFAULT_REQUEST_CACHE_TTL, DEFAULT_REQUEST_CACHE_MAX_SIZE, createCacheKey, isCacheExpired, createLoadingState, createSuccessState, createErrorState } from "./RequestCache.mjs";
14
+ import { DEFAULT_REQUEST_CACHE_TTL, DEFAULT_REQUEST_CACHE_MAX_SIZE, createCacheKey, isCacheExpired, createLoadingState, createSuccessState, createErrorState, RequestStatus } from "./RequestCache.mjs";
15
15
  // === GENERAL ===
16
16
  /**
17
17
  * The name of the {@link RampsController}, used to namespace the
@@ -29,7 +29,13 @@ const rampsControllerMetadata = {
29
29
  includeInStateLogs: true,
30
30
  usedInUi: true,
31
31
  },
32
- preferredProvider: {
32
+ selectedProvider: {
33
+ persist: false,
34
+ includeInDebugSnapshot: true,
35
+ includeInStateLogs: true,
36
+ usedInUi: true,
37
+ },
38
+ countries: {
33
39
  persist: true,
34
40
  includeInDebugSnapshot: true,
35
41
  includeInStateLogs: true,
@@ -47,6 +53,24 @@ const rampsControllerMetadata = {
47
53
  includeInStateLogs: true,
48
54
  usedInUi: true,
49
55
  },
56
+ selectedToken: {
57
+ persist: false,
58
+ includeInDebugSnapshot: true,
59
+ includeInStateLogs: true,
60
+ usedInUi: true,
61
+ },
62
+ paymentMethods: {
63
+ persist: false,
64
+ includeInDebugSnapshot: true,
65
+ includeInStateLogs: true,
66
+ usedInUi: true,
67
+ },
68
+ selectedPaymentMethod: {
69
+ persist: false,
70
+ includeInDebugSnapshot: true,
71
+ includeInStateLogs: true,
72
+ usedInUi: true,
73
+ },
50
74
  requests: {
51
75
  persist: false,
52
76
  includeInDebugSnapshot: true,
@@ -65,9 +89,13 @@ const rampsControllerMetadata = {
65
89
  export function getDefaultRampsControllerState() {
66
90
  return {
67
91
  userRegion: null,
68
- preferredProvider: null,
92
+ selectedProvider: null,
93
+ countries: [],
69
94
  providers: [],
70
95
  tokens: null,
96
+ selectedToken: null,
97
+ paymentMethods: [],
98
+ selectedPaymentMethod: null,
71
99
  requests: {},
72
100
  };
73
101
  }
@@ -187,7 +215,6 @@ export class RampsController extends BaseController {
187
215
  if (pending) {
188
216
  return pending.promise;
189
217
  }
190
- // Check cache validity (unless force refresh)
191
218
  if (!options?.forceRefresh) {
192
219
  const cached = this.state.requests[cacheKey];
193
220
  if (cached && !isCacheExpired(cached, ttl)) {
@@ -256,86 +283,6 @@ export class RampsController extends BaseController {
256
283
  getRequestState(cacheKey) {
257
284
  return this.state.requests[cacheKey];
258
285
  }
259
- /**
260
- * Updates the user's region by fetching geolocation.
261
- * This method calls the RampsService to get the geolocation.
262
- *
263
- * @param options - Options for cache behavior.
264
- * @returns The user region object.
265
- */
266
- async updateUserRegion(options) {
267
- // If a userRegion already exists and forceRefresh is not requested,
268
- // return it immediately without fetching geolocation.
269
- // This ensures that once a region is set (either via geolocation or manual selection),
270
- // it will not be overwritten by subsequent geolocation fetches.
271
- if (this.state.userRegion && !options?.forceRefresh) {
272
- return this.state.userRegion;
273
- }
274
- // When forceRefresh is true, clear the existing region, tokens, and providers before fetching
275
- if (options?.forceRefresh) {
276
- this.update((state) => {
277
- state.userRegion = null;
278
- state.tokens = null;
279
- state.providers = [];
280
- });
281
- }
282
- const cacheKey = createCacheKey('updateUserRegion', []);
283
- const regionCode = await this.executeRequest(cacheKey, async () => {
284
- const result = await this.messenger.call('RampsService:getGeolocation');
285
- return result;
286
- }, options);
287
- if (!regionCode) {
288
- this.update((state) => {
289
- state.userRegion = null;
290
- state.tokens = null;
291
- state.providers = [];
292
- });
293
- return null;
294
- }
295
- const normalizedRegion = regionCode.toLowerCase().trim();
296
- try {
297
- const countries = await this.getCountries('buy', options);
298
- const userRegion = findRegionFromCode(normalizedRegion, countries);
299
- if (userRegion) {
300
- this.update((state) => {
301
- const regionChanged = state.userRegion?.regionCode !== userRegion.regionCode;
302
- state.userRegion = userRegion;
303
- // Clear tokens and providers when region changes
304
- if (regionChanged) {
305
- state.tokens = null;
306
- state.providers = [];
307
- }
308
- });
309
- // Fetch providers for the new region
310
- if (userRegion.regionCode) {
311
- try {
312
- await this.getProviders(userRegion.regionCode, options);
313
- }
314
- catch {
315
- // Provider fetch failed - error state will be available via selectors
316
- }
317
- }
318
- return userRegion;
319
- }
320
- // Region not found in countries data
321
- this.update((state) => {
322
- state.userRegion = null;
323
- state.tokens = null;
324
- state.providers = [];
325
- });
326
- return null;
327
- }
328
- catch {
329
- // If countries fetch fails, we can't create a valid UserRegion
330
- // Return null to indicate we don't have valid country data
331
- this.update((state) => {
332
- state.userRegion = null;
333
- state.tokens = null;
334
- state.providers = [];
335
- });
336
- return null;
337
- }
338
- }
339
286
  /**
340
287
  * Sets the user's region manually (without fetching geolocation).
341
288
  * This allows users to override the detected region.
@@ -347,100 +294,129 @@ export class RampsController extends BaseController {
347
294
  async setUserRegion(region, options) {
348
295
  const normalizedRegion = region.toLowerCase().trim();
349
296
  try {
350
- const countries = await this.getCountries('buy', options);
297
+ const { countries } = this.state;
298
+ if (!countries || countries.length === 0) {
299
+ __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_cleanupState).call(this);
300
+ throw new Error('No countries found. Cannot set user region without valid country information.');
301
+ }
351
302
  const userRegion = findRegionFromCode(normalizedRegion, countries);
352
- if (userRegion) {
353
- this.update((state) => {
354
- state.userRegion = userRegion;
303
+ if (!userRegion) {
304
+ __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_cleanupState).call(this);
305
+ throw new Error(`Region "${normalizedRegion}" not found in countries data. Cannot set user region without valid country information.`);
306
+ }
307
+ // Only cleanup state if region is actually changing
308
+ const regionChanged = normalizedRegion !== this.state.userRegion?.regionCode;
309
+ // Set the new region atomically with cleanup to avoid intermediate null state
310
+ this.update((state) => {
311
+ if (regionChanged) {
312
+ state.selectedProvider = null;
313
+ state.selectedToken = null;
355
314
  state.tokens = null;
356
315
  state.providers = [];
357
- });
358
- // Fetch providers for the new region
359
- try {
360
- await this.getProviders(userRegion.regionCode, options);
361
- }
362
- catch {
363
- // Provider fetch failed - error state will be available via selectors
316
+ state.paymentMethods = [];
317
+ state.selectedPaymentMethod = null;
364
318
  }
365
- return userRegion;
366
- }
367
- // Region not found in countries data
368
- this.update((state) => {
369
- state.userRegion = null;
370
- state.tokens = null;
371
- state.providers = [];
319
+ state.userRegion = userRegion;
372
320
  });
373
- throw new Error(`Region "${normalizedRegion}" not found in countries data. Cannot set user region without valid country information.`);
321
+ // Only trigger fetches if region changed or if data is missing
322
+ if (regionChanged || !this.state.tokens) {
323
+ this.triggerGetTokens(userRegion.regionCode, 'buy', options);
324
+ }
325
+ if (regionChanged || this.state.providers.length === 0) {
326
+ this.triggerGetProviders(userRegion.regionCode, options);
327
+ }
328
+ return userRegion;
374
329
  }
375
330
  catch (error) {
376
- // If the error is "not found", re-throw it
377
- // Otherwise, it's from countries fetch failure
378
- if (error instanceof Error && error.message.includes('not found')) {
379
- throw error;
380
- }
381
- // Countries fetch failed
382
- this.update((state) => {
383
- state.userRegion = null;
384
- state.tokens = null;
385
- state.providers = [];
386
- });
387
- throw new Error('Failed to fetch countries data. Cannot set user region without valid country information.');
331
+ __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_cleanupState).call(this);
332
+ throw error;
388
333
  }
389
334
  }
390
335
  /**
391
- * Sets the user's preferred provider.
392
- * This allows users to set their preferred ramp provider.
336
+ * Sets the user's selected provider by ID, or clears the selection.
337
+ * Looks up the provider from the current providers in state and automatically
338
+ * fetches payment methods for that provider.
393
339
  *
394
- * @param provider - The provider object to set.
340
+ * @param providerId - The provider ID (e.g., "/providers/moonpay"), or null to clear.
341
+ * @throws If region is not set, providers are not loaded, or provider is not found.
395
342
  */
396
- setPreferredProvider(provider) {
343
+ setSelectedProvider(providerId) {
344
+ if (providerId === null) {
345
+ this.update((state) => {
346
+ state.selectedProvider = null;
347
+ state.paymentMethods = [];
348
+ state.selectedPaymentMethod = null;
349
+ });
350
+ return;
351
+ }
352
+ const regionCode = this.state.userRegion?.regionCode;
353
+ if (!regionCode) {
354
+ throw new Error('Region is required. Cannot set selected provider without valid region information.');
355
+ }
356
+ const { providers } = this.state;
357
+ if (!providers || providers.length === 0) {
358
+ throw new Error('Providers not loaded. Cannot set selected provider before providers are fetched.');
359
+ }
360
+ const provider = providers.find((prov) => prov.id === providerId);
361
+ if (!provider) {
362
+ throw new Error(`Provider with ID "${providerId}" not found in available providers.`);
363
+ }
397
364
  this.update((state) => {
398
- state.preferredProvider = provider;
365
+ state.selectedProvider = provider;
366
+ state.paymentMethods = [];
367
+ state.selectedPaymentMethod = null;
368
+ });
369
+ // fetch payment methods for the new provider
370
+ // this is needed because you can change providers without changing the token
371
+ // (getPaymentMethods will use state as its default)
372
+ this.triggerGetPaymentMethods(regionCode, {
373
+ provider: provider.id,
399
374
  });
400
375
  }
401
376
  /**
402
377
  * Initializes the controller by fetching the user's region from geolocation.
403
378
  * This should be called once at app startup to set up the initial region.
404
- * After the region is set, tokens are fetched and saved to state.
405
379
  *
406
380
  * If a userRegion already exists (from persistence or manual selection),
407
- * this method will skip geolocation fetch and only fetch tokens if needed.
381
+ * this method will skip geolocation fetch and use the existing region.
408
382
  *
409
383
  * @param options - Options for cache behavior.
410
384
  * @returns Promise that resolves when initialization is complete.
411
385
  */
412
386
  async init(options) {
413
- const userRegion = await this.updateUserRegion(options).catch(() => {
414
- // User region fetch failed - error state will be available via selectors
415
- return null;
416
- });
417
- if (userRegion) {
418
- try {
419
- await this.getTokens(userRegion.regionCode, 'buy', options);
420
- }
421
- catch {
422
- // Token fetch failed - error state will be available via selectors
423
- }
424
- try {
425
- await this.getProviders(userRegion.regionCode, options);
426
- }
427
- catch {
428
- // Provider fetch failed - error state will be available via selectors
429
- }
387
+ await this.getCountries(options);
388
+ let regionCode = this.state.userRegion?.regionCode;
389
+ regionCode ?? (regionCode = await this.messenger.call('RampsService:getGeolocation'));
390
+ if (!regionCode) {
391
+ throw new Error('Failed to fetch geolocation. Cannot initialize controller without valid region information.');
392
+ }
393
+ await this.setUserRegion(regionCode, options);
394
+ }
395
+ hydrateState(options) {
396
+ const regionCode = this.state.userRegion?.regionCode;
397
+ if (!regionCode) {
398
+ throw new Error('Region code is required. Cannot hydrate state without valid region information.');
430
399
  }
400
+ this.triggerGetTokens(regionCode, 'buy', options);
401
+ this.triggerGetProviders(regionCode, options);
431
402
  }
432
403
  /**
433
- * Fetches the list of supported countries for a given ramp action.
404
+ * Fetches the list of supported countries.
405
+ * The API returns countries with support information for both buy and sell actions.
406
+ * The countries are saved in the controller state once fetched.
434
407
  *
435
- * @param action - The ramp action type ('buy' or 'sell').
436
408
  * @param options - Options for cache behavior.
437
409
  * @returns An array of countries.
438
410
  */
439
- async getCountries(action = 'buy', options) {
440
- const cacheKey = createCacheKey('getCountries', [action]);
441
- return this.executeRequest(cacheKey, async () => {
442
- return this.messenger.call('RampsService:getCountries', action);
411
+ async getCountries(options) {
412
+ const cacheKey = createCacheKey('getCountries', []);
413
+ const countries = await this.executeRequest(cacheKey, async () => {
414
+ return this.messenger.call('RampsService:getCountries');
443
415
  }, options);
416
+ this.update((state) => {
417
+ state.countries = countries;
418
+ });
419
+ return countries;
444
420
  }
445
421
  /**
446
422
  * Fetches the list of available tokens for a given region and action.
@@ -448,7 +424,8 @@ export class RampsController extends BaseController {
448
424
  *
449
425
  * @param region - The region code (e.g., "us", "fr", "us-ny"). If not provided, uses the user's region from controller state.
450
426
  * @param action - The ramp action type ('buy' or 'sell').
451
- * @param options - Options for cache behavior.
427
+ * @param options - Options for cache behavior and query filters.
428
+ * @param options.provider - Provider ID(s) to filter by.
452
429
  * @returns The tokens response containing topTokens and allTokens.
453
430
  */
454
431
  async getTokens(region, action = 'buy', options) {
@@ -457,9 +434,15 @@ export class RampsController extends BaseController {
457
434
  throw new Error('Region is required. Either provide a region parameter or ensure userRegion is set in controller state.');
458
435
  }
459
436
  const normalizedRegion = regionToUse.toLowerCase().trim();
460
- const cacheKey = createCacheKey('getTokens', [normalizedRegion, action]);
437
+ const cacheKey = createCacheKey('getTokens', [
438
+ normalizedRegion,
439
+ action,
440
+ options?.provider,
441
+ ]);
461
442
  const tokens = await this.executeRequest(cacheKey, async () => {
462
- return this.messenger.call('RampsService:getTokens', normalizedRegion, action);
443
+ return this.messenger.call('RampsService:getTokens', normalizedRegion, action, {
444
+ provider: options?.provider,
445
+ });
463
446
  }, options);
464
447
  this.update((state) => {
465
448
  const userRegionCode = state.userRegion?.regionCode;
@@ -469,6 +452,45 @@ export class RampsController extends BaseController {
469
452
  });
470
453
  return tokens;
471
454
  }
455
+ /**
456
+ * Sets the user's selected token by asset ID.
457
+ * Looks up the token from the current tokens in state and automatically
458
+ * fetches payment methods for that token.
459
+ *
460
+ * @param assetId - The asset identifier in CAIP-19 format (e.g., "eip155:1/erc20:0x..."), or undefined to clear.
461
+ * @throws If region is not set, tokens are not loaded, or token is not found.
462
+ */
463
+ setSelectedToken(assetId) {
464
+ if (!assetId) {
465
+ this.update((state) => {
466
+ state.selectedToken = null;
467
+ state.paymentMethods = [];
468
+ state.selectedPaymentMethod = null;
469
+ });
470
+ return;
471
+ }
472
+ const regionCode = this.state.userRegion?.regionCode;
473
+ if (!regionCode) {
474
+ throw new Error('Region is required. Cannot set selected token without valid region information.');
475
+ }
476
+ const { tokens } = this.state;
477
+ if (!tokens) {
478
+ throw new Error('Tokens not loaded. Cannot set selected token before tokens are fetched.');
479
+ }
480
+ const token = tokens.allTokens.find((tok) => tok.assetId === assetId) ??
481
+ tokens.topTokens.find((tok) => tok.assetId === assetId);
482
+ if (!token) {
483
+ throw new Error(`Token with asset ID "${assetId}" not found in available tokens.`);
484
+ }
485
+ this.update((state) => {
486
+ state.selectedToken = token;
487
+ state.paymentMethods = [];
488
+ state.selectedPaymentMethod = null;
489
+ });
490
+ this.triggerGetPaymentMethods(regionCode, {
491
+ assetId: token.assetId,
492
+ });
493
+ }
472
494
  /**
473
495
  * Fetches the list of providers for a given region.
474
496
  * The providers are saved in the controller state once fetched.
@@ -510,28 +532,196 @@ export class RampsController extends BaseController {
510
532
  });
511
533
  return { providers };
512
534
  }
535
+ /**
536
+ * Fetches the list of payment methods for a given context.
537
+ * The payment methods are saved in the controller state once fetched.
538
+ *
539
+ * @param region - User's region code (e.g. "fr", "us-ny").
540
+ * @param options - Query parameters for filtering payment methods.
541
+ * @param options.fiat - Fiat currency code (e.g., "usd"). If not provided, uses the user's region currency.
542
+ * @param options.assetId - CAIP-19 cryptocurrency identifier.
543
+ * @param options.provider - Provider ID path.
544
+ * @returns The payment methods response containing payments array.
545
+ */
546
+ async getPaymentMethods(region, options) {
547
+ const regionCode = region ?? this.state.userRegion?.regionCode ?? null;
548
+ const fiatToUse = options?.fiat ?? this.state.userRegion?.country?.currency ?? null;
549
+ const assetIdToUse = options?.assetId ?? this.state.selectedToken?.assetId ?? '';
550
+ const providerToUse = options?.provider ?? this.state.selectedProvider?.id ?? '';
551
+ if (!regionCode) {
552
+ throw new Error('Region is required. Either provide a region parameter or ensure userRegion is set in controller state.');
553
+ }
554
+ if (!fiatToUse) {
555
+ throw new Error('Fiat currency is required. Either provide a fiat parameter or ensure userRegion is set in controller state.');
556
+ }
557
+ const normalizedRegion = regionCode.toLowerCase().trim();
558
+ const normalizedFiat = fiatToUse.toLowerCase().trim();
559
+ const cacheKey = createCacheKey('getPaymentMethods', [
560
+ normalizedRegion,
561
+ normalizedFiat,
562
+ assetIdToUse,
563
+ providerToUse,
564
+ ]);
565
+ const response = await this.executeRequest(cacheKey, async () => {
566
+ return this.messenger.call('RampsService:getPaymentMethods', {
567
+ region: normalizedRegion,
568
+ fiat: normalizedFiat,
569
+ assetId: assetIdToUse,
570
+ provider: providerToUse,
571
+ });
572
+ }, options);
573
+ this.update((state) => {
574
+ const currentAssetId = state.selectedToken?.assetId ?? '';
575
+ const currentProviderId = state.selectedProvider?.id ?? '';
576
+ const tokenSelectionUnchanged = assetIdToUse === currentAssetId;
577
+ const providerSelectionUnchanged = providerToUse === currentProviderId;
578
+ // 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
579
+ // ex: if the user rapidly changes the token or provider, the in-flight payment methods might not be valid
580
+ // so this check will ensure that the payment methods are still valid for the token and provider that were requested
581
+ if (tokenSelectionUnchanged && providerSelectionUnchanged) {
582
+ state.paymentMethods = response.payments;
583
+ // this will auto-select the first payment method if the selected payment method is not in the new payment methods
584
+ const currentSelectionStillValid = response.payments.some((pm) => pm.id === state.selectedPaymentMethod?.id);
585
+ if (!currentSelectionStillValid) {
586
+ state.selectedPaymentMethod = response.payments[0] ?? null;
587
+ }
588
+ }
589
+ });
590
+ return response;
591
+ }
592
+ /**
593
+ * Sets the user's selected payment method by ID.
594
+ * Looks up the payment method from the current payment methods in state.
595
+ *
596
+ * @param paymentMethodId - The payment method ID (e.g., "/payments/debit-credit-card"), or null to clear.
597
+ * @throws If payment methods are not loaded or payment method is not found.
598
+ */
599
+ setSelectedPaymentMethod(paymentMethodId) {
600
+ if (!paymentMethodId) {
601
+ this.update((state) => {
602
+ state.selectedPaymentMethod = null;
603
+ });
604
+ return;
605
+ }
606
+ const { paymentMethods } = this.state;
607
+ if (!paymentMethods || paymentMethods.length === 0) {
608
+ throw new Error('Payment methods not loaded. Cannot set selected payment method before payment methods are fetched.');
609
+ }
610
+ const paymentMethod = paymentMethods.find((pm) => pm.id === paymentMethodId);
611
+ if (!paymentMethod) {
612
+ throw new Error(`Payment method with ID "${paymentMethodId}" not found in available payment methods.`);
613
+ }
614
+ this.update((state) => {
615
+ state.selectedPaymentMethod = paymentMethod;
616
+ });
617
+ }
618
+ // ============================================================
619
+ // Sync Trigger Methods
620
+ // These fire-and-forget methods are for use in React effects.
621
+ // Errors are stored in state and available via selectors.
622
+ // ============================================================
623
+ /**
624
+ * Triggers setting the user region without throwing.
625
+ *
626
+ * @param region - The region code to set (e.g., "US-CA").
627
+ * @param options - Options for cache behavior.
628
+ */
629
+ triggerSetUserRegion(region, options) {
630
+ this.setUserRegion(region, options).catch(() => {
631
+ // Error stored in state
632
+ });
633
+ }
634
+ /**
635
+ * Triggers fetching countries without throwing.
636
+ *
637
+ * @param options - Options for cache behavior.
638
+ */
639
+ triggerGetCountries(options) {
640
+ this.getCountries(options).catch(() => {
641
+ // Error stored in state
642
+ });
643
+ }
644
+ /**
645
+ * Triggers fetching tokens without throwing.
646
+ *
647
+ * @param region - The region code. If not provided, uses userRegion from state.
648
+ * @param action - The ramp action type ('buy' or 'sell').
649
+ * @param options - Options for cache behavior.
650
+ */
651
+ triggerGetTokens(region, action = 'buy', options) {
652
+ this.getTokens(region, action, options).catch(() => {
653
+ // Error stored in state
654
+ });
655
+ }
656
+ /**
657
+ * Triggers fetching providers without throwing.
658
+ *
659
+ * @param region - The region code. If not provided, uses userRegion from state.
660
+ * @param options - Options for cache behavior and query filters.
661
+ */
662
+ triggerGetProviders(region, options) {
663
+ this.getProviders(region, options).catch(() => {
664
+ // Error stored in state
665
+ });
666
+ }
667
+ /**
668
+ * Triggers fetching payment methods without throwing.
669
+ *
670
+ * @param region - User's region code (e.g., "us", "fr", "us-ny").
671
+ * @param options - Query parameters for filtering payment methods.
672
+ * @param options.fiat - Fiat currency code. If not provided, uses userRegion currency.
673
+ * @param options.assetId - CAIP-19 cryptocurrency identifier.
674
+ * @param options.provider - Provider ID path.
675
+ */
676
+ triggerGetPaymentMethods(region, options) {
677
+ this.getPaymentMethods(region, options).catch(() => {
678
+ // Error stored in state
679
+ });
680
+ }
513
681
  }
514
682
  _RampsController_requestCacheTTL = new WeakMap(), _RampsController_requestCacheMaxSize = new WeakMap(), _RampsController_pendingRequests = new WeakMap(), _RampsController_instances = new WeakSet(), _RampsController_removeRequestState = function _RampsController_removeRequestState(cacheKey) {
515
683
  this.update((state) => {
516
684
  const requests = state.requests;
517
685
  delete requests[cacheKey];
518
686
  });
687
+ }, _RampsController_cleanupState = function _RampsController_cleanupState() {
688
+ this.update((state) => {
689
+ state.userRegion = null;
690
+ state.selectedProvider = null;
691
+ state.selectedToken = null;
692
+ state.tokens = null;
693
+ state.providers = [];
694
+ state.paymentMethods = [];
695
+ state.selectedPaymentMethod = null;
696
+ });
519
697
  }, _RampsController_updateRequestState = function _RampsController_updateRequestState(cacheKey, requestState) {
520
698
  const maxSize = __classPrivateFieldGet(this, _RampsController_requestCacheMaxSize, "f");
699
+ const ttl = __classPrivateFieldGet(this, _RampsController_requestCacheTTL, "f");
521
700
  this.update((state) => {
522
701
  const requests = state.requests;
523
702
  requests[cacheKey] = requestState;
524
- // Evict oldest entries if cache exceeds max size
703
+ // Evict expired entries based on TTL
704
+ // Only evict SUCCESS states that have exceeded their TTL
525
705
  const keys = Object.keys(requests);
526
- if (keys.length > maxSize) {
706
+ for (const key of keys) {
707
+ const entry = requests[key];
708
+ if (entry &&
709
+ entry.status === RequestStatus.SUCCESS &&
710
+ isCacheExpired(entry, ttl)) {
711
+ delete requests[key];
712
+ }
713
+ }
714
+ // Evict oldest entries if cache still exceeds max size
715
+ const remainingKeys = Object.keys(requests);
716
+ if (remainingKeys.length > maxSize) {
527
717
  // Sort by timestamp (oldest first)
528
- const sortedKeys = keys.sort((a, b) => {
718
+ const sortedKeys = remainingKeys.sort((a, b) => {
529
719
  const aTime = requests[a]?.timestamp ?? 0;
530
720
  const bTime = requests[b]?.timestamp ?? 0;
531
721
  return aTime - bTime;
532
722
  });
533
723
  // Remove oldest entries until we're under the limit
534
- const entriesToRemove = keys.length - maxSize;
724
+ const entriesToRemove = remainingKeys.length - maxSize;
535
725
  for (let i = 0; i < entriesToRemove; i++) {
536
726
  const keyToRemove = sortedKeys[i];
537
727
  if (keyToRemove) {