@metamask/ramps-controller 15.1.0 → 17.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.
Files changed (55) hide show
  1. package/CHANGELOG.md +33 -1
  2. package/LICENSE +1 -0
  3. package/dist/RampsController-method-action-types.cjs.map +1 -1
  4. package/dist/RampsController-method-action-types.d.cts +1 -1
  5. package/dist/RampsController-method-action-types.d.mts +1 -1
  6. package/dist/RampsController-method-action-types.mjs.map +1 -1
  7. package/dist/RampsController.cjs +49 -85
  8. package/dist/RampsController.cjs.map +1 -1
  9. package/dist/RampsController.d.cts +10 -28
  10. package/dist/RampsController.d.cts.map +1 -1
  11. package/dist/RampsController.d.mts +10 -28
  12. package/dist/RampsController.d.mts.map +1 -1
  13. package/dist/RampsController.mjs +48 -83
  14. package/dist/RampsController.mjs.map +1 -1
  15. package/dist/errorNormalization.cjs +84 -0
  16. package/dist/errorNormalization.cjs.map +1 -0
  17. package/dist/errorNormalization.d.cts +56 -0
  18. package/dist/errorNormalization.d.cts.map +1 -0
  19. package/dist/errorNormalization.d.mts +56 -0
  20. package/dist/errorNormalization.d.mts.map +1 -0
  21. package/dist/errorNormalization.mjs +78 -0
  22. package/dist/errorNormalization.mjs.map +1 -0
  23. package/dist/featureFlags.cjs +39 -0
  24. package/dist/featureFlags.cjs.map +1 -0
  25. package/dist/featureFlags.d.cts +40 -0
  26. package/dist/featureFlags.d.cts.map +1 -0
  27. package/dist/featureFlags.d.mts +40 -0
  28. package/dist/featureFlags.d.mts.map +1 -0
  29. package/dist/featureFlags.mjs +35 -0
  30. package/dist/featureFlags.mjs.map +1 -0
  31. package/dist/index.cjs +17 -2
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.d.cts +8 -2
  34. package/dist/index.d.cts.map +1 -1
  35. package/dist/index.d.mts +8 -2
  36. package/dist/index.d.mts.map +1 -1
  37. package/dist/index.mjs +5 -1
  38. package/dist/index.mjs.map +1 -1
  39. package/dist/providerAvailability.cjs +103 -0
  40. package/dist/providerAvailability.cjs.map +1 -0
  41. package/dist/providerAvailability.d.cts +76 -0
  42. package/dist/providerAvailability.d.cts.map +1 -0
  43. package/dist/providerAvailability.d.mts +76 -0
  44. package/dist/providerAvailability.d.mts.map +1 -0
  45. package/dist/providerAvailability.mjs +96 -0
  46. package/dist/providerAvailability.mjs.map +1 -0
  47. package/dist/quoteClassification.cjs +44 -0
  48. package/dist/quoteClassification.cjs.map +1 -0
  49. package/dist/quoteClassification.d.cts +33 -0
  50. package/dist/quoteClassification.d.cts.map +1 -0
  51. package/dist/quoteClassification.d.mts +33 -0
  52. package/dist/quoteClassification.d.mts.map +1 -0
  53. package/dist/quoteClassification.mjs +38 -0
  54. package/dist/quoteClassification.mjs.map +1 -0
  55. package/package.json +3 -2
@@ -10,11 +10,13 @@ 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_getProviderScope, _RampsController_getDefaultRedirectUrl, _RampsController_pendingRequests, _RampsController_pendingResourceCount, _RampsController_pendingResourceGeneration, _RampsController_orderPollingMeta, _RampsController_orderPollingTimer, _RampsController_isPolling, _RampsController_initPromise, _RampsController_clearPendingResourceCountForDependentResources, _RampsController_abortDependentRequests, _RampsController_mutateRequests, _RampsController_removeRequestState, _RampsController_cleanupState, _RampsController_requireRegion, _RampsController_isRegionCurrent, _RampsController_isTokenCurrent, _RampsController_isProviderCurrent, _RampsController_updateResourceField, _RampsController_setResourceLoading, _RampsController_setResourceError, _RampsController_updateRequestState, _RampsController_runInit, _RampsController_syncUserRegionFromCountriesCatalog, _RampsController_pickInAppQuote, _RampsController_getSupportingProvidersForRegion, _RampsController_filterProviderIdsBySupport, _RampsController_resolveProviderIdsForQuote, _RampsController_getPreferredProviderIdsFromOrders, _RampsController_refreshOrder, _RampsController_pollPendingOrders, _RampsController_syncTransakAuthOnError, _RampsController_getNormalizedTransakError;
13
+ var _RampsController_instances, _RampsController_requestCacheTTL, _RampsController_requestCacheMaxSize, _RampsController_getDefaultRedirectUrl, _RampsController_pendingRequests, _RampsController_pendingResourceCount, _RampsController_pendingResourceGeneration, _RampsController_orderPollingMeta, _RampsController_orderPollingTimer, _RampsController_isPolling, _RampsController_initPromise, _RampsController_clearPendingResourceCountForDependentResources, _RampsController_abortDependentRequests, _RampsController_isAllProvidersEnabled, _RampsController_mutateRequests, _RampsController_removeRequestState, _RampsController_cleanupState, _RampsController_requireRegion, _RampsController_isRegionCurrent, _RampsController_isTokenCurrent, _RampsController_isProviderCurrent, _RampsController_updateResourceField, _RampsController_setResourceLoading, _RampsController_setResourceError, _RampsController_updateRequestState, _RampsController_runInit, _RampsController_syncUserRegionFromCountriesCatalog, _RampsController_pickWidenedQuote, _RampsController_getSupportingProvidersForRegion, _RampsController_filterProviderIdsBySupport, _RampsController_resolveProviderIdsForQuote, _RampsController_getPreferredProviderIdsFromOrders, _RampsController_refreshOrder, _RampsController_pollPendingOrders, _RampsController_syncTransakAuthOnError, _RampsController_getNormalizedTransakError;
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.RampsController = exports.getInternalOrderCode = exports.normalizeProviderCode = exports.getDefaultRampsControllerState = exports.RAMPS_CONTROLLER_REQUIRED_SERVICE_ACTIONS = exports.controllerName = void 0;
15
+ exports.RampsController = exports.getInternalOrderCode = exports.getDefaultRampsControllerState = exports.RAMPS_CONTROLLER_REQUIRED_SERVICE_ACTIONS = exports.controllerName = void 0;
16
16
  const base_controller_1 = require("@metamask/base-controller");
17
17
  const controller_utils_1 = require("@metamask/controller-utils");
18
+ const featureFlags_1 = require("./featureFlags.cjs");
19
+ const providerAvailability_1 = require("./providerAvailability.cjs");
18
20
  const rampsErrorCodes_1 = require("./rampsErrorCodes.cjs");
19
21
  const RampsService_1 = require("./RampsService.cjs");
20
22
  const RequestCache_1 = require("./RequestCache.cjs");
@@ -329,10 +331,6 @@ function findRegionFromCode(regionCode, countries) {
329
331
  regionCode: normalizedCode,
330
332
  };
331
333
  }
332
- function normalizeProviderCode(providerCode) {
333
- return providerCode.replace(/^\/providers\//u, '');
334
- }
335
- exports.normalizeProviderCode = normalizeProviderCode;
336
334
  /**
337
335
  * Returns the internal MetaMask order code used for state lookups and polling.
338
336
  * Prefers the code embedded in the canonical order `id` path over `providerOrderId`,
@@ -441,13 +439,11 @@ class RampsController extends base_controller_1.BaseController {
441
439
  * controller. Missing properties will be filled in with defaults.
442
440
  * @param args.requestCacheTTL - Time to live for cached requests in milliseconds.
443
441
  * @param args.requestCacheMaxSize - Maximum number of entries in the request cache.
444
- * @param args.getProviderScope - Optional callback returning the current
445
- * provider-class scope for fiat quote widening. Defaults to `off`.
446
442
  * @param args.getDefaultRedirectUrl - Optional callback returning the default
447
- * redirect URL used for the widened in-app quote fetch when the caller omits
443
+ * redirect URL used for the widened quote fetch when the caller omits
448
444
  * `redirectUrl`. Defaults to a callback returning `undefined`.
449
445
  */
450
- constructor({ messenger, state = {}, requestCacheTTL = RequestCache_1.DEFAULT_REQUEST_CACHE_TTL, requestCacheMaxSize = RequestCache_1.DEFAULT_REQUEST_CACHE_MAX_SIZE, getProviderScope, getDefaultRedirectUrl, }) {
446
+ constructor({ messenger, state = {}, requestCacheTTL = RequestCache_1.DEFAULT_REQUEST_CACHE_TTL, requestCacheMaxSize = RequestCache_1.DEFAULT_REQUEST_CACHE_MAX_SIZE, getDefaultRedirectUrl, }) {
451
447
  super({
452
448
  messenger,
453
449
  metadata: rampsControllerMetadata,
@@ -469,12 +465,7 @@ class RampsController extends base_controller_1.BaseController {
469
465
  */
470
466
  _RampsController_requestCacheMaxSize.set(this, void 0);
471
467
  /**
472
- * Resolves the current provider-class scope for fiat quote widening. Defaults
473
- * to `() => 'off'` (native-only) when no callback is injected.
474
- */
475
- _RampsController_getProviderScope.set(this, void 0);
476
- /**
477
- * Resolves the default redirect URL for the widened in-app quote fetch when
468
+ * Resolves the default redirect URL for the widened quote fetch when
478
469
  * the caller omits `redirectUrl`. Defaults to `() => undefined` when no
479
470
  * callback is injected.
480
471
  */
@@ -500,7 +491,6 @@ class RampsController extends base_controller_1.BaseController {
500
491
  _RampsController_initPromise.set(this, null);
501
492
  __classPrivateFieldSet(this, _RampsController_requestCacheTTL, requestCacheTTL, "f");
502
493
  __classPrivateFieldSet(this, _RampsController_requestCacheMaxSize, requestCacheMaxSize, "f");
503
- __classPrivateFieldSet(this, _RampsController_getProviderScope, getProviderScope ?? (() => 'off'), "f");
504
494
  __classPrivateFieldSet(this, _RampsController_getDefaultRedirectUrl, getDefaultRedirectUrl ?? (() => undefined), "f");
505
495
  this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
506
496
  }
@@ -1016,19 +1006,17 @@ class RampsController extends base_controller_1.BaseController {
1016
1006
  if (normalizedAssetIdForValidation === '') {
1017
1007
  throw new Error('assetId is required.');
1018
1008
  }
1019
- // When a non-`off` provider scope is active, widen the native-only
1020
- // auto-selection path to every supporting provider and pick the best in-app
1021
- // quote from the results (in-app vs external is only knowable per-quote via
1022
- // `buyWidget.browser`). Only the auto-select/restrict path that MM Pay's
1009
+ // When the all-providers feature flag is enabled, widen the native-only
1010
+ // auto-selection path to every supporting provider and pick the best
1011
+ // quote from the results. Only the auto-select/restrict path that MM Pay's
1023
1012
  // `getRampsQuote` uses is affected; explicit-`providers` callers and the
1024
- // plain all-provider path are untouched.
1025
- const providerScope = __classPrivateFieldGet(this, _RampsController_getProviderScope, "f").call(this);
1026
- const widenToInAppProviders = providerScope !== 'off' &&
1027
- !options.providers &&
1013
+ // plain all-provider path are untouched (and never read the flag).
1014
+ const wantsAutoSelection = !options.providers &&
1028
1015
  (options.autoSelectProvider === true ||
1029
1016
  options.restrictToKnownOrNativeProviders === true);
1017
+ const widenToAllProviders = wantsAutoSelection && __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_isAllProvidersEnabled).call(this);
1030
1018
  let providersToUse;
1031
- let inAppProviderCatalog = this.state.providers.data;
1019
+ let widenedProviderCatalog = this.state.providers.data;
1032
1020
  if (options.providers) {
1033
1021
  providersToUse = options.restrictToKnownOrNativeProviders
1034
1022
  ? await __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_filterProviderIdsBySupport).call(this, {
@@ -1038,7 +1026,7 @@ class RampsController extends base_controller_1.BaseController {
1038
1026
  })
1039
1027
  : options.providers;
1040
1028
  }
1041
- else if (widenToInAppProviders) {
1029
+ else if (widenToAllProviders) {
1042
1030
  // `#getSupportingProvidersForRegion` also hydrates the provider catalog
1043
1031
  // when controller state is empty, so all-provider quoting cannot silently
1044
1032
  // return zero providers here.
@@ -1046,7 +1034,7 @@ class RampsController extends base_controller_1.BaseController {
1046
1034
  assetId: normalizedAssetIdForValidation,
1047
1035
  region: regionToUse,
1048
1036
  });
1049
- inAppProviderCatalog = supporting;
1037
+ widenedProviderCatalog = supporting;
1050
1038
  providersToUse = supporting.map((provider) => provider.id);
1051
1039
  }
1052
1040
  else if (options.autoSelectProvider ||
@@ -1078,12 +1066,12 @@ class RampsController extends base_controller_1.BaseController {
1078
1066
  // Under headless-buy gating, an empty resolved provider list means no
1079
1067
  // eligible (native/supporting) provider exists. Return an empty response
1080
1068
  // rather than passing `[]` to the service, which omits the provider filter
1081
- // and would quote every provider. This also guards the widened in-app path:
1069
+ // and would quote every provider. This also guards the widened path:
1082
1070
  // a caller may trigger widening with `autoSelectProvider` alone (no
1083
1071
  // `restrictToKnownOrNativeProviders`), and an empty supporting set must not
1084
1072
  // fall through to unfiltered quotes from providers that do not support the
1085
1073
  // asset.
1086
- if ((options.restrictToKnownOrNativeProviders || widenToInAppProviders) &&
1074
+ if ((options.restrictToKnownOrNativeProviders || widenToAllProviders) &&
1087
1075
  providersToUse.length === 0) {
1088
1076
  return { success: [], sorted: [], error: [], customActions: [] };
1089
1077
  }
@@ -1092,11 +1080,12 @@ class RampsController extends base_controller_1.BaseController {
1092
1080
  const normalizedAssetId = normalizedAssetIdForValidation;
1093
1081
  const normalizedWalletAddress = options.walletAddress.trim();
1094
1082
  // The quotes API only embeds a `buyURL`/`buyWidget` when a `redirectUrl` is
1095
- // present, so on the widened in-app path (where MM Pay omits one) supply the
1083
+ // present, so on the widened path (where MM Pay omits one) supply the
1096
1084
  // injected default so aggregator quotes carry a usable widget URL. An
1097
- // explicit caller `redirectUrl` always wins, and scope `off` never injects.
1085
+ // explicit caller `redirectUrl` always wins, and the native-only path
1086
+ // (flag off) never injects.
1098
1087
  const effectiveRedirectUrl = options.redirectUrl ??
1099
- (widenToInAppProviders ? __classPrivateFieldGet(this, _RampsController_getDefaultRedirectUrl, "f").call(this) : undefined);
1088
+ (widenToAllProviders ? __classPrivateFieldGet(this, _RampsController_getDefaultRedirectUrl, "f").call(this) : undefined);
1100
1089
  const cacheKey = (0, RequestCache_1.createCacheKey)('getQuotes', [
1101
1090
  normalizedRegion,
1102
1091
  normalizedFiat,
@@ -1125,22 +1114,21 @@ class RampsController extends base_controller_1.BaseController {
1125
1114
  forceRefresh: options.forceRefresh,
1126
1115
  ttl: options.ttl ?? DEFAULT_QUOTES_TTL,
1127
1116
  });
1128
- if (!widenToInAppProviders) {
1117
+ if (!widenToAllProviders) {
1129
1118
  return response;
1130
1119
  }
1131
- // Reduce the widened multi-provider result to the single best in-app quote
1120
+ // Reduce the widened multi-provider result to the single best quote
1132
1121
  // and place it at `success[0]`, since single-pick consumers
1133
1122
  // (`getRampsQuote` -> `success?.[0]`) rely on index 0 while `success[]`
1134
1123
  // order is server-defined rather than ranked.
1135
- const selectedQuote = __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_pickInAppQuote).call(this, response, {
1136
- scope: providerScope,
1124
+ const selectedQuote = __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_pickWidenedQuote).call(this, response, {
1137
1125
  amount: options.amount,
1138
1126
  fiat: normalizedFiat,
1139
- providers: inAppProviderCatalog,
1127
+ providers: widenedProviderCatalog,
1140
1128
  });
1141
1129
  if (!selectedQuote) {
1142
- // No usable in-app quote: surface "no quote" rather than leaking an
1143
- // external/custom quote to the single-pick consumer.
1130
+ // No quote fits the published provider limits: surface "no quote"
1131
+ // rather than handing the single-pick consumer an out-of-limits quote.
1144
1132
  return {
1145
1133
  success: [],
1146
1134
  sorted: response.sorted,
@@ -1253,7 +1241,7 @@ class RampsController extends base_controller_1.BaseController {
1253
1241
  *
1254
1242
  * @param params - Object containing order identifiers and wallet info.
1255
1243
  * @param params.orderId - Full order ID (e.g. "/providers/paypal/orders/abc123") or order code.
1256
- * @param params.providerCode - Provider code (e.g. "paypal", "transak"), with or without /providers/ prefix.
1244
+ * @param params.providerCode - Canonical provider code (e.g. "paypal", "transak").
1257
1245
  * @param params.walletAddress - Wallet address for the order.
1258
1246
  * @param params.chainId - Optional chain ID for the order.
1259
1247
  */
@@ -1263,11 +1251,10 @@ class RampsController extends base_controller_1.BaseController {
1263
1251
  if (!orderCode?.trim()) {
1264
1252
  return;
1265
1253
  }
1266
- const normalizedProviderCode = normalizeProviderCode(providerCode);
1267
1254
  const stubOrder = {
1268
1255
  providerOrderId: orderCode,
1269
1256
  provider: {
1270
- id: `/providers/${normalizedProviderCode}`,
1257
+ id: providerCode,
1271
1258
  name: '',
1272
1259
  environmentType: '',
1273
1260
  description: '',
@@ -1771,7 +1758,7 @@ class RampsController extends base_controller_1.BaseController {
1771
1758
  }
1772
1759
  }
1773
1760
  exports.RampsController = RampsController;
1774
- _RampsController_requestCacheTTL = new WeakMap(), _RampsController_requestCacheMaxSize = new WeakMap(), _RampsController_getProviderScope = new WeakMap(), _RampsController_getDefaultRedirectUrl = new WeakMap(), _RampsController_pendingRequests = new WeakMap(), _RampsController_pendingResourceCount = new WeakMap(), _RampsController_pendingResourceGeneration = new WeakMap(), _RampsController_orderPollingMeta = new WeakMap(), _RampsController_orderPollingTimer = new WeakMap(), _RampsController_isPolling = new WeakMap(), _RampsController_initPromise = new WeakMap(), _RampsController_instances = new WeakSet(), _RampsController_clearPendingResourceCountForDependentResources = function _RampsController_clearPendingResourceCountForDependentResources() {
1761
+ _RampsController_requestCacheTTL = new WeakMap(), _RampsController_requestCacheMaxSize = new WeakMap(), _RampsController_getDefaultRedirectUrl = new WeakMap(), _RampsController_pendingRequests = new WeakMap(), _RampsController_pendingResourceCount = new WeakMap(), _RampsController_pendingResourceGeneration = new WeakMap(), _RampsController_orderPollingMeta = new WeakMap(), _RampsController_orderPollingTimer = new WeakMap(), _RampsController_isPolling = new WeakMap(), _RampsController_initPromise = new WeakMap(), _RampsController_instances = new WeakSet(), _RampsController_clearPendingResourceCountForDependentResources = function _RampsController_clearPendingResourceCountForDependentResources() {
1775
1762
  for (const resourceType of DEPENDENT_RESOURCE_KEYS) {
1776
1763
  __classPrivateFieldGet(this, _RampsController_pendingResourceCount, "f").delete(resourceType);
1777
1764
  const generation = __classPrivateFieldGet(this, _RampsController_pendingResourceGeneration, "f").get(resourceType) ?? 0;
@@ -1786,6 +1773,14 @@ _RampsController_requestCacheTTL = new WeakMap(), _RampsController_requestCacheM
1786
1773
  __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_removeRequestState).call(this, cacheKey);
1787
1774
  }
1788
1775
  }
1776
+ }, _RampsController_isAllProvidersEnabled = function _RampsController_isAllProvidersEnabled() {
1777
+ try {
1778
+ const remoteFeatureFlagState = this.messenger.call('RemoteFeatureFlagController:getState');
1779
+ return (0, featureFlags_1.isHeadlessAllProvidersEnabled)(remoteFeatureFlagState);
1780
+ }
1781
+ catch {
1782
+ return false;
1783
+ }
1789
1784
  }, _RampsController_mutateRequests = function _RampsController_mutateRequests(fn) {
1790
1785
  this.update((state) => {
1791
1786
  const requests = state.requests;
@@ -1897,14 +1892,10 @@ _RampsController_requestCacheTTL = new WeakMap(), _RampsController_requestCacheM
1897
1892
  this.update((state) => {
1898
1893
  state.userRegion = userRegion;
1899
1894
  });
1900
- }, _RampsController_pickInAppQuote = function _RampsController_pickInAppQuote(response, { scope, amount, fiat, providers, }) {
1901
- const providerByCode = new Map(providers.map((provider) => [
1902
- normalizeProviderCode(provider.id),
1903
- provider,
1904
- ]));
1905
- const customActionProviderCodes = new Set(response.customActions.map((action) => normalizeProviderCode(action.buy.providerId)));
1895
+ }, _RampsController_pickWidenedQuote = function _RampsController_pickWidenedQuote(response, { amount, fiat, providers, }) {
1896
+ const providerByCode = new Map(providers.map((provider) => [provider.id, provider]));
1906
1897
  const fitsProviderLimits = (quote) => {
1907
- const provider = providerByCode.get(normalizeProviderCode(quote.provider));
1898
+ const provider = providerByCode.get(quote.provider);
1908
1899
  const limit = provider?.limits?.fiat?.[fiat]?.[quote.quote.paymentMethod];
1909
1900
  if (!limit) {
1910
1901
  // No published limits for this provider/payment method: treat as
@@ -1913,37 +1904,18 @@ _RampsController_requestCacheTTL = new WeakMap(), _RampsController_requestCacheM
1913
1904
  }
1914
1905
  return amount >= limit.minAmount && amount <= limit.maxAmount;
1915
1906
  };
1916
- const isEligible = (quote) => {
1917
- // `all` (Phase 2) skips the in-app-only exclusions; both scopes still
1918
- // enforce provider limits up front.
1919
- if (scope !== 'all') {
1920
- const providerCode = normalizeProviderCode(quote.provider);
1921
- if (customActionProviderCodes.has(providerCode)) {
1922
- return false;
1923
- }
1924
- // Defensive: the wire may carry an inline `isCustomAction` flag that is
1925
- // absent from the published `Quote` type.
1926
- if (quote.quote.isCustomAction === true) {
1927
- return false;
1928
- }
1929
- if (quote.quote.buyWidget?.browser === 'IN_APP_OS_BROWSER') {
1930
- return false;
1931
- }
1932
- }
1933
- return fitsProviderLimits(quote);
1934
- };
1935
- const candidates = response.success.filter(isEligible);
1907
+ const candidates = response.success.filter(fitsProviderLimits);
1936
1908
  if (candidates.length === 0) {
1937
1909
  return undefined;
1938
1910
  }
1939
- const candidateByCode = new Map(candidates.map((quote) => [normalizeProviderCode(quote.provider), quote]));
1911
+ const candidateByCode = new Map(candidates.map((quote) => [quote.provider, quote]));
1940
1912
  const pickBySortOrder = (sortBy) => {
1941
1913
  const order = response.sorted.find((entry) => entry.sortBy === sortBy)?.ids;
1942
1914
  if (!order) {
1943
1915
  return undefined;
1944
1916
  }
1945
1917
  for (const providerId of order) {
1946
- const match = candidateByCode.get(normalizeProviderCode(providerId));
1918
+ const match = candidateByCode.get(providerId);
1947
1919
  if (match) {
1948
1920
  return match;
1949
1921
  }
@@ -1977,18 +1949,10 @@ async function _RampsController_getSupportingProvidersForRegion({ assetId, regio
1977
1949
  else {
1978
1950
  ({ providers } = await this.getProviders(normalizedRegion));
1979
1951
  }
1980
- // EVM CAIP-19 asset IDs may arrive checksummed or lowercased, and the
1981
- // providers API returns both forms, so match case-insensitively on both
1982
- // sides. Only the lowercased forms are compared (the original IDs are never
1983
- // returned), so case-sensitive non-EVM asset IDs are not corrupted.
1984
- const normalizedAssetId = assetId.toLowerCase();
1985
- const supporting = providers.filter((provider) => {
1986
- const map = provider?.supportedCryptoCurrencies;
1987
- if (!map) {
1988
- return false;
1989
- }
1990
- return Object.keys(map).some((key) => key.toLowerCase() === normalizedAssetId);
1991
- });
1952
+ // Case-insensitive CAIP-19 matching is shared with headless-buy consumers
1953
+ // via `getProvidersServingAsset`, so the controller and the UI region gate
1954
+ // cannot disagree about which providers serve the asset.
1955
+ const supporting = (0, providerAvailability_1.getProvidersServingAsset)(providers, assetId);
1992
1956
  return { supporting, all: providers };
1993
1957
  }, _RampsController_filterProviderIdsBySupport =
1994
1958
  /**