@metamask-previews/ramps-controller 8.0.0-preview-e7b1aa6 → 8.0.0-preview-27e39dd44

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,14 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
- ### Added
11
-
12
- - Add `widgetUrl` resource state that automatically fetches and stores the buy widget URL whenever the selected quote changes ([#7920](https://github.com/MetaMask/core/pull/7920))
13
-
14
- ### Changed
15
-
16
- - Refactor: Consolidate reset logic with a shared resetResource helper and fix abort handling for dependent resources ([#7818](https://github.com/MetaMask/core/pull/7818))
17
-
18
10
  ## [8.0.0]
19
11
 
20
12
  ### Changed
@@ -10,7 +10,7 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
10
10
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
11
11
  return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
12
12
  };
13
- var _RampsController_instances, _RampsController_requestCacheTTL, _RampsController_requestCacheMaxSize, _RampsController_pendingRequests, _RampsController_pendingResourceCount, _RampsController_quotePollingInterval, _RampsController_quotePollingOptions, _RampsController_clearPendingResourceCountForDependentResources, _RampsController_abortDependentRequests, _RampsController_mutateRequests, _RampsController_removeRequestState, _RampsController_cleanupState, _RampsController_fireAndForget, _RampsController_restartPollingIfActive, _RampsController_requireRegion, _RampsController_isRegionCurrent, _RampsController_isTokenCurrent, _RampsController_isProviderCurrent, _RampsController_updateResourceField, _RampsController_setResourceLoading, _RampsController_setResourceError, _RampsController_updateRequestState, _RampsController_syncWidgetUrl;
13
+ var _RampsController_instances, _RampsController_requestCacheTTL, _RampsController_requestCacheMaxSize, _RampsController_pendingRequests, _RampsController_pendingResourceCount, _RampsController_quotePollingInterval, _RampsController_quotePollingOptions, _RampsController_clearPendingResourceCountForDependentResources, _RampsController_removeRequestState, _RampsController_cleanupState, _RampsController_fireAndForget, _RampsController_restartPollingIfActive, _RampsController_updateResourceField, _RampsController_setResourceLoading, _RampsController_setResourceError, _RampsController_updateRequestState;
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.RampsController = exports.getDefaultRampsControllerState = exports.RAMPS_CONTROLLER_REQUIRED_SERVICE_ACTIONS = exports.controllerName = void 0;
16
16
  const base_controller_1 = require("@metamask/base-controller");
@@ -81,12 +81,6 @@ const rampsControllerMetadata = {
81
81
  includeInStateLogs: false,
82
82
  usedInUi: true,
83
83
  },
84
- widgetUrl: {
85
- persist: false,
86
- includeInDebugSnapshot: true,
87
- includeInStateLogs: false,
88
- usedInUi: true,
89
- },
90
84
  requests: {
91
85
  persist: false,
92
86
  includeInDebugSnapshot: true,
@@ -127,39 +121,10 @@ function getDefaultRampsControllerState() {
127
121
  tokens: createDefaultResourceState(null, null),
128
122
  paymentMethods: createDefaultResourceState([], null),
129
123
  quotes: createDefaultResourceState(null, null),
130
- widgetUrl: createDefaultResourceState(null),
131
124
  requests: {},
132
125
  };
133
126
  }
134
127
  exports.getDefaultRampsControllerState = getDefaultRampsControllerState;
135
- const DEPENDENT_RESOURCE_KEYS = [
136
- 'providers',
137
- 'tokens',
138
- 'paymentMethods',
139
- 'quotes',
140
- ];
141
- const DEPENDENT_RESOURCE_KEYS_SET = new Set(DEPENDENT_RESOURCE_KEYS);
142
- function resetResource(state, resourceType, defaultResource) {
143
- const def = defaultResource ?? getDefaultRampsControllerState()[resourceType];
144
- const resource = state[resourceType];
145
- resource.data = def.data;
146
- resource.selected = def.selected;
147
- resource.isLoading = def.isLoading;
148
- resource.error = def.error;
149
- }
150
- /**
151
- * Resets the widgetUrl resource to its default state.
152
- * Mutates state in place; use from within controller update() for atomic updates.
153
- *
154
- * @param state - The state object to mutate.
155
- */
156
- function resetWidgetUrl(state) {
157
- const def = getDefaultRampsControllerState().widgetUrl;
158
- state.widgetUrl.data = def.data;
159
- state.widgetUrl.selected = def.selected;
160
- state.widgetUrl.isLoading = def.isLoading;
161
- state.widgetUrl.error = def.error;
162
- }
163
128
  /**
164
129
  * Resets region-dependent resources (userRegion, providers, tokens, paymentMethods, quotes).
165
130
  * Mutates state in place; use from within controller update() for atomic updates.
@@ -172,11 +137,22 @@ function resetDependentResources(state, options) {
172
137
  if (options?.clearUserRegionData) {
173
138
  state.userRegion = null;
174
139
  }
175
- const defaultState = getDefaultRampsControllerState();
176
- for (const key of DEPENDENT_RESOURCE_KEYS) {
177
- resetResource(state, key, defaultState[key]);
178
- }
179
- resetWidgetUrl(state);
140
+ state.providers.selected = null;
141
+ state.providers.data = [];
142
+ state.providers.isLoading = false;
143
+ state.providers.error = null;
144
+ state.tokens.selected = null;
145
+ state.tokens.data = null;
146
+ state.tokens.isLoading = false;
147
+ state.tokens.error = null;
148
+ state.paymentMethods.data = [];
149
+ state.paymentMethods.selected = null;
150
+ state.paymentMethods.isLoading = false;
151
+ state.paymentMethods.error = null;
152
+ state.quotes.data = null;
153
+ state.quotes.selected = null;
154
+ state.quotes.isLoading = false;
155
+ state.quotes.error = null;
180
156
  }
181
157
  // === HELPER FUNCTIONS ===
182
158
  /**
@@ -300,49 +276,31 @@ class RampsController extends base_controller_1.BaseController {
300
276
  __classPrivateFieldSet(this, _RampsController_requestCacheMaxSize, requestCacheMaxSize, "f");
301
277
  }
302
278
  /**
303
- * Executes a request with caching, deduplication, and at most one in-flight
304
- * request per resource type.
305
- *
306
- * 1. **Same cache key in flight** – If a request with this cache key is
307
- * already pending, returns that promise (deduplication; no second request).
279
+ * Executes a request with caching and deduplication.
308
280
  *
309
- * 2. **Cache hit** If valid, non-expired data exists in state.requests for
310
- * this key and forceRefresh is not set, returns that data without fetching.
281
+ * If a request with the same cache key is already in flight, returns the
282
+ * existing promise. If valid cached data exists, returns it without making
283
+ * a new request.
311
284
  *
312
- * 3. **New request** Creates an AbortController and fires the fetcher.
313
- * If options.resourceType is set, tags the pending request with that
314
- * resource type (so #abortDependentRequests can cancel it on region
315
- * change or cleanup) and ref-counts resource-level loading state.
316
- * On success or error, updates request state and resource error;
317
- * in finally, clears resource loading only if this request was not
318
- * aborted.
319
- *
320
- * @param cacheKey - Unique identifier for this request (e.g. from createCacheKey).
321
- * @param fetcher - Async function that performs the fetch. Receives an AbortSignal
322
- * that is aborted when this request is superseded by another for the same resource.
323
- * @param options - Optional forceRefresh, ttl, and resourceType for loading/error state.
324
- * @returns The result of the request (from cache, joined promise, or fetcher).
285
+ * @param cacheKey - Unique identifier for this request.
286
+ * @param fetcher - Function that performs the actual fetch. Receives an AbortSignal.
287
+ * @param options - Options for cache behavior.
288
+ * @returns The result of the request.
325
289
  */
326
290
  async executeRequest(cacheKey, fetcher, options) {
327
- // Get TTL for verifying cache expiration
328
291
  const ttl = options?.ttl ?? __classPrivateFieldGet(this, _RampsController_requestCacheTTL, "f");
329
- // DEDUPLICATION:
330
- // Check if a request is already in flight for this cache key
331
- // If so, return the original promise for that request
292
+ // Check for existing pending request - join it instead of making a duplicate
332
293
  const pending = __classPrivateFieldGet(this, _RampsController_pendingRequests, "f").get(cacheKey);
333
294
  if (pending) {
334
295
  return pending.promise;
335
296
  }
336
- // CACHE HIT:
337
- // If cache is not expired, return the cached data
338
297
  if (!options?.forceRefresh) {
339
298
  const cached = this.state.requests[cacheKey];
340
299
  if (cached && !(0, RequestCache_1.isCacheExpired)(cached, ttl)) {
341
300
  return cached.data;
342
301
  }
343
302
  }
344
- // Create a new abort controller for this request
345
- // Record the time the request was started
303
+ // Create abort controller for this request
346
304
  const abortController = new AbortController();
347
305
  const lastFetchedAt = Date.now();
348
306
  const { resourceType } = options ?? {};
@@ -361,11 +319,15 @@ class RampsController extends base_controller_1.BaseController {
361
319
  const promise = (async () => {
362
320
  try {
363
321
  const data = await fetcher(abortController.signal);
322
+ // Don't update state if aborted
364
323
  if (abortController.signal.aborted) {
365
324
  throw new Error('Request was aborted');
366
325
  }
367
326
  __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_updateRequestState).call(this, cacheKey, (0, RequestCache_1.createSuccessState)(data, lastFetchedAt));
368
327
  if (resourceType) {
328
+ // We need the extra logic because there are two situations where we’re allowed to clear the error:
329
+ // No callback → always clear
330
+ // Callback present → clear only when isResultCurrent() returns true.
369
331
  const isCurrent = !options?.isResultCurrent || options.isResultCurrent();
370
332
  if (isCurrent) {
371
333
  __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_setResourceError).call(this, resourceType, null);
@@ -374,6 +336,7 @@ class RampsController extends base_controller_1.BaseController {
374
336
  return data;
375
337
  }
376
338
  catch (error) {
339
+ // Don't update state if aborted
377
340
  if (abortController.signal.aborted) {
378
341
  throw error;
379
342
  }
@@ -388,12 +351,13 @@ class RampsController extends base_controller_1.BaseController {
388
351
  throw error;
389
352
  }
390
353
  finally {
391
- if (__classPrivateFieldGet(this, _RampsController_pendingRequests, "f").get(cacheKey)?.abortController ===
392
- abortController) {
354
+ // Only delete if this is still our entry (not replaced by a new request)
355
+ const currentPending = __classPrivateFieldGet(this, _RampsController_pendingRequests, "f").get(cacheKey);
356
+ if (currentPending?.abortController === abortController) {
393
357
  __classPrivateFieldGet(this, _RampsController_pendingRequests, "f").delete(cacheKey);
394
358
  }
395
359
  // Clear resource-level loading state only when no requests for this resource remain
396
- if (resourceType && !abortController.signal.aborted) {
360
+ if (resourceType) {
397
361
  const count = __classPrivateFieldGet(this, _RampsController_pendingResourceCount, "f").get(resourceType) ?? 0;
398
362
  const next = Math.max(0, count - 1);
399
363
  if (next === 0) {
@@ -406,11 +370,8 @@ class RampsController extends base_controller_1.BaseController {
406
370
  }
407
371
  }
408
372
  })();
409
- __classPrivateFieldGet(this, _RampsController_pendingRequests, "f").set(cacheKey, {
410
- promise,
411
- abortController,
412
- resourceType,
413
- });
373
+ // Store pending request for deduplication
374
+ __classPrivateFieldGet(this, _RampsController_pendingRequests, "f").set(cacheKey, { promise, abortController });
414
375
  return promise;
415
376
  }
416
377
  /**
@@ -464,8 +425,9 @@ class RampsController extends base_controller_1.BaseController {
464
425
  !this.state.tokens.data ||
465
426
  this.state.providers.data.length === 0;
466
427
  if (regionChanged) {
467
- __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_abortDependentRequests).call(this);
468
428
  __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_clearPendingResourceCountForDependentResources).call(this);
429
+ }
430
+ if (regionChanged) {
469
431
  this.stopQuotePolling();
470
432
  }
471
433
  this.update((state) => {
@@ -506,11 +468,15 @@ class RampsController extends base_controller_1.BaseController {
506
468
  this.stopQuotePolling();
507
469
  this.update((state) => {
508
470
  state.providers.selected = null;
509
- resetResource(state, 'paymentMethods');
471
+ state.paymentMethods.data = [];
472
+ state.paymentMethods.selected = null;
510
473
  });
511
474
  return;
512
475
  }
513
- const regionCode = __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_requireRegion).call(this);
476
+ const regionCode = this.state.userRegion?.regionCode;
477
+ if (!regionCode) {
478
+ throw new Error('Region is required. Cannot set selected provider without valid region information.');
479
+ }
514
480
  const providers = this.state.providers.data;
515
481
  if (!providers || providers.length === 0) {
516
482
  throw new Error('Providers not loaded. Cannot set selected provider before providers are fetched.');
@@ -521,9 +487,9 @@ class RampsController extends base_controller_1.BaseController {
521
487
  }
522
488
  this.update((state) => {
523
489
  state.providers.selected = provider;
524
- resetResource(state, 'paymentMethods');
490
+ state.paymentMethods.data = [];
491
+ state.paymentMethods.selected = null;
525
492
  state.quotes.selected = null;
526
- resetWidgetUrl(state);
527
493
  });
528
494
  __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_fireAndForget).call(this, this.getPaymentMethods(regionCode, { provider: provider.id }).then(() => {
529
495
  // Restart quote polling after payment methods are fetched
@@ -551,7 +517,10 @@ class RampsController extends base_controller_1.BaseController {
551
517
  await this.setUserRegion(regionCode, options);
552
518
  }
553
519
  hydrateState(options) {
554
- const regionCode = __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_requireRegion).call(this);
520
+ const regionCode = this.state.userRegion?.regionCode;
521
+ if (!regionCode) {
522
+ throw new Error('Region code is required. Cannot hydrate state without valid region information.');
523
+ }
555
524
  __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_fireAndForget).call(this, this.getTokens(regionCode, 'buy', options));
556
525
  __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_fireAndForget).call(this, this.getProviders(regionCode, options));
557
526
  }
@@ -569,7 +538,7 @@ class RampsController extends base_controller_1.BaseController {
569
538
  return this.messenger.call('RampsService:getCountries');
570
539
  }, { ...options, resourceType: 'countries' });
571
540
  this.update((state) => {
572
- state.countries.data = Array.isArray(countries) ? [...countries] : [];
541
+ state.countries.data = countries;
573
542
  });
574
543
  return countries;
575
544
  }
@@ -584,7 +553,10 @@ class RampsController extends base_controller_1.BaseController {
584
553
  * @returns The tokens response containing topTokens and allTokens.
585
554
  */
586
555
  async getTokens(region, action = 'buy', options) {
587
- const regionToUse = region ?? __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_requireRegion).call(this);
556
+ const regionToUse = region ?? this.state.userRegion?.regionCode;
557
+ if (!regionToUse) {
558
+ throw new Error('Region is required. Either provide a region parameter or ensure userRegion is set in controller state.');
559
+ }
588
560
  const normalizedRegion = regionToUse.toLowerCase().trim();
589
561
  const cacheKey = (0, RequestCache_1.createCacheKey)('getTokens', [
590
562
  normalizedRegion,
@@ -598,7 +570,8 @@ class RampsController extends base_controller_1.BaseController {
598
570
  }, {
599
571
  ...options,
600
572
  resourceType: 'tokens',
601
- isResultCurrent: () => __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_isRegionCurrent).call(this, normalizedRegion),
573
+ isResultCurrent: () => this.state.userRegion?.regionCode === undefined ||
574
+ this.state.userRegion?.regionCode === normalizedRegion,
602
575
  });
603
576
  this.update((state) => {
604
577
  const userRegionCode = state.userRegion?.regionCode;
@@ -621,11 +594,15 @@ class RampsController extends base_controller_1.BaseController {
621
594
  this.stopQuotePolling();
622
595
  this.update((state) => {
623
596
  state.tokens.selected = null;
624
- resetResource(state, 'paymentMethods');
597
+ state.paymentMethods.data = [];
598
+ state.paymentMethods.selected = null;
625
599
  });
626
600
  return;
627
601
  }
628
- const regionCode = __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_requireRegion).call(this);
602
+ const regionCode = this.state.userRegion?.regionCode;
603
+ if (!regionCode) {
604
+ throw new Error('Region is required. Cannot set selected token without valid region information.');
605
+ }
629
606
  const tokens = this.state.tokens.data;
630
607
  if (!tokens) {
631
608
  throw new Error('Tokens not loaded. Cannot set selected token before tokens are fetched.');
@@ -637,9 +614,9 @@ class RampsController extends base_controller_1.BaseController {
637
614
  }
638
615
  this.update((state) => {
639
616
  state.tokens.selected = token;
640
- resetResource(state, 'paymentMethods');
617
+ state.paymentMethods.data = [];
618
+ state.paymentMethods.selected = null;
641
619
  state.quotes.selected = null;
642
- resetWidgetUrl(state);
643
620
  });
644
621
  __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_fireAndForget).call(this, this.getPaymentMethods(regionCode, { assetId: token.assetId }).then(() => {
645
622
  __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_restartPollingIfActive).call(this);
@@ -659,7 +636,10 @@ class RampsController extends base_controller_1.BaseController {
659
636
  * @returns The providers response containing providers array.
660
637
  */
661
638
  async getProviders(region, options) {
662
- const regionToUse = region ?? __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_requireRegion).call(this);
639
+ const regionToUse = region ?? this.state.userRegion?.regionCode;
640
+ if (!regionToUse) {
641
+ throw new Error('Region is required. Either provide a region parameter or ensure userRegion is set in controller state.');
642
+ }
663
643
  const normalizedRegion = regionToUse.toLowerCase().trim();
664
644
  const cacheKey = (0, RequestCache_1.createCacheKey)('getProviders', [
665
645
  normalizedRegion,
@@ -678,7 +658,8 @@ class RampsController extends base_controller_1.BaseController {
678
658
  }, {
679
659
  ...options,
680
660
  resourceType: 'providers',
681
- isResultCurrent: () => __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_isRegionCurrent).call(this, normalizedRegion),
661
+ isResultCurrent: () => this.state.userRegion?.regionCode === undefined ||
662
+ this.state.userRegion?.regionCode === normalizedRegion,
682
663
  });
683
664
  this.update((state) => {
684
665
  const userRegionCode = state.userRegion?.regionCode;
@@ -700,10 +681,13 @@ class RampsController extends base_controller_1.BaseController {
700
681
  * @returns The payment methods response containing payments array.
701
682
  */
702
683
  async getPaymentMethods(region, options) {
703
- const regionCode = region ?? __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_requireRegion).call(this);
684
+ const regionCode = region ?? this.state.userRegion?.regionCode ?? null;
704
685
  const fiatToUse = options?.fiat ?? this.state.userRegion?.country?.currency ?? null;
705
686
  const assetIdToUse = options?.assetId ?? this.state.tokens.selected?.assetId ?? '';
706
687
  const providerToUse = options?.provider ?? this.state.providers.selected?.id ?? '';
688
+ if (!regionCode) {
689
+ throw new Error('Region is required. Either provide a region parameter or ensure userRegion is set in controller state.');
690
+ }
707
691
  if (!fiatToUse) {
708
692
  throw new Error('Fiat currency is required. Either provide a fiat parameter or ensure userRegion is set in controller state.');
709
693
  }
@@ -726,9 +710,10 @@ class RampsController extends base_controller_1.BaseController {
726
710
  ...options,
727
711
  resourceType: 'paymentMethods',
728
712
  isResultCurrent: () => {
729
- const regionMatch = __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_isRegionCurrent).call(this, normalizedRegion);
730
- const tokenMatch = __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_isTokenCurrent).call(this, assetIdToUse);
731
- const providerMatch = __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_isProviderCurrent).call(this, providerToUse);
713
+ const regionMatch = this.state.userRegion?.regionCode === undefined ||
714
+ this.state.userRegion?.regionCode === normalizedRegion;
715
+ const tokenMatch = (this.state.tokens.selected?.assetId ?? '') === assetIdToUse;
716
+ const providerMatch = (this.state.providers.selected?.id ?? '') === providerToUse;
732
717
  return regionMatch && tokenMatch && providerMatch;
733
718
  },
734
719
  });
@@ -798,7 +783,7 @@ class RampsController extends base_controller_1.BaseController {
798
783
  * @returns The quotes response containing success, sorted, error, and customActions.
799
784
  */
800
785
  async getQuotes(options) {
801
- const regionToUse = options.region ?? __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_requireRegion).call(this);
786
+ const regionToUse = options.region ?? this.state.userRegion?.regionCode;
802
787
  const fiatToUse = options.fiat ?? this.state.userRegion?.country?.currency;
803
788
  const paymentMethodsToUse = options.paymentMethods ??
804
789
  this.state.paymentMethods.data.map((pm) => pm.id);
@@ -806,6 +791,9 @@ class RampsController extends base_controller_1.BaseController {
806
791
  this.state.providers.data.map((provider) => provider.id);
807
792
  const action = options.action ?? 'buy';
808
793
  const assetIdToUse = options.assetId ?? this.state.tokens.selected?.assetId;
794
+ if (!regionToUse) {
795
+ throw new Error('Region is required. Either provide a region parameter or ensure userRegion is set in controller state.');
796
+ }
809
797
  if (!fiatToUse) {
810
798
  throw new Error('Fiat currency is required. Either provide a fiat parameter or ensure userRegion is set in controller state.');
811
799
  }
@@ -856,7 +844,8 @@ class RampsController extends base_controller_1.BaseController {
856
844
  forceRefresh: options.forceRefresh,
857
845
  ttl: options.ttl ?? DEFAULT_QUOTES_TTL,
858
846
  resourceType: 'quotes',
859
- isResultCurrent: () => __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_isRegionCurrent).call(this, normalizedRegion),
847
+ isResultCurrent: () => this.state.userRegion?.regionCode === undefined ||
848
+ this.state.userRegion?.regionCode === normalizedRegion,
860
849
  });
861
850
  this.update((state) => {
862
851
  const userRegionCode = state.userRegion?.regionCode;
@@ -879,10 +868,14 @@ class RampsController extends base_controller_1.BaseController {
879
868
  * @throws If required dependencies (region, token, provider, payment method) are not set.
880
869
  */
881
870
  startQuotePolling(options) {
882
- __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_requireRegion).call(this);
871
+ // Validate required dependencies
872
+ const regionCode = this.state.userRegion?.regionCode;
883
873
  const token = this.state.tokens.selected;
884
874
  const provider = this.state.providers.selected;
885
875
  const paymentMethod = this.state.paymentMethods.selected;
876
+ if (!regionCode) {
877
+ throw new Error('Region is required. Cannot start quote polling without valid region information.');
878
+ }
886
879
  if (!token) {
887
880
  throw new Error('Token is required. Cannot start quote polling without a selected token.');
888
881
  }
@@ -907,12 +900,10 @@ class RampsController extends base_controller_1.BaseController {
907
900
  providers: [provider.id],
908
901
  forceRefresh: true,
909
902
  }).then((response) => {
910
- let newSelectedQuote = null;
911
903
  // Auto-select logic: only when exactly one quote is returned
912
904
  this.update((state) => {
913
905
  if (response.success.length === 1) {
914
- newSelectedQuote = response.success[0];
915
- state.quotes.selected = newSelectedQuote;
906
+ state.quotes.selected = response.success[0];
916
907
  }
917
908
  else {
918
909
  // Keep existing selection if still valid, but update with fresh data
@@ -921,12 +912,11 @@ class RampsController extends base_controller_1.BaseController {
921
912
  const freshQuote = response.success.find((quote) => quote.provider === currentSelection.provider &&
922
913
  quote.quote.paymentMethod ===
923
914
  currentSelection.quote.paymentMethod);
924
- newSelectedQuote = freshQuote ?? null;
925
- state.quotes.selected = newSelectedQuote;
915
+ // Update with fresh quote data, or clear if no longer valid
916
+ state.quotes.selected = freshQuote ?? null;
926
917
  }
927
918
  }
928
919
  });
929
- __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_syncWidgetUrl).call(this, newSelectedQuote);
930
920
  return undefined;
931
921
  }));
932
922
  };
@@ -948,7 +938,6 @@ class RampsController extends base_controller_1.BaseController {
948
938
  }
949
939
  /**
950
940
  * Manually sets the selected quote.
951
- * Automatically triggers a widget URL fetch for the new quote.
952
941
  *
953
942
  * @param quote - The quote to select, or null to clear the selection.
954
943
  */
@@ -956,7 +945,6 @@ class RampsController extends base_controller_1.BaseController {
956
945
  this.update((state) => {
957
946
  state.quotes.selected = quote;
958
947
  });
959
- __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_syncWidgetUrl).call(this, quote);
960
948
  }
961
949
  /**
962
950
  * Cleans up controller resources.
@@ -974,8 +962,6 @@ class RampsController extends base_controller_1.BaseController {
974
962
  *
975
963
  * @param quote - The quote to fetch the widget URL from.
976
964
  * @returns Promise resolving to the widget URL string, or null if not available.
977
- * @deprecated Read `state.widgetUrl` instead. The widget URL is now automatically
978
- * fetched and stored in state whenever the selected quote changes.
979
965
  */
980
966
  async getWidgetUrl(quote) {
981
967
  const buyUrl = quote.quote?.buyURL;
@@ -994,32 +980,26 @@ class RampsController extends base_controller_1.BaseController {
994
980
  }
995
981
  exports.RampsController = RampsController;
996
982
  _RampsController_requestCacheTTL = new WeakMap(), _RampsController_requestCacheMaxSize = new WeakMap(), _RampsController_pendingRequests = new WeakMap(), _RampsController_pendingResourceCount = new WeakMap(), _RampsController_quotePollingInterval = new WeakMap(), _RampsController_quotePollingOptions = new WeakMap(), _RampsController_instances = new WeakSet(), _RampsController_clearPendingResourceCountForDependentResources = function _RampsController_clearPendingResourceCountForDependentResources() {
997
- for (const resourceType of DEPENDENT_RESOURCE_KEYS) {
983
+ const types = [
984
+ 'providers',
985
+ 'tokens',
986
+ 'paymentMethods',
987
+ 'quotes',
988
+ ];
989
+ for (const resourceType of types) {
998
990
  __classPrivateFieldGet(this, _RampsController_pendingResourceCount, "f").delete(resourceType);
999
991
  }
1000
- }, _RampsController_abortDependentRequests = function _RampsController_abortDependentRequests() {
1001
- for (const [cacheKey, pending] of __classPrivateFieldGet(this, _RampsController_pendingRequests, "f").entries()) {
1002
- if (pending.resourceType &&
1003
- DEPENDENT_RESOURCE_KEYS_SET.has(pending.resourceType)) {
1004
- pending.abortController.abort();
1005
- __classPrivateFieldGet(this, _RampsController_pendingRequests, "f").delete(cacheKey);
1006
- __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_removeRequestState).call(this, cacheKey);
1007
- }
1008
- }
1009
- }, _RampsController_mutateRequests = function _RampsController_mutateRequests(fn) {
992
+ }, _RampsController_removeRequestState = function _RampsController_removeRequestState(cacheKey) {
1010
993
  this.update((state) => {
1011
994
  const requests = state.requests;
1012
- fn(requests);
1013
- });
1014
- }, _RampsController_removeRequestState = function _RampsController_removeRequestState(cacheKey) {
1015
- __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_mutateRequests).call(this, (requests) => {
1016
995
  delete requests[cacheKey];
1017
996
  });
1018
997
  }, _RampsController_cleanupState = function _RampsController_cleanupState() {
1019
998
  this.stopQuotePolling();
1020
- __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_abortDependentRequests).call(this);
1021
999
  __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_clearPendingResourceCountForDependentResources).call(this);
1022
- this.update((state) => resetDependentResources(state, { clearUserRegionData: true }));
1000
+ this.update((state) => resetDependentResources(state, {
1001
+ clearUserRegionData: true,
1002
+ }));
1023
1003
  }, _RampsController_fireAndForget = function _RampsController_fireAndForget(promise) {
1024
1004
  promise.catch((_error) => undefined);
1025
1005
  }, _RampsController_restartPollingIfActive = function _RampsController_restartPollingIfActive() {
@@ -1034,21 +1014,6 @@ _RampsController_requestCacheTTL = new WeakMap(), _RampsController_requestCacheM
1034
1014
  // when dependencies are available
1035
1015
  }
1036
1016
  }
1037
- }, _RampsController_requireRegion = function _RampsController_requireRegion() {
1038
- const regionCode = this.state.userRegion?.regionCode;
1039
- if (!regionCode) {
1040
- throw new Error('Region is required. Cannot proceed without valid region information.');
1041
- }
1042
- return regionCode;
1043
- }, _RampsController_isRegionCurrent = function _RampsController_isRegionCurrent(normalizedRegion) {
1044
- const current = this.state.userRegion?.regionCode;
1045
- return current === undefined || current === normalizedRegion;
1046
- }, _RampsController_isTokenCurrent = function _RampsController_isTokenCurrent(normalizedAssetId) {
1047
- const current = this.state.tokens.selected?.assetId ?? '';
1048
- return current === normalizedAssetId;
1049
- }, _RampsController_isProviderCurrent = function _RampsController_isProviderCurrent(normalizedProviderId) {
1050
- const current = this.state.providers.selected?.id ?? '';
1051
- return current === normalizedProviderId;
1052
1017
  }, _RampsController_updateResourceField = function _RampsController_updateResourceField(resourceType, field, value) {
1053
1018
  this.update((state) => {
1054
1019
  const resource = state[resourceType];
@@ -1063,8 +1028,11 @@ _RampsController_requestCacheTTL = new WeakMap(), _RampsController_requestCacheM
1063
1028
  }, _RampsController_updateRequestState = function _RampsController_updateRequestState(cacheKey, requestState) {
1064
1029
  const maxSize = __classPrivateFieldGet(this, _RampsController_requestCacheMaxSize, "f");
1065
1030
  const ttl = __classPrivateFieldGet(this, _RampsController_requestCacheTTL, "f");
1066
- __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_mutateRequests).call(this, (requests) => {
1031
+ this.update((state) => {
1032
+ const requests = state.requests;
1067
1033
  requests[cacheKey] = requestState;
1034
+ // Evict expired entries based on TTL
1035
+ // Only evict SUCCESS states that have exceeded their TTL
1068
1036
  const keys = Object.keys(requests);
1069
1037
  for (const key of keys) {
1070
1038
  const entry = requests[key];
@@ -1073,13 +1041,16 @@ _RampsController_requestCacheTTL = new WeakMap(), _RampsController_requestCacheM
1073
1041
  delete requests[key];
1074
1042
  }
1075
1043
  }
1044
+ // Evict oldest entries if cache still exceeds max size
1076
1045
  const remainingKeys = Object.keys(requests);
1077
1046
  if (remainingKeys.length > maxSize) {
1047
+ // Sort by timestamp (oldest first)
1078
1048
  const sortedKeys = remainingKeys.sort((a, b) => {
1079
1049
  const aTime = requests[a]?.timestamp ?? 0;
1080
1050
  const bTime = requests[b]?.timestamp ?? 0;
1081
1051
  return aTime - bTime;
1082
1052
  });
1053
+ // Remove oldest entries until we're under the limit
1083
1054
  const entriesToRemove = remainingKeys.length - maxSize;
1084
1055
  for (let i = 0; i < entriesToRemove; i++) {
1085
1056
  const keyToRemove = sortedKeys[i];
@@ -1089,38 +1060,5 @@ _RampsController_requestCacheTTL = new WeakMap(), _RampsController_requestCacheM
1089
1060
  }
1090
1061
  }
1091
1062
  });
1092
- }, _RampsController_syncWidgetUrl = function _RampsController_syncWidgetUrl(quote) {
1093
- const buyUrl = quote?.quote?.buyURL;
1094
- if (!buyUrl) {
1095
- this.update((state) => {
1096
- resetWidgetUrl(state);
1097
- });
1098
- return;
1099
- }
1100
- if (this.state.widgetUrl.data === null) {
1101
- this.update((state) => {
1102
- state.widgetUrl.isLoading = true;
1103
- state.widgetUrl.error = null;
1104
- });
1105
- }
1106
- __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_fireAndForget).call(this, this.messenger
1107
- .call('RampsService:getBuyWidgetUrl', buyUrl)
1108
- .then((buyWidget) => {
1109
- this.update((state) => {
1110
- state.widgetUrl.data = buyWidget;
1111
- state.widgetUrl.isLoading = false;
1112
- state.widgetUrl.error = null;
1113
- });
1114
- return undefined;
1115
- })
1116
- .catch((error) => {
1117
- this.update((state) => {
1118
- state.widgetUrl.isLoading = false;
1119
- state.widgetUrl.error =
1120
- error instanceof Error
1121
- ? error.message
1122
- : 'Failed to fetch widget URL';
1123
- });
1124
- }));
1125
1063
  };
1126
1064
  //# sourceMappingURL=RampsController.cjs.map