@metamask/ramps-controller 19.0.0 → 20.1.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 (56) hide show
  1. package/CHANGELOG.md +37 -1
  2. package/dist/RampsController-method-action-types.cjs.map +1 -1
  3. package/dist/RampsController-method-action-types.d.cts +45 -2
  4. package/dist/RampsController-method-action-types.d.cts.map +1 -1
  5. package/dist/RampsController-method-action-types.d.mts +45 -2
  6. package/dist/RampsController-method-action-types.d.mts.map +1 -1
  7. package/dist/RampsController-method-action-types.mjs.map +1 -1
  8. package/dist/RampsController.cjs +230 -20
  9. package/dist/RampsController.cjs.map +1 -1
  10. package/dist/RampsController.d.cts +78 -9
  11. package/dist/RampsController.d.cts.map +1 -1
  12. package/dist/RampsController.d.mts +78 -9
  13. package/dist/RampsController.d.mts.map +1 -1
  14. package/dist/RampsController.mjs +229 -19
  15. package/dist/RampsController.mjs.map +1 -1
  16. package/dist/RampsService.cjs +4 -1
  17. package/dist/RampsService.cjs.map +1 -1
  18. package/dist/RampsService.d.cts +15 -3
  19. package/dist/RampsService.d.cts.map +1 -1
  20. package/dist/RampsService.d.mts +15 -3
  21. package/dist/RampsService.d.mts.map +1 -1
  22. package/dist/RampsService.mjs +4 -1
  23. package/dist/RampsService.mjs.map +1 -1
  24. package/dist/index.cjs +5 -1
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.d.cts +5 -4
  27. package/dist/index.d.cts.map +1 -1
  28. package/dist/index.d.mts +5 -4
  29. package/dist/index.d.mts.map +1 -1
  30. package/dist/index.mjs +2 -1
  31. package/dist/index.mjs.map +1 -1
  32. package/dist/orderStatus.cjs +34 -0
  33. package/dist/orderStatus.cjs.map +1 -0
  34. package/dist/orderStatus.d.cts +18 -0
  35. package/dist/orderStatus.d.cts.map +1 -0
  36. package/dist/orderStatus.d.mts +18 -0
  37. package/dist/orderStatus.d.mts.map +1 -0
  38. package/dist/orderStatus.mjs +30 -0
  39. package/dist/orderStatus.mjs.map +1 -0
  40. package/dist/paymentMethodMerge.cjs +51 -0
  41. package/dist/paymentMethodMerge.cjs.map +1 -0
  42. package/dist/paymentMethodMerge.d.cts +26 -0
  43. package/dist/paymentMethodMerge.d.cts.map +1 -0
  44. package/dist/paymentMethodMerge.d.mts +26 -0
  45. package/dist/paymentMethodMerge.d.mts.map +1 -0
  46. package/dist/paymentMethodMerge.mjs +46 -0
  47. package/dist/paymentMethodMerge.mjs.map +1 -0
  48. package/dist/providerAvailability.cjs +39 -8
  49. package/dist/providerAvailability.cjs.map +1 -1
  50. package/dist/providerAvailability.d.cts +30 -5
  51. package/dist/providerAvailability.d.cts.map +1 -1
  52. package/dist/providerAvailability.d.mts +30 -5
  53. package/dist/providerAvailability.d.mts.map +1 -1
  54. package/dist/providerAvailability.mjs +37 -7
  55. package/dist/providerAvailability.mjs.map +1 -1
  56. package/package.json +3 -3
@@ -9,11 +9,13 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
9
9
  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");
10
10
  return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
11
11
  };
12
- var _RampsController_instances, _RampsController_requestCacheTTL, _RampsController_requestCacheMaxSize, _RampsController_pendingRequests, _RampsController_pendingResourceCount, _RampsController_pendingResourceGeneration, _RampsController_orderPollingMeta, _RampsController_orderPollingTimer, _RampsController_isPolling, _RampsController_initPromise, _RampsController_clearPendingResourceCountForDependentResources, _RampsController_abortDependentRequests, _RampsController_resolveAllProvidersFlag, _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;
12
+ var _RampsController_instances, _RampsController_requestCacheTTL, _RampsController_requestCacheMaxSize, _RampsController_pendingRequests, _RampsController_pendingResourceCount, _RampsController_pendingResourceGeneration, _RampsController_orderPollingMeta, _RampsController_orderPollingTimer, _RampsController_isPolling, _RampsController_initPromise, _RampsController_clearPendingResourceCountForDependentResources, _RampsController_abortDependentRequests, _RampsController_resolveAllProvidersFlag, _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_resolveProviderIdsForPaymentMethods, _RampsController_getPreferredProviderIdsFromOrders, _RampsController_refreshOrder, _RampsController_pollPendingOrders, _RampsController_syncTransakAuthOnError, _RampsController_getNormalizedTransakError;
13
13
  import { BaseController } from "@metamask/base-controller";
14
14
  import { BrokenCircuitError } from "@metamask/controller-utils";
15
15
  import { getHeadlessProviderAllowlist, isHeadlessAllProvidersEnabled, normalizeHeadlessProviderId } from "./featureFlags.mjs";
16
- import { getProvidersServingAsset, providerServesAsset } from "./providerAvailability.mjs";
16
+ import { PENDING_ORDER_STATUSES, TERMINAL_ORDER_STATUSES } from "./orderStatus.mjs";
17
+ import { mergePaymentMethodsById, pickPaymentMethod } from "./paymentMethodMerge.mjs";
18
+ import { getProvidersServingAsset, normalizeRampsAssetId, providerServesAsset } from "./providerAvailability.mjs";
17
19
  import { RAMPS_ERROR_CODES } from "./rampsErrorCodes.mjs";
18
20
  import { RampsOrderStatus } from "./RampsService.mjs";
19
21
  import { DEFAULT_REQUEST_CACHE_TTL, DEFAULT_REQUEST_CACHE_MAX_SIZE, createCacheKey, isCacheExpired, createLoadingState, createSuccessState, createErrorState, RequestStatus } from "./RequestCache.mjs";
@@ -350,18 +352,6 @@ export function getInternalOrderCode(orderOrId) {
350
352
  return providerOrderId;
351
353
  }
352
354
  // === ORDER POLLING CONSTANTS ===
353
- const TERMINAL_ORDER_STATUSES = new Set([
354
- RampsOrderStatus.Completed,
355
- RampsOrderStatus.Failed,
356
- RampsOrderStatus.Cancelled,
357
- RampsOrderStatus.IdExpired,
358
- ]);
359
- const PENDING_ORDER_STATUSES = new Set([
360
- RampsOrderStatus.Pending,
361
- RampsOrderStatus.Created,
362
- RampsOrderStatus.Unknown,
363
- RampsOrderStatus.Precreated,
364
- ]);
365
355
  const DEFAULT_POLLING_INTERVAL_MS = 30000;
366
356
  const MAX_ERROR_COUNT = 5;
367
357
  // === CONTROLLER DEFINITION ===
@@ -378,6 +368,7 @@ const MESSENGER_EXPOSED_METHODS = [
378
368
  'setSelectedToken',
379
369
  'getProviders',
380
370
  'getPaymentMethods',
371
+ 'getPaymentMethodsForContext',
381
372
  'setSelectedPaymentMethod',
382
373
  'getQuotes',
383
374
  'addOrder',
@@ -417,8 +408,25 @@ const MESSENGER_EXPOSED_METHODS = [
417
408
  'transakGetActiveOrders',
418
409
  ];
419
410
  /**
420
- * Manages cryptocurrency on/off ramps functionality.
411
+ * Whether controller state still describes the context a payment-method
412
+ * request was issued for, so a completed request may write the Buy catalog.
413
+ *
414
+ * Compared at commit time rather than against a snapshot, so an older request
415
+ * that returns after the context moved on is dropped.
416
+ *
417
+ * @param state - Controller state at commit time.
418
+ * @param context - The context the request was issued for.
419
+ * @param context.region - Normalized region code.
420
+ * @param context.assetId - Canonicalized CAIP-19 asset id.
421
+ * @param context.providerId - Trimmed provider id, or an empty string.
422
+ * @returns Whether the write may proceed.
421
423
  */
424
+ function contextStillMatches(state, context) {
425
+ return (state.userRegion?.regionCode?.trim().toLowerCase() === context.region &&
426
+ normalizeRampsAssetId(state.tokens.selected?.assetId ?? '') ===
427
+ context.assetId &&
428
+ (state.providers.selected?.id.trim() ?? '') === context.providerId);
429
+ }
422
430
  export class RampsController extends BaseController {
423
431
  /**
424
432
  * Clears the pending resource count map. Used only in tests to exercise the
@@ -891,7 +899,7 @@ export class RampsController extends BaseController {
891
899
  options?.crypto,
892
900
  options?.payments,
893
901
  ]);
894
- const { providers } = await this.executeRequest(cacheKey, async () => {
902
+ const response = await this.executeRequest(cacheKey, async () => {
895
903
  return this.messenger.call('RampsService:getProviders', normalizedRegion, {
896
904
  provider: options?.provider,
897
905
  crypto: options?.crypto,
@@ -902,13 +910,14 @@ export class RampsController extends BaseController {
902
910
  resourceType: 'providers',
903
911
  isResultCurrent: () => __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_isRegionCurrent).call(this, normalizedRegion),
904
912
  });
913
+ const { providers } = response;
905
914
  this.update((state) => {
906
915
  const userRegionCode = state.userRegion?.regionCode;
907
916
  if (userRegionCode === undefined || userRegionCode === normalizedRegion) {
908
917
  state.providers.data = providers;
909
918
  }
910
919
  });
911
- return { providers };
920
+ return response;
912
921
  }
913
922
  /**
914
923
  * Fetches the list of payment methods for a given context.
@@ -965,6 +974,146 @@ export class RampsController extends BaseController {
965
974
  });
966
975
  return response;
967
976
  }
977
+ /**
978
+ * Fetches payment methods for a quoting context without coupling callers to
979
+ * the Buy flow's globally selected provider/token catalog.
980
+ *
981
+ * Provider contribution mirrors {@link getQuotes}:
982
+ * - explicit `providers` (optionally filtered when
983
+ * `restrictToKnownOrNativeProviders` is set)
984
+ * - auto-select / restrict path, including `moneyHeadlessAllProviders`
985
+ * widening: flag off uses the restricted/native resolver; flag on uses
986
+ * supporting providers, intersected with the flag allowlist when that
987
+ * allowlist is non-empty (pick-survivor set for picker methods)
988
+ * - when those resolution flags and `providers` are omitted, uses only
989
+ * `providers.selected` (UB2 selected-provider context)
990
+ *
991
+ * By default this is request-only: it does **not** mutate
992
+ * `paymentMethods.data` or `paymentMethods.selected`. Pass `updateState:
993
+ * true` only when the caller explicitly wants Buy-catalog write semantics
994
+ * (UB2). Headless / MM Pay selection stays TPC-owned. `updateState: true`
995
+ * throws when the resolved provider set holds more than one provider, because
996
+ * the write guards cannot tell two such requests apart.
997
+ *
998
+ * Methods are request-eligible for the resolved provider set; they are not
999
+ * guaranteed to produce a quote for every amount (provider fiat limits still
1000
+ * apply at quote time).
1001
+ *
1002
+ * @param options - Context for the payment-method fetch.
1003
+ * @param options.region - Region code. Defaults to `userRegion`.
1004
+ * @param options.assetId - Required CAIP-19 quoting asset.
1005
+ * @param options.providers - Explicit provider ids.
1006
+ * @param options.autoSelectProvider - Resolve providers like `getQuotes`.
1007
+ * @param options.preferredProviderIds - Preferred ids for auto-selection.
1008
+ * @param options.restrictToKnownOrNativeProviders - Headless gating.
1009
+ * @param options.updateState - When true, write `paymentMethods` state.
1010
+ * @param options.preferPaymentMethodId - Preserve this id when still present.
1011
+ * @param options.forceRefresh - Bypass request cache for provider fetches.
1012
+ * @param options.ttl - Custom TTL for provider payment-method fetches.
1013
+ * @returns Deduped methods, a request-only suggested selection, and the
1014
+ * provider ids that contributed.
1015
+ */
1016
+ async getPaymentMethodsForContext(options) {
1017
+ const regionToUse = options.region ?? __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_requireRegion).call(this);
1018
+ const normalizedRegion = regionToUse.toLowerCase().trim();
1019
+ const assetId = options.assetId.trim();
1020
+ if (assetId === '') {
1021
+ throw new Error('assetId is required.');
1022
+ }
1023
+ const normalizedAssetContext = normalizeRampsAssetId(assetId);
1024
+ const updateState = options.updateState === true;
1025
+ const providerIdForState = options.providers?.length === 1
1026
+ ? options.providers[0].trim()
1027
+ : (this.state.providers.selected?.id.trim() ?? '');
1028
+ const providerIds = await __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_resolveProviderIdsForPaymentMethods).call(this, {
1029
+ assetId,
1030
+ region: normalizedRegion,
1031
+ providers: options.providers,
1032
+ autoSelectProvider: options.autoSelectProvider,
1033
+ preferredProviderIds: options.preferredProviderIds,
1034
+ restrictToKnownOrNativeProviders: options.restrictToKnownOrNativeProviders,
1035
+ });
1036
+ // A fan-out across several providers produces a merged catalog whose value
1037
+ // depends on the provider set, but the write guards below only compare
1038
+ // region, selected token, and selected provider. Two concurrent
1039
+ // multi-provider requests share all three, so nothing distinguishes them
1040
+ // and the slower one would silently overwrite the faster one.
1041
+ if (updateState && providerIds.length > 1) {
1042
+ throw new Error(`getPaymentMethodsForContext cannot write paymentMethods state for ${providerIds.length} resolved providers. Use updateState: false, or request exactly one provider.`);
1043
+ }
1044
+ const writeContext = {
1045
+ region: normalizedRegion,
1046
+ assetId: normalizedAssetContext,
1047
+ providerId: providerIdForState,
1048
+ };
1049
+ if (providerIds.length === 0) {
1050
+ if (updateState) {
1051
+ this.update((state) => {
1052
+ if (contextStillMatches(state, writeContext)) {
1053
+ state.paymentMethods.data = [];
1054
+ state.paymentMethods.selected = null;
1055
+ }
1056
+ });
1057
+ }
1058
+ return { methods: [], selected: null, providerIds };
1059
+ }
1060
+ const settled = await Promise.allSettled(providerIds.map(async (providerId) => {
1061
+ const cacheKey = createCacheKey('getPaymentMethodsForContext', [
1062
+ normalizedRegion,
1063
+ assetId,
1064
+ providerId,
1065
+ ]);
1066
+ return this.executeRequest(cacheKey, async () => {
1067
+ return this.messenger.call('RampsService:getPaymentMethods', {
1068
+ region: normalizedRegion,
1069
+ assetId,
1070
+ provider: providerId,
1071
+ });
1072
+ }, {
1073
+ forceRefresh: options.forceRefresh,
1074
+ ttl: options.ttl,
1075
+ // Intentionally omit resourceType / isResultCurrent so this
1076
+ // request-only path never drives Buy paymentMethods loading or
1077
+ // selection state unless `updateState` is explicitly set below.
1078
+ });
1079
+ }));
1080
+ const successfulLists = [];
1081
+ const failures = [];
1082
+ for (const result of settled) {
1083
+ if (result.status === 'fulfilled') {
1084
+ successfulLists.push(result.value.payments);
1085
+ }
1086
+ else {
1087
+ failures.push(result.reason);
1088
+ }
1089
+ }
1090
+ if (successfulLists.length === 0) {
1091
+ const firstFailure = failures[0];
1092
+ throw firstFailure instanceof Error
1093
+ ? firstFailure
1094
+ : new Error('Failed to fetch payment methods for context.');
1095
+ }
1096
+ const methods = mergePaymentMethodsById(successfulLists);
1097
+ let selected = pickPaymentMethod(methods, [
1098
+ options.preferPaymentMethodId,
1099
+ this.state.paymentMethods.selected?.id,
1100
+ ]);
1101
+ if (updateState) {
1102
+ this.update((state) => {
1103
+ if (contextStillMatches(state, writeContext)) {
1104
+ // The stored selection outranks the caller's preference: the user may
1105
+ // have picked a method while this request was in flight.
1106
+ selected = pickPaymentMethod(methods, [
1107
+ state.paymentMethods.selected?.id,
1108
+ options.preferPaymentMethodId,
1109
+ ]);
1110
+ state.paymentMethods.data = methods;
1111
+ state.paymentMethods.selected = selected;
1112
+ }
1113
+ });
1114
+ }
1115
+ return { methods, selected, providerIds };
1116
+ }
968
1117
  /**
969
1118
  * Sets the user's selected payment method.
970
1119
  *
@@ -1289,7 +1438,7 @@ export class RampsController extends BaseController {
1289
1438
  * @param params.orderId - Full order ID (e.g. "/providers/paypal/orders/abc123") or order code.
1290
1439
  * @param params.providerCode - Canonical provider code (e.g. "paypal", "transak").
1291
1440
  * @param params.walletAddress - Wallet address for the order.
1292
- * @param params.chainId - Optional chain ID for the order.
1441
+ * @param params.chainId - Chain ID for the order (decimal, hex, or CAIP-2). Must be non-empty.
1293
1442
  */
1294
1443
  addPrecreatedOrder(params) {
1295
1444
  const { orderId, providerCode, walletAddress, chainId } = params;
@@ -1297,6 +1446,9 @@ export class RampsController extends BaseController {
1297
1446
  if (!orderCode?.trim()) {
1298
1447
  return;
1299
1448
  }
1449
+ if (!chainId.trim()) {
1450
+ return;
1451
+ }
1300
1452
  const stubOrder = {
1301
1453
  providerOrderId: orderCode,
1302
1454
  provider: {
@@ -1319,7 +1471,7 @@ export class RampsController extends BaseController {
1319
1471
  providerOrderLink: '',
1320
1472
  totalFeesFiat: 0,
1321
1473
  txHash: '',
1322
- network: chainId ? { chainId, name: '' } : { chainId: '', name: '' },
1474
+ network: { chainId, name: '' },
1323
1475
  canBeUpdated: true,
1324
1476
  idHasExpired: false,
1325
1477
  excludeFromPurchases: false,
@@ -2114,6 +2266,64 @@ async function _RampsController_resolveProviderIdsForQuote({ assetId, region, pr
2114
2266
  return [];
2115
2267
  }
2116
2268
  return [supporting[0].id];
2269
+ }, _RampsController_resolveProviderIdsForPaymentMethods =
2270
+ /**
2271
+ * Resolves provider IDs that should contribute payment methods for a
2272
+ * quoting context. Mirrors {@link getQuotes} provider-set selection, with
2273
+ * one intentional difference on the widened path: when the all-providers
2274
+ * flag allowlist is non-empty, returns supporting providers intersected
2275
+ * with that allowlist (pick survivors for the picker). Does not mutate
2276
+ * state.
2277
+ *
2278
+ * @param options - Resolution inputs aligned with `getQuotes`.
2279
+ * @param options.assetId - CAIP-19 asset type identifier to resolve for.
2280
+ * @param options.region - Region to resolve providers for.
2281
+ * @param options.providers - Explicit provider IDs, when provided.
2282
+ * @param options.autoSelectProvider - Resolve providers like `getQuotes`.
2283
+ * @param options.preferredProviderIds - Preferred provider IDs in order.
2284
+ * @param options.restrictToKnownOrNativeProviders - Headless gating.
2285
+ * @returns Provider IDs for this request only.
2286
+ */
2287
+ async function _RampsController_resolveProviderIdsForPaymentMethods({ assetId, region, providers, autoSelectProvider, preferredProviderIds, restrictToKnownOrNativeProviders, }) {
2288
+ const wantsAutoSelection = !providers &&
2289
+ (autoSelectProvider === true ||
2290
+ restrictToKnownOrNativeProviders === true);
2291
+ const { enabled: allProvidersEnabled, allowlist: providerAllowlist } = wantsAutoSelection
2292
+ ? __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_resolveAllProvidersFlag).call(this)
2293
+ : { enabled: false, allowlist: undefined };
2294
+ const widenToAllProviders = wantsAutoSelection && allProvidersEnabled;
2295
+ if (providers) {
2296
+ return restrictToKnownOrNativeProviders
2297
+ ? __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_filterProviderIdsBySupport).call(this, {
2298
+ providerIds: providers,
2299
+ assetId,
2300
+ region,
2301
+ })
2302
+ : providers;
2303
+ }
2304
+ if (widenToAllProviders) {
2305
+ const { supporting } = await __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_getSupportingProvidersForRegion).call(this, {
2306
+ assetId,
2307
+ region,
2308
+ });
2309
+ if (providerAllowlist && providerAllowlist.length > 0) {
2310
+ const allowedProviderIds = new Set(providerAllowlist.map(normalizeHeadlessProviderId));
2311
+ return supporting
2312
+ .filter((provider) => allowedProviderIds.has(normalizeHeadlessProviderId(provider.id)))
2313
+ .map((provider) => provider.id);
2314
+ }
2315
+ return supporting.map((provider) => provider.id);
2316
+ }
2317
+ if (autoSelectProvider || restrictToKnownOrNativeProviders) {
2318
+ return __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_resolveProviderIdsForQuote).call(this, {
2319
+ assetId,
2320
+ region,
2321
+ preferredProviderIds,
2322
+ restrictToKnownOrNative: restrictToKnownOrNativeProviders,
2323
+ });
2324
+ }
2325
+ const selectedId = this.state.providers.selected?.id;
2326
+ return selectedId ? [selectedId] : [];
2117
2327
  }, _RampsController_getPreferredProviderIdsFromOrders = function _RampsController_getPreferredProviderIdsFromOrders() {
2118
2328
  const orderedIds = [];
2119
2329
  const completedOrders = this.state.orders