@metamask/ramps-controller 20.3.0 → 21.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -1
- package/dist/{NeoBankService-method-action-types.d.mts → NeoBankService-method-action-types.d.ts} +2 -2
- package/dist/NeoBankService-method-action-types.d.ts.map +1 -0
- package/dist/{NeoBankService-method-action-types.mjs → NeoBankService-method-action-types.js} +1 -1
- package/dist/NeoBankService-method-action-types.js.map +1 -0
- package/dist/{NeoBankService.d.cts → NeoBankService.d.ts} +8 -8
- package/dist/NeoBankService.d.ts.map +1 -0
- package/dist/NeoBankService.js +380 -0
- package/dist/NeoBankService.js.map +1 -0
- package/dist/{RampsController-method-action-types.d.mts → RampsController-method-action-types.d.ts} +2 -2
- package/dist/RampsController-method-action-types.d.ts.map +1 -0
- package/dist/{TransakService-method-action-types.mjs → RampsController-method-action-types.js} +1 -1
- package/dist/RampsController-method-action-types.js.map +1 -0
- package/dist/{RampsController.d.cts → RampsController.d.ts} +19 -19
- package/dist/RampsController.d.ts.map +1 -0
- package/dist/{RampsController.mjs → RampsController.js} +725 -613
- package/dist/RampsController.js.map +1 -0
- package/dist/{RampsService-method-action-types.d.cts → RampsService-method-action-types.d.ts} +2 -2
- package/dist/RampsService-method-action-types.d.ts.map +1 -0
- package/dist/{RampsService-method-action-types.mjs → RampsService-method-action-types.js} +1 -1
- package/dist/RampsService-method-action-types.js.map +1 -0
- package/dist/{RampsService.d.mts → RampsService.d.ts} +5 -5
- package/dist/RampsService.d.ts.map +1 -0
- package/dist/{RampsService.mjs → RampsService.js} +159 -147
- package/dist/RampsService.js.map +1 -0
- package/dist/{RequestCache.d.mts → RequestCache.d.ts} +3 -3
- package/dist/RequestCache.d.ts.map +1 -0
- package/dist/{RequestCache.mjs → RequestCache.js} +1 -1
- package/dist/RequestCache.js.map +1 -0
- package/dist/{TransakService-method-action-types.d.cts → TransakService-method-action-types.d.ts} +2 -2
- package/dist/TransakService-method-action-types.d.ts.map +1 -0
- package/dist/{RampsController-method-action-types.mjs → TransakService-method-action-types.js} +1 -1
- package/dist/TransakService-method-action-types.js.map +1 -0
- package/dist/{TransakService.d.cts → TransakService.d.ts} +6 -6
- package/dist/TransakService.d.ts.map +1 -0
- package/dist/TransakService.js +746 -0
- package/dist/TransakService.js.map +1 -0
- package/dist/{autoramp-types.d.cts → autoramp-types.d.ts} +1 -1
- package/dist/autoramp-types.d.ts.map +1 -0
- package/dist/autoramp-types.js +2 -0
- package/dist/autoramp-types.js.map +1 -0
- package/dist/{autorampAccount.d.mts → autorampAccount.d.ts} +3 -3
- package/dist/autorampAccount.d.ts.map +1 -0
- package/dist/{autorampAccount.mjs → autorampAccount.js} +1 -1
- package/dist/autorampAccount.js.map +1 -0
- package/dist/{client-identity.d.mts → client-identity.d.ts} +1 -1
- package/dist/client-identity.d.ts.map +1 -0
- package/dist/{client-identity.mjs → client-identity.js} +1 -1
- package/dist/client-identity.js.map +1 -0
- package/dist/{errorNormalization.d.mts → errorNormalization.d.ts} +1 -1
- package/dist/errorNormalization.d.ts.map +1 -0
- package/dist/{errorNormalization.mjs → errorNormalization.js} +1 -1
- package/dist/errorNormalization.js.map +1 -0
- package/dist/{featureFlags.d.mts → featureFlags.d.ts} +2 -2
- package/dist/featureFlags.d.ts.map +1 -0
- package/dist/{featureFlags.mjs → featureFlags.js} +1 -1
- package/dist/featureFlags.js.map +1 -0
- package/dist/{index.d.mts → index.d.ts} +35 -35
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/{orderStatus.d.mts → orderStatus.d.ts} +2 -2
- package/dist/orderStatus.d.ts.map +1 -0
- package/dist/{orderStatus.mjs → orderStatus.js} +2 -2
- package/dist/orderStatus.js.map +1 -0
- package/dist/{ownership-message.d.cts → ownership-message.d.ts} +1 -1
- package/dist/ownership-message.d.ts.map +1 -0
- package/dist/{ownership-message.mjs → ownership-message.js} +1 -1
- package/dist/ownership-message.js.map +1 -0
- package/dist/{paymentMethodMerge.d.cts → paymentMethodMerge.d.ts} +2 -2
- package/dist/paymentMethodMerge.d.ts.map +1 -0
- package/dist/{paymentMethodMerge.mjs → paymentMethodMerge.js} +1 -1
- package/dist/paymentMethodMerge.js.map +1 -0
- package/dist/{providerAvailability.d.cts → providerAvailability.d.ts} +2 -2
- package/dist/providerAvailability.d.ts.map +1 -0
- package/dist/{providerAvailability.mjs → providerAvailability.js} +1 -1
- package/dist/providerAvailability.js.map +1 -0
- package/dist/{quoteClassification.d.cts → quoteClassification.d.ts} +2 -2
- package/dist/quoteClassification.d.ts.map +1 -0
- package/dist/{quoteClassification.mjs → quoteClassification.js} +1 -1
- package/dist/quoteClassification.js.map +1 -0
- package/dist/{rampsErrorCodes.d.cts → rampsErrorCodes.d.ts} +2 -2
- package/dist/rampsErrorCodes.d.ts.map +1 -0
- package/dist/{rampsErrorCodes.mjs → rampsErrorCodes.js} +1 -1
- package/dist/rampsErrorCodes.js.map +1 -0
- package/dist/{selectors.d.mts → selectors.d.ts} +3 -3
- package/dist/selectors.d.ts.map +1 -0
- package/dist/{selectors.mjs → selectors.js} +2 -2
- package/dist/selectors.js.map +1 -0
- package/dist/{transakApiErrorUtils.d.cts → transakApiErrorUtils.d.ts} +1 -1
- package/dist/transakApiErrorUtils.d.ts.map +1 -0
- package/dist/{transakApiErrorUtils.mjs → transakApiErrorUtils.js} +3 -3
- package/dist/transakApiErrorUtils.js.map +1 -0
- package/dist/{transakErrorCodes.d.cts → transakErrorCodes.d.ts} +3 -3
- package/dist/transakErrorCodes.d.ts.map +1 -0
- package/dist/{transakErrorCodes.mjs → transakErrorCodes.js} +1 -1
- package/dist/transakErrorCodes.js.map +1 -0
- package/dist/{wallet-registration-machine.d.cts → wallet-registration-machine.d.ts} +1 -1
- package/dist/wallet-registration-machine.d.ts.map +1 -0
- package/dist/{wallet-registration-machine.mjs → wallet-registration-machine.js} +1 -1
- package/dist/wallet-registration-machine.js.map +1 -0
- package/dist/{wallet-registration-service.d.cts → wallet-registration-service.d.ts} +1 -1
- package/dist/wallet-registration-service.d.ts.map +1 -0
- package/dist/{wallet-registration-service.mjs → wallet-registration-service.js} +60 -69
- package/dist/wallet-registration-service.js.map +1 -0
- package/package.json +17 -21
- package/dist/NeoBankService-method-action-types.cjs +0 -7
- package/dist/NeoBankService-method-action-types.cjs.map +0 -1
- package/dist/NeoBankService-method-action-types.d.cts +0 -126
- package/dist/NeoBankService-method-action-types.d.cts.map +0 -1
- package/dist/NeoBankService-method-action-types.d.mts.map +0 -1
- package/dist/NeoBankService-method-action-types.mjs.map +0 -1
- package/dist/NeoBankService.cjs +0 -387
- package/dist/NeoBankService.cjs.map +0 -1
- package/dist/NeoBankService.d.cts.map +0 -1
- package/dist/NeoBankService.d.mts +0 -191
- package/dist/NeoBankService.d.mts.map +0 -1
- package/dist/NeoBankService.mjs +0 -379
- package/dist/NeoBankService.mjs.map +0 -1
- package/dist/RampsController-method-action-types.cjs +0 -7
- package/dist/RampsController-method-action-types.cjs.map +0 -1
- package/dist/RampsController-method-action-types.d.cts +0 -758
- package/dist/RampsController-method-action-types.d.cts.map +0 -1
- package/dist/RampsController-method-action-types.d.mts.map +0 -1
- package/dist/RampsController-method-action-types.mjs.map +0 -1
- package/dist/RampsController.cjs +0 -2799
- package/dist/RampsController.cjs.map +0 -1
- package/dist/RampsController.d.cts.map +0 -1
- package/dist/RampsController.d.mts +0 -968
- package/dist/RampsController.d.mts.map +0 -1
- package/dist/RampsController.mjs.map +0 -1
- package/dist/RampsService-method-action-types.cjs +0 -7
- package/dist/RampsService-method-action-types.cjs.map +0 -1
- package/dist/RampsService-method-action-types.d.cts.map +0 -1
- package/dist/RampsService-method-action-types.d.mts +0 -171
- package/dist/RampsService-method-action-types.d.mts.map +0 -1
- package/dist/RampsService-method-action-types.mjs.map +0 -1
- package/dist/RampsService.cjs +0 -708
- package/dist/RampsService.cjs.map +0 -1
- package/dist/RampsService.d.cts +0 -944
- package/dist/RampsService.d.cts.map +0 -1
- package/dist/RampsService.d.mts.map +0 -1
- package/dist/RampsService.mjs.map +0 -1
- package/dist/RequestCache.cjs +0 -103
- package/dist/RequestCache.cjs.map +0 -1
- package/dist/RequestCache.d.cts +0 -110
- package/dist/RequestCache.d.cts.map +0 -1
- package/dist/RequestCache.d.mts.map +0 -1
- package/dist/RequestCache.mjs.map +0 -1
- package/dist/TransakService-method-action-types.cjs +0 -7
- package/dist/TransakService-method-action-types.cjs.map +0 -1
- package/dist/TransakService-method-action-types.d.cts.map +0 -1
- package/dist/TransakService-method-action-types.d.mts +0 -140
- package/dist/TransakService-method-action-types.d.mts.map +0 -1
- package/dist/TransakService-method-action-types.mjs.map +0 -1
- package/dist/TransakService.cjs +0 -743
- package/dist/TransakService.cjs.map +0 -1
- package/dist/TransakService.d.cts.map +0 -1
- package/dist/TransakService.d.mts +0 -370
- package/dist/TransakService.d.mts.map +0 -1
- package/dist/TransakService.mjs +0 -734
- package/dist/TransakService.mjs.map +0 -1
- package/dist/autoramp-types.cjs +0 -3
- package/dist/autoramp-types.cjs.map +0 -1
- package/dist/autoramp-types.d.cts.map +0 -1
- package/dist/autoramp-types.d.mts +0 -22
- package/dist/autoramp-types.d.mts.map +0 -1
- package/dist/autoramp-types.mjs +0 -2
- package/dist/autoramp-types.mjs.map +0 -1
- package/dist/autorampAccount.cjs +0 -155
- package/dist/autorampAccount.cjs.map +0 -1
- package/dist/autorampAccount.d.cts +0 -126
- package/dist/autorampAccount.d.cts.map +0 -1
- package/dist/autorampAccount.d.mts.map +0 -1
- package/dist/autorampAccount.mjs.map +0 -1
- package/dist/client-identity.cjs +0 -28
- package/dist/client-identity.cjs.map +0 -1
- package/dist/client-identity.d.cts +0 -24
- package/dist/client-identity.d.cts.map +0 -1
- package/dist/client-identity.d.mts.map +0 -1
- package/dist/client-identity.mjs.map +0 -1
- package/dist/errorNormalization.cjs +0 -84
- package/dist/errorNormalization.cjs.map +0 -1
- package/dist/errorNormalization.d.cts +0 -56
- package/dist/errorNormalization.d.cts.map +0 -1
- package/dist/errorNormalization.d.mts.map +0 -1
- package/dist/errorNormalization.mjs.map +0 -1
- package/dist/featureFlags.cjs +0 -144
- package/dist/featureFlags.cjs.map +0 -1
- package/dist/featureFlags.d.cts +0 -82
- package/dist/featureFlags.d.cts.map +0 -1
- package/dist/featureFlags.d.mts.map +0 -1
- package/dist/featureFlags.mjs.map +0 -1
- package/dist/index.cjs +0 -82
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -35
- package/dist/index.d.cts.map +0 -1
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs +0 -18
- package/dist/index.mjs.map +0 -1
- package/dist/orderStatus.cjs +0 -34
- package/dist/orderStatus.cjs.map +0 -1
- package/dist/orderStatus.d.cts +0 -18
- package/dist/orderStatus.d.cts.map +0 -1
- package/dist/orderStatus.d.mts.map +0 -1
- package/dist/orderStatus.mjs.map +0 -1
- package/dist/ownership-message.cjs +0 -26
- package/dist/ownership-message.cjs.map +0 -1
- package/dist/ownership-message.d.cts.map +0 -1
- package/dist/ownership-message.d.mts +0 -22
- package/dist/ownership-message.d.mts.map +0 -1
- package/dist/ownership-message.mjs.map +0 -1
- package/dist/paymentMethodMerge.cjs +0 -51
- package/dist/paymentMethodMerge.cjs.map +0 -1
- package/dist/paymentMethodMerge.d.cts.map +0 -1
- package/dist/paymentMethodMerge.d.mts +0 -26
- package/dist/paymentMethodMerge.d.mts.map +0 -1
- package/dist/paymentMethodMerge.mjs.map +0 -1
- package/dist/providerAvailability.cjs +0 -134
- package/dist/providerAvailability.cjs.map +0 -1
- package/dist/providerAvailability.d.cts.map +0 -1
- package/dist/providerAvailability.d.mts +0 -101
- package/dist/providerAvailability.d.mts.map +0 -1
- package/dist/providerAvailability.mjs.map +0 -1
- package/dist/quoteClassification.cjs +0 -44
- package/dist/quoteClassification.cjs.map +0 -1
- package/dist/quoteClassification.d.cts.map +0 -1
- package/dist/quoteClassification.d.mts +0 -33
- package/dist/quoteClassification.d.mts.map +0 -1
- package/dist/quoteClassification.mjs.map +0 -1
- package/dist/rampsErrorCodes.cjs +0 -11
- package/dist/rampsErrorCodes.cjs.map +0 -1
- package/dist/rampsErrorCodes.d.cts.map +0 -1
- package/dist/rampsErrorCodes.d.mts +0 -9
- package/dist/rampsErrorCodes.d.mts.map +0 -1
- package/dist/rampsErrorCodes.mjs.map +0 -1
- package/dist/selectors.cjs +0 -85
- package/dist/selectors.cjs.map +0 -1
- package/dist/selectors.d.cts +0 -78
- package/dist/selectors.d.cts.map +0 -1
- package/dist/selectors.d.mts.map +0 -1
- package/dist/selectors.mjs.map +0 -1
- package/dist/transakApiErrorUtils.cjs +0 -15
- package/dist/transakApiErrorUtils.cjs.map +0 -1
- package/dist/transakApiErrorUtils.d.cts.map +0 -1
- package/dist/transakApiErrorUtils.d.mts +0 -3
- package/dist/transakApiErrorUtils.d.mts.map +0 -1
- package/dist/transakApiErrorUtils.mjs.map +0 -1
- package/dist/transakErrorCodes.cjs +0 -14
- package/dist/transakErrorCodes.cjs.map +0 -1
- package/dist/transakErrorCodes.d.cts.map +0 -1
- package/dist/transakErrorCodes.d.mts +0 -12
- package/dist/transakErrorCodes.d.mts.map +0 -1
- package/dist/transakErrorCodes.mjs.map +0 -1
- package/dist/wallet-registration-machine.cjs +0 -144
- package/dist/wallet-registration-machine.cjs.map +0 -1
- package/dist/wallet-registration-machine.d.cts.map +0 -1
- package/dist/wallet-registration-machine.d.mts +0 -81
- package/dist/wallet-registration-machine.d.mts.map +0 -1
- package/dist/wallet-registration-machine.mjs.map +0 -1
- package/dist/wallet-registration-service.cjs +0 -351
- package/dist/wallet-registration-service.cjs.map +0 -1
- package/dist/wallet-registration-service.d.cts.map +0 -1
- package/dist/wallet-registration-service.d.mts +0 -144
- package/dist/wallet-registration-service.d.mts.map +0 -1
- package/dist/wallet-registration-service.mjs.map +0 -1
|
@@ -1,28 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
};
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
import {
|
|
14
|
-
import { BrokenCircuitError } from "@metamask/controller-utils";
|
|
15
|
-
import { applyAutorampRemoteStatus, createAutorampAccount, markAutorampNotified } from "./autorampAccount.mjs";
|
|
16
|
-
import { getHeadlessProviderAllowlist, isHeadlessAllProvidersEnabled, normalizeHeadlessProviderId } from "./featureFlags.mjs";
|
|
17
|
-
import { PENDING_ORDER_STATUSES, TERMINAL_ORDER_STATUSES } from "./orderStatus.mjs";
|
|
18
|
-
import { buildOwnershipMessage } from "./ownership-message.mjs";
|
|
19
|
-
import { mergePaymentMethodsById, pickPaymentMethod } from "./paymentMethodMerge.mjs";
|
|
20
|
-
import { getProvidersServingAsset, normalizeRampsAssetId, providerServesAsset } from "./providerAvailability.mjs";
|
|
21
|
-
import { RAMPS_ERROR_CODES } from "./rampsErrorCodes.mjs";
|
|
22
|
-
import { RampsOrderStatus } from "./RampsService.mjs";
|
|
23
|
-
import { DEFAULT_REQUEST_CACHE_TTL, DEFAULT_REQUEST_CACHE_MAX_SIZE, createCacheKey, isCacheExpired, createLoadingState, createSuccessState, createErrorState, RequestStatus } from "./RequestCache.mjs";
|
|
24
|
-
import { createInitialState as createInitialWalletRegistrationState, transition as transitionWalletRegistration } from "./wallet-registration-machine.mjs";
|
|
25
|
-
import { createIdempotencyKey, WalletRegistrationError } from "./wallet-registration-service.mjs";
|
|
1
|
+
import { BaseController } from '@metamask/base-controller';
|
|
2
|
+
import { BrokenCircuitError } from '@metamask/controller-utils';
|
|
3
|
+
import { applyAutorampRemoteStatus, createAutorampAccount, markAutorampNotified, } from './autorampAccount.js';
|
|
4
|
+
import { getHeadlessProviderAllowlist, isHeadlessAllProvidersEnabled, normalizeHeadlessProviderId, } from './featureFlags.js';
|
|
5
|
+
import { PENDING_ORDER_STATUSES, TERMINAL_ORDER_STATUSES, } from './orderStatus.js';
|
|
6
|
+
import { buildOwnershipMessage } from './ownership-message.js';
|
|
7
|
+
import { mergePaymentMethodsById, pickPaymentMethod, } from './paymentMethodMerge.js';
|
|
8
|
+
import { getProvidersServingAsset, normalizeRampsAssetId, providerServesAsset, } from './providerAvailability.js';
|
|
9
|
+
import { RAMPS_ERROR_CODES } from './rampsErrorCodes.js';
|
|
10
|
+
import { RampsOrderStatus } from './RampsService.js';
|
|
11
|
+
import { DEFAULT_REQUEST_CACHE_TTL, DEFAULT_REQUEST_CACHE_MAX_SIZE, createCacheKey, isCacheExpired, createLoadingState, createSuccessState, createErrorState, RequestStatus, } from './RequestCache.js';
|
|
12
|
+
import { createInitialState as createInitialWalletRegistrationState, transition as transitionWalletRegistration, } from './wallet-registration-machine.js';
|
|
13
|
+
import { createIdempotencyKey, WalletRegistrationError, } from './wallet-registration-service.js';
|
|
26
14
|
// === GENERAL ===
|
|
27
15
|
/**
|
|
28
16
|
* The name of the {@link RampsController}, used to namespace the
|
|
@@ -392,7 +380,7 @@ export function getInternalOrderCode(orderOrId) {
|
|
|
392
380
|
return providerOrderId;
|
|
393
381
|
}
|
|
394
382
|
// === ORDER POLLING CONSTANTS ===
|
|
395
|
-
const DEFAULT_POLLING_INTERVAL_MS =
|
|
383
|
+
const DEFAULT_POLLING_INTERVAL_MS = 30_000;
|
|
396
384
|
const MAX_ERROR_COUNT = 5;
|
|
397
385
|
// === CONTROLLER DEFINITION ===
|
|
398
386
|
const MESSENGER_EXPOSED_METHODS = [
|
|
@@ -476,6 +464,33 @@ function contextStillMatches(state, context) {
|
|
|
476
464
|
(state.providers.selected?.id.trim() ?? '') === context.providerId);
|
|
477
465
|
}
|
|
478
466
|
export class RampsController extends BaseController {
|
|
467
|
+
/**
|
|
468
|
+
* Default TTL for cached requests.
|
|
469
|
+
*/
|
|
470
|
+
#requestCacheTTL;
|
|
471
|
+
/**
|
|
472
|
+
* Maximum number of entries in the request cache.
|
|
473
|
+
*/
|
|
474
|
+
#requestCacheMaxSize;
|
|
475
|
+
/**
|
|
476
|
+
* Map of pending requests for deduplication.
|
|
477
|
+
* Key is the cache key, value is the pending request with abort controller.
|
|
478
|
+
*/
|
|
479
|
+
#pendingRequests = new Map();
|
|
480
|
+
/**
|
|
481
|
+
* Count of in-flight requests per resource type.
|
|
482
|
+
* Used so isLoading is only cleared when the last request for that resource finishes.
|
|
483
|
+
*/
|
|
484
|
+
#pendingResourceCount = new Map();
|
|
485
|
+
/**
|
|
486
|
+
* Monotonic generation per resource type used to invalidate stale in-flight
|
|
487
|
+
* requests after region/token/provider dependent-resource resets.
|
|
488
|
+
*/
|
|
489
|
+
#pendingResourceGeneration = new Map();
|
|
490
|
+
#orderPollingMeta = new Map();
|
|
491
|
+
#orderPollingTimer = null;
|
|
492
|
+
#isPolling = false;
|
|
493
|
+
#initPromise = null;
|
|
479
494
|
/**
|
|
480
495
|
* Clears the pending resource count map. Used only in tests to exercise the
|
|
481
496
|
* defensive path when get() returns undefined in the finally block.
|
|
@@ -483,7 +498,24 @@ export class RampsController extends BaseController {
|
|
|
483
498
|
* @internal
|
|
484
499
|
*/
|
|
485
500
|
clearPendingResourceCountForTest() {
|
|
486
|
-
|
|
501
|
+
this.#pendingResourceCount.clear();
|
|
502
|
+
}
|
|
503
|
+
#clearPendingResourceCountForDependentResources() {
|
|
504
|
+
for (const resourceType of DEPENDENT_RESOURCE_KEYS) {
|
|
505
|
+
this.#pendingResourceCount.delete(resourceType);
|
|
506
|
+
const generation = this.#pendingResourceGeneration.get(resourceType) ?? 0;
|
|
507
|
+
this.#pendingResourceGeneration.set(resourceType, generation + 1);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
#abortDependentRequests() {
|
|
511
|
+
for (const [cacheKey, pending] of this.#pendingRequests.entries()) {
|
|
512
|
+
if (pending.resourceType &&
|
|
513
|
+
DEPENDENT_RESOURCE_KEYS_SET.has(pending.resourceType)) {
|
|
514
|
+
pending.abortController.abort();
|
|
515
|
+
this.#pendingRequests.delete(cacheKey);
|
|
516
|
+
this.#removeRequestState(cacheKey);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
487
519
|
}
|
|
488
520
|
/**
|
|
489
521
|
* Constructs a new {@link RampsController}.
|
|
@@ -507,38 +539,39 @@ export class RampsController extends BaseController {
|
|
|
507
539
|
requests: {},
|
|
508
540
|
},
|
|
509
541
|
});
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
* Default TTL for cached requests.
|
|
513
|
-
*/
|
|
514
|
-
_RampsController_requestCacheTTL.set(this, void 0);
|
|
515
|
-
/**
|
|
516
|
-
* Maximum number of entries in the request cache.
|
|
517
|
-
*/
|
|
518
|
-
_RampsController_requestCacheMaxSize.set(this, void 0);
|
|
519
|
-
/**
|
|
520
|
-
* Map of pending requests for deduplication.
|
|
521
|
-
* Key is the cache key, value is the pending request with abort controller.
|
|
522
|
-
*/
|
|
523
|
-
_RampsController_pendingRequests.set(this, new Map());
|
|
524
|
-
/**
|
|
525
|
-
* Count of in-flight requests per resource type.
|
|
526
|
-
* Used so isLoading is only cleared when the last request for that resource finishes.
|
|
527
|
-
*/
|
|
528
|
-
_RampsController_pendingResourceCount.set(this, new Map());
|
|
529
|
-
/**
|
|
530
|
-
* Monotonic generation per resource type used to invalidate stale in-flight
|
|
531
|
-
* requests after region/token/provider dependent-resource resets.
|
|
532
|
-
*/
|
|
533
|
-
_RampsController_pendingResourceGeneration.set(this, new Map());
|
|
534
|
-
_RampsController_orderPollingMeta.set(this, new Map());
|
|
535
|
-
_RampsController_orderPollingTimer.set(this, null);
|
|
536
|
-
_RampsController_isPolling.set(this, false);
|
|
537
|
-
_RampsController_initPromise.set(this, null);
|
|
538
|
-
__classPrivateFieldSet(this, _RampsController_requestCacheTTL, requestCacheTTL, "f");
|
|
539
|
-
__classPrivateFieldSet(this, _RampsController_requestCacheMaxSize, requestCacheMaxSize, "f");
|
|
542
|
+
this.#requestCacheTTL = requestCacheTTL;
|
|
543
|
+
this.#requestCacheMaxSize = requestCacheMaxSize;
|
|
540
544
|
this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
|
|
541
545
|
}
|
|
546
|
+
/**
|
|
547
|
+
* Resolves the Headless Buy all-providers feature flag: whether widening is
|
|
548
|
+
* enabled, and the provider-id allowlist the widened pick is restricted to
|
|
549
|
+
* (if the flag's object payload carries one).
|
|
550
|
+
*
|
|
551
|
+
* Reads `RemoteFeatureFlagController` state through the messenger on every
|
|
552
|
+
* call, so a remote flag fetch or a local dev override takes effect at
|
|
553
|
+
* runtime without reconstructing the controller. The single read keeps the
|
|
554
|
+
* enabled bit and the allowlist consistent even if the flag changes while a
|
|
555
|
+
* quote request is in flight. Key lookup, local-override merging, and value
|
|
556
|
+
* coercion live in the shared `isHeadlessAllProvidersEnabled` /
|
|
557
|
+
* `getHeadlessProviderAllowlist` helpers so UI consumers resolve the flag
|
|
558
|
+
* identically. Fails closed: when `RemoteFeatureFlagController:getState` is
|
|
559
|
+
* not wired up, quoting stays native-only.
|
|
560
|
+
*
|
|
561
|
+
* @returns The enabled bit and the optional provider-id allowlist.
|
|
562
|
+
*/
|
|
563
|
+
#resolveAllProvidersFlag() {
|
|
564
|
+
try {
|
|
565
|
+
const remoteFeatureFlagState = this.messenger.call('RemoteFeatureFlagController:getState');
|
|
566
|
+
return {
|
|
567
|
+
enabled: isHeadlessAllProvidersEnabled(remoteFeatureFlagState),
|
|
568
|
+
allowlist: getHeadlessProviderAllowlist(remoteFeatureFlagState),
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
catch {
|
|
572
|
+
return { enabled: false };
|
|
573
|
+
}
|
|
574
|
+
}
|
|
542
575
|
/**
|
|
543
576
|
* Executes a request with caching, deduplication, and at most one in-flight
|
|
544
577
|
* request per resource type.
|
|
@@ -565,11 +598,11 @@ export class RampsController extends BaseController {
|
|
|
565
598
|
*/
|
|
566
599
|
async executeRequest(cacheKey, fetcher, options) {
|
|
567
600
|
// Get TTL for verifying cache expiration
|
|
568
|
-
const ttl = options?.ttl ??
|
|
601
|
+
const ttl = options?.ttl ?? this.#requestCacheTTL;
|
|
569
602
|
// DEDUPLICATION:
|
|
570
603
|
// Check if a request is already in flight for this cache key
|
|
571
604
|
// If so, return the original promise for that request
|
|
572
|
-
const pending =
|
|
605
|
+
const pending = this.#pendingRequests.get(cacheKey);
|
|
573
606
|
if (pending) {
|
|
574
607
|
return pending.promise;
|
|
575
608
|
}
|
|
@@ -587,17 +620,17 @@ export class RampsController extends BaseController {
|
|
|
587
620
|
const lastFetchedAt = Date.now();
|
|
588
621
|
const { resourceType } = options ?? {};
|
|
589
622
|
const resourceGeneration = resourceType
|
|
590
|
-
? (
|
|
623
|
+
? (this.#pendingResourceGeneration.get(resourceType) ?? 0)
|
|
591
624
|
: undefined;
|
|
592
625
|
// Update state to loading
|
|
593
|
-
|
|
626
|
+
this.#updateRequestState(cacheKey, createLoadingState());
|
|
594
627
|
// Set resource-level loading state (only on cache miss). Ref-count so concurrent
|
|
595
628
|
// requests for the same resource type (different cache keys) keep isLoading true.
|
|
596
629
|
if (resourceType) {
|
|
597
|
-
const count =
|
|
598
|
-
|
|
630
|
+
const count = this.#pendingResourceCount.get(resourceType) ?? 0;
|
|
631
|
+
this.#pendingResourceCount.set(resourceType, count + 1);
|
|
599
632
|
if (count === 0) {
|
|
600
|
-
|
|
633
|
+
this.#setResourceLoading(resourceType, true);
|
|
601
634
|
}
|
|
602
635
|
}
|
|
603
636
|
// Create the fetch promise
|
|
@@ -607,11 +640,11 @@ export class RampsController extends BaseController {
|
|
|
607
640
|
if (abortController.signal.aborted) {
|
|
608
641
|
throw new Error('Request was aborted');
|
|
609
642
|
}
|
|
610
|
-
|
|
643
|
+
this.#updateRequestState(cacheKey, createSuccessState(data, lastFetchedAt));
|
|
611
644
|
if (resourceType) {
|
|
612
645
|
const isCurrent = !options?.isResultCurrent || options.isResultCurrent();
|
|
613
646
|
if (isCurrent) {
|
|
614
|
-
|
|
647
|
+
this.#setResourceError(resourceType, null);
|
|
615
648
|
}
|
|
616
649
|
}
|
|
617
650
|
return data;
|
|
@@ -621,38 +654,38 @@ export class RampsController extends BaseController {
|
|
|
621
654
|
throw error;
|
|
622
655
|
}
|
|
623
656
|
const { errorInfo, normalizedError } = getNormalizedRampsError(error);
|
|
624
|
-
|
|
657
|
+
this.#updateRequestState(cacheKey, createErrorState(errorInfo.message, lastFetchedAt, errorInfo.errorKey));
|
|
625
658
|
if (resourceType) {
|
|
626
659
|
const isCurrent = !options?.isResultCurrent || options.isResultCurrent();
|
|
627
660
|
if (isCurrent) {
|
|
628
|
-
|
|
661
|
+
this.#setResourceError(resourceType, errorInfo);
|
|
629
662
|
}
|
|
630
663
|
}
|
|
631
664
|
throw normalizedError;
|
|
632
665
|
}
|
|
633
666
|
finally {
|
|
634
|
-
if (
|
|
667
|
+
if (this.#pendingRequests.get(cacheKey)?.abortController ===
|
|
635
668
|
abortController) {
|
|
636
|
-
|
|
669
|
+
this.#pendingRequests.delete(cacheKey);
|
|
637
670
|
}
|
|
638
671
|
// Clear resource-level loading state only when no requests for this resource remain
|
|
639
672
|
if (resourceType && !abortController.signal.aborted) {
|
|
640
|
-
const currentGeneration =
|
|
673
|
+
const currentGeneration = this.#pendingResourceGeneration.get(resourceType) ?? 0;
|
|
641
674
|
if (currentGeneration === resourceGeneration) {
|
|
642
|
-
const count =
|
|
675
|
+
const count = this.#pendingResourceCount.get(resourceType) ?? 0;
|
|
643
676
|
const next = Math.max(0, count - 1);
|
|
644
677
|
if (next === 0) {
|
|
645
|
-
|
|
646
|
-
|
|
678
|
+
this.#pendingResourceCount.delete(resourceType);
|
|
679
|
+
this.#setResourceLoading(resourceType, false);
|
|
647
680
|
}
|
|
648
681
|
else {
|
|
649
|
-
|
|
682
|
+
this.#pendingResourceCount.set(resourceType, next);
|
|
650
683
|
}
|
|
651
684
|
}
|
|
652
685
|
}
|
|
653
686
|
}
|
|
654
687
|
})();
|
|
655
|
-
|
|
688
|
+
this.#pendingRequests.set(cacheKey, {
|
|
656
689
|
promise,
|
|
657
690
|
abortController,
|
|
658
691
|
resourceType,
|
|
@@ -666,15 +699,92 @@ export class RampsController extends BaseController {
|
|
|
666
699
|
* @returns True if a request was aborted.
|
|
667
700
|
*/
|
|
668
701
|
abortRequest(cacheKey) {
|
|
669
|
-
const pending =
|
|
702
|
+
const pending = this.#pendingRequests.get(cacheKey);
|
|
670
703
|
if (pending) {
|
|
671
704
|
pending.abortController.abort();
|
|
672
|
-
|
|
673
|
-
|
|
705
|
+
this.#pendingRequests.delete(cacheKey);
|
|
706
|
+
this.#removeRequestState(cacheKey);
|
|
674
707
|
return true;
|
|
675
708
|
}
|
|
676
709
|
return false;
|
|
677
710
|
}
|
|
711
|
+
/**
|
|
712
|
+
* Mutates state.requests inside update(); cast is centralized here.
|
|
713
|
+
*
|
|
714
|
+
* @param fn - Callback that mutates the requests record.
|
|
715
|
+
*/
|
|
716
|
+
#mutateRequests(fn) {
|
|
717
|
+
this.update((state) => {
|
|
718
|
+
const requests = state.requests;
|
|
719
|
+
fn(requests);
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
#removeRequestState(cacheKey) {
|
|
723
|
+
this.#mutateRequests((requests) => {
|
|
724
|
+
delete requests[cacheKey];
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
#cleanupState() {
|
|
728
|
+
this.#abortDependentRequests();
|
|
729
|
+
this.#clearPendingResourceCountForDependentResources();
|
|
730
|
+
this.update((state) => resetDependentResources(state, { clearUserRegionData: true }));
|
|
731
|
+
}
|
|
732
|
+
#requireRegion() {
|
|
733
|
+
const regionCode = this.state.userRegion?.regionCode;
|
|
734
|
+
if (!regionCode) {
|
|
735
|
+
throw new Error('Region is required. Cannot proceed without valid region information.');
|
|
736
|
+
}
|
|
737
|
+
return regionCode;
|
|
738
|
+
}
|
|
739
|
+
#isRegionCurrent(normalizedRegion) {
|
|
740
|
+
const current = this.state.userRegion?.regionCode;
|
|
741
|
+
return current === undefined || current === normalizedRegion;
|
|
742
|
+
}
|
|
743
|
+
#isTokenCurrent(normalizedAssetId) {
|
|
744
|
+
const current = this.state.tokens.selected?.assetId ?? '';
|
|
745
|
+
return current === normalizedAssetId;
|
|
746
|
+
}
|
|
747
|
+
#isProviderCurrent(normalizedProviderId) {
|
|
748
|
+
const current = this.state.providers.selected?.id ?? '';
|
|
749
|
+
return current === normalizedProviderId;
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Updates a single field (isLoading or error) on a resource state.
|
|
753
|
+
* All resources share the same ResourceState structure, so we use
|
|
754
|
+
* dynamic property access to avoid duplicating switch statements.
|
|
755
|
+
*
|
|
756
|
+
* @param resourceType - The type of resource.
|
|
757
|
+
* @param field - The field to update ('isLoading' or 'error').
|
|
758
|
+
* @param value - The value to set.
|
|
759
|
+
*/
|
|
760
|
+
#updateResourceField(resourceType, field, value) {
|
|
761
|
+
this.update((state) => {
|
|
762
|
+
const resource = getResourceState(state, resourceType);
|
|
763
|
+
resource[field] = value;
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Sets the loading state for a resource type.
|
|
768
|
+
*
|
|
769
|
+
* @param resourceType - The type of resource.
|
|
770
|
+
* @param loading - Whether the resource is loading.
|
|
771
|
+
*/
|
|
772
|
+
#setResourceLoading(resourceType, loading) {
|
|
773
|
+
this.#updateResourceField(resourceType, 'isLoading', loading);
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* Sets the error state for a resource type.
|
|
777
|
+
*
|
|
778
|
+
* @param resourceType - The type of resource.
|
|
779
|
+
* @param errorInfo - The error info, or null to clear.
|
|
780
|
+
*/
|
|
781
|
+
#setResourceError(resourceType, errorInfo) {
|
|
782
|
+
this.update((state) => {
|
|
783
|
+
const resource = getResourceState(state, resourceType);
|
|
784
|
+
resource.error = errorInfo?.message ?? null;
|
|
785
|
+
resource.errorKey = errorInfo?.errorKey ?? null;
|
|
786
|
+
});
|
|
787
|
+
}
|
|
678
788
|
/**
|
|
679
789
|
* Gets the state of a specific cached request.
|
|
680
790
|
*
|
|
@@ -684,6 +794,42 @@ export class RampsController extends BaseController {
|
|
|
684
794
|
getRequestState(cacheKey) {
|
|
685
795
|
return this.state.requests[cacheKey];
|
|
686
796
|
}
|
|
797
|
+
/**
|
|
798
|
+
* Updates the state for a specific request.
|
|
799
|
+
*
|
|
800
|
+
* @param cacheKey - The cache key.
|
|
801
|
+
* @param requestState - The new state for the request.
|
|
802
|
+
*/
|
|
803
|
+
#updateRequestState(cacheKey, requestState) {
|
|
804
|
+
const maxSize = this.#requestCacheMaxSize;
|
|
805
|
+
const ttl = this.#requestCacheTTL;
|
|
806
|
+
this.#mutateRequests((requests) => {
|
|
807
|
+
requests[cacheKey] = requestState;
|
|
808
|
+
const keys = Object.keys(requests);
|
|
809
|
+
for (const key of keys) {
|
|
810
|
+
const entry = requests[key];
|
|
811
|
+
if (entry?.status === RequestStatus.SUCCESS &&
|
|
812
|
+
isCacheExpired(entry, ttl)) {
|
|
813
|
+
delete requests[key];
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
const remainingKeys = Object.keys(requests);
|
|
817
|
+
if (remainingKeys.length > maxSize) {
|
|
818
|
+
const sortedKeys = remainingKeys.sort((a, b) => {
|
|
819
|
+
const aTime = requests[a]?.timestamp ?? 0;
|
|
820
|
+
const bTime = requests[b]?.timestamp ?? 0;
|
|
821
|
+
return aTime - bTime;
|
|
822
|
+
});
|
|
823
|
+
const entriesToRemove = remainingKeys.length - maxSize;
|
|
824
|
+
for (let i = 0; i < entriesToRemove; i++) {
|
|
825
|
+
const keyToRemove = sortedKeys[i];
|
|
826
|
+
if (keyToRemove) {
|
|
827
|
+
delete requests[keyToRemove];
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
});
|
|
832
|
+
}
|
|
687
833
|
/**
|
|
688
834
|
* Sets the user's region manually (without fetching geolocation).
|
|
689
835
|
* This allows users to override the detected region.
|
|
@@ -697,12 +843,12 @@ export class RampsController extends BaseController {
|
|
|
697
843
|
try {
|
|
698
844
|
const countriesData = this.state.countries.data;
|
|
699
845
|
if (!countriesData || countriesData.length === 0) {
|
|
700
|
-
|
|
846
|
+
this.#cleanupState();
|
|
701
847
|
throw new Error('No countries found. Cannot set user region without valid country information.');
|
|
702
848
|
}
|
|
703
849
|
const userRegion = findRegionFromCode(normalizedRegion, countriesData);
|
|
704
850
|
if (!userRegion) {
|
|
705
|
-
|
|
851
|
+
this.#cleanupState();
|
|
706
852
|
throw new Error(`Region "${normalizedRegion}" not found in countries data. Cannot set user region without valid country information.`);
|
|
707
853
|
}
|
|
708
854
|
const regionChanged = Boolean(options?.forceRefresh) ||
|
|
@@ -713,7 +859,7 @@ export class RampsController extends BaseController {
|
|
|
713
859
|
// user taps France → Finland → France quickly). Instead we let
|
|
714
860
|
// old requests complete naturally; isResultCurrent guards in
|
|
715
861
|
// getProviders/getPaymentMethods discard stale results.
|
|
716
|
-
|
|
862
|
+
this.#clearPendingResourceCountForDependentResources();
|
|
717
863
|
}
|
|
718
864
|
this.update((state) => {
|
|
719
865
|
if (regionChanged) {
|
|
@@ -724,7 +870,7 @@ export class RampsController extends BaseController {
|
|
|
724
870
|
return userRegion;
|
|
725
871
|
}
|
|
726
872
|
catch (error) {
|
|
727
|
-
|
|
873
|
+
this.#cleanupState();
|
|
728
874
|
throw error;
|
|
729
875
|
}
|
|
730
876
|
}
|
|
@@ -749,7 +895,7 @@ export class RampsController extends BaseController {
|
|
|
749
895
|
});
|
|
750
896
|
return;
|
|
751
897
|
}
|
|
752
|
-
|
|
898
|
+
this.#requireRegion();
|
|
753
899
|
// If a full Provider object is passed, store it directly (avoids
|
|
754
900
|
// depending on state.providers.data being populated).
|
|
755
901
|
if (typeof providerOrId !== 'string') {
|
|
@@ -829,21 +975,68 @@ export class RampsController extends BaseController {
|
|
|
829
975
|
* @returns Promise that resolves when initialization is complete.
|
|
830
976
|
*/
|
|
831
977
|
async init(options) {
|
|
832
|
-
if (!options?.forceRefresh &&
|
|
833
|
-
return
|
|
978
|
+
if (!options?.forceRefresh && this.#initPromise !== null) {
|
|
979
|
+
return this.#initPromise;
|
|
834
980
|
}
|
|
835
981
|
if (options?.forceRefresh) {
|
|
836
|
-
|
|
982
|
+
this.#initPromise = null;
|
|
837
983
|
}
|
|
838
|
-
const initPromise =
|
|
839
|
-
if (
|
|
840
|
-
|
|
984
|
+
const initPromise = this.#runInit(options).then(() => undefined, (error) => {
|
|
985
|
+
if (this.#initPromise === initPromise) {
|
|
986
|
+
this.#initPromise = null;
|
|
841
987
|
}
|
|
842
988
|
throw error;
|
|
843
989
|
});
|
|
844
|
-
|
|
990
|
+
this.#initPromise = initPromise;
|
|
845
991
|
return initPromise;
|
|
846
992
|
}
|
|
993
|
+
async #runInit(options) {
|
|
994
|
+
// Force-refetch the catalog on startup so region preset amounts stay
|
|
995
|
+
// current, bypassing the in-session request cache. The catalog is not
|
|
996
|
+
// persisted, so a cold start always re-fetches it regardless.
|
|
997
|
+
await this.getCountries({ ...options, forceRefresh: true });
|
|
998
|
+
// Always prefer the user's persisted region. Geolocation is only used to
|
|
999
|
+
// seed the initial value; once the user (or a prior init) has set a region
|
|
1000
|
+
// we must respect that choice — even on forceRefresh.
|
|
1001
|
+
const persistedRegionCode = this.state.userRegion?.regionCode;
|
|
1002
|
+
const regionCode = persistedRegionCode ??
|
|
1003
|
+
(await this.messenger.call('RampsService:getGeolocation'));
|
|
1004
|
+
if (!regionCode) {
|
|
1005
|
+
throw new Error('Failed to fetch geolocation. Cannot initialize controller without valid region information.');
|
|
1006
|
+
}
|
|
1007
|
+
// For an already-persisted region, getCountries() has already re-synced it
|
|
1008
|
+
// from the fresh catalog (see #syncUserRegionFromCountriesCatalog). Calling
|
|
1009
|
+
// setUserRegion here would re-validate against that catalog and, if it is
|
|
1010
|
+
// momentarily empty or no longer lists the region (e.g. a transient/partial
|
|
1011
|
+
// catalog response or a region with no current provider coverage), throw and
|
|
1012
|
+
// wipe the persisted region via #cleanupState. Preserve the existing region
|
|
1013
|
+
// instead; only resolve a brand-new region (from geolocation) strictly.
|
|
1014
|
+
if (persistedRegionCode) {
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
await this.setUserRegion(regionCode, options);
|
|
1018
|
+
}
|
|
1019
|
+
/**
|
|
1020
|
+
* Re-applies `userRegion` from the current countries catalog so preset
|
|
1021
|
+
* amounts and support flags stay in sync after a catalog refresh.
|
|
1022
|
+
*/
|
|
1023
|
+
#syncUserRegionFromCountriesCatalog() {
|
|
1024
|
+
const regionCode = this.state.userRegion?.regionCode;
|
|
1025
|
+
if (!regionCode) {
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
const countriesData = this.state.countries.data;
|
|
1029
|
+
if (!countriesData.length) {
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
const userRegion = findRegionFromCode(regionCode, countriesData);
|
|
1033
|
+
if (!userRegion) {
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
this.update((state) => {
|
|
1037
|
+
state.userRegion = userRegion;
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
847
1040
|
/**
|
|
848
1041
|
* Fetches the list of supported countries.
|
|
849
1042
|
* The API returns countries with support information for both buy and sell actions.
|
|
@@ -860,7 +1053,7 @@ export class RampsController extends BaseController {
|
|
|
860
1053
|
this.update((state) => {
|
|
861
1054
|
state.countries.data = Array.isArray(countries) ? [...countries] : [];
|
|
862
1055
|
});
|
|
863
|
-
|
|
1056
|
+
this.#syncUserRegionFromCountriesCatalog();
|
|
864
1057
|
return countries;
|
|
865
1058
|
}
|
|
866
1059
|
/**
|
|
@@ -874,7 +1067,7 @@ export class RampsController extends BaseController {
|
|
|
874
1067
|
* @returns The tokens response containing topTokens and allTokens.
|
|
875
1068
|
*/
|
|
876
1069
|
async getTokens(region, action = 'buy', options) {
|
|
877
|
-
const regionToUse = region ??
|
|
1070
|
+
const regionToUse = region ?? this.#requireRegion();
|
|
878
1071
|
const normalizedRegion = regionToUse.toLowerCase().trim();
|
|
879
1072
|
const cacheKey = createCacheKey('getTokens', [
|
|
880
1073
|
normalizedRegion,
|
|
@@ -888,7 +1081,7 @@ export class RampsController extends BaseController {
|
|
|
888
1081
|
}, {
|
|
889
1082
|
...options,
|
|
890
1083
|
resourceType: 'tokens',
|
|
891
|
-
isResultCurrent: () =>
|
|
1084
|
+
isResultCurrent: () => this.#isRegionCurrent(normalizedRegion),
|
|
892
1085
|
});
|
|
893
1086
|
this.update((state) => {
|
|
894
1087
|
const userRegionCode = state.userRegion?.regionCode;
|
|
@@ -913,7 +1106,7 @@ export class RampsController extends BaseController {
|
|
|
913
1106
|
});
|
|
914
1107
|
return;
|
|
915
1108
|
}
|
|
916
|
-
|
|
1109
|
+
this.#requireRegion();
|
|
917
1110
|
const tokens = this.state.tokens.data;
|
|
918
1111
|
if (!tokens) {
|
|
919
1112
|
throw new Error('Tokens not loaded. Cannot set selected token before tokens are fetched.');
|
|
@@ -939,7 +1132,7 @@ export class RampsController extends BaseController {
|
|
|
939
1132
|
* @returns The providers response containing providers array.
|
|
940
1133
|
*/
|
|
941
1134
|
async getProviders(region, options) {
|
|
942
|
-
const regionToUse = region ??
|
|
1135
|
+
const regionToUse = region ?? this.#requireRegion();
|
|
943
1136
|
const normalizedRegion = regionToUse.toLowerCase().trim();
|
|
944
1137
|
const cacheKey = createCacheKey('getProviders', [
|
|
945
1138
|
normalizedRegion,
|
|
@@ -956,7 +1149,7 @@ export class RampsController extends BaseController {
|
|
|
956
1149
|
}, {
|
|
957
1150
|
...options,
|
|
958
1151
|
resourceType: 'providers',
|
|
959
|
-
isResultCurrent: () =>
|
|
1152
|
+
isResultCurrent: () => this.#isRegionCurrent(normalizedRegion),
|
|
960
1153
|
});
|
|
961
1154
|
const { providers } = response;
|
|
962
1155
|
this.update((state) => {
|
|
@@ -978,7 +1171,7 @@ export class RampsController extends BaseController {
|
|
|
978
1171
|
* @returns The payment methods response containing payments array.
|
|
979
1172
|
*/
|
|
980
1173
|
async getPaymentMethods(region, options) {
|
|
981
|
-
const regionCode = region ??
|
|
1174
|
+
const regionCode = region ?? this.#requireRegion();
|
|
982
1175
|
const assetIdToUse = options?.assetId ?? this.state.tokens.selected?.assetId ?? '';
|
|
983
1176
|
const providerToUse = options?.provider ?? this.state.providers.selected?.id ?? '';
|
|
984
1177
|
const normalizedRegion = regionCode.toLowerCase().trim();
|
|
@@ -997,9 +1190,9 @@ export class RampsController extends BaseController {
|
|
|
997
1190
|
...options,
|
|
998
1191
|
resourceType: 'paymentMethods',
|
|
999
1192
|
isResultCurrent: () => {
|
|
1000
|
-
const regionMatch =
|
|
1001
|
-
const tokenMatch =
|
|
1002
|
-
const providerMatch =
|
|
1193
|
+
const regionMatch = this.#isRegionCurrent(normalizedRegion);
|
|
1194
|
+
const tokenMatch = this.#isTokenCurrent(assetIdToUse);
|
|
1195
|
+
const providerMatch = this.#isProviderCurrent(providerToUse);
|
|
1003
1196
|
return regionMatch && tokenMatch && providerMatch;
|
|
1004
1197
|
},
|
|
1005
1198
|
});
|
|
@@ -1062,7 +1255,7 @@ export class RampsController extends BaseController {
|
|
|
1062
1255
|
* provider ids that contributed.
|
|
1063
1256
|
*/
|
|
1064
1257
|
async getPaymentMethodsForContext(options) {
|
|
1065
|
-
const regionToUse = options.region ??
|
|
1258
|
+
const regionToUse = options.region ?? this.#requireRegion();
|
|
1066
1259
|
const normalizedRegion = regionToUse.toLowerCase().trim();
|
|
1067
1260
|
const assetId = options.assetId.trim();
|
|
1068
1261
|
if (assetId === '') {
|
|
@@ -1073,7 +1266,7 @@ export class RampsController extends BaseController {
|
|
|
1073
1266
|
const providerIdForState = options.providers?.length === 1
|
|
1074
1267
|
? options.providers[0].trim()
|
|
1075
1268
|
: (this.state.providers.selected?.id.trim() ?? '');
|
|
1076
|
-
const providerIds = await
|
|
1269
|
+
const providerIds = await this.#resolveProviderIdsForPaymentMethods({
|
|
1077
1270
|
assetId,
|
|
1078
1271
|
region: normalizedRegion,
|
|
1079
1272
|
providers: options.providers,
|
|
@@ -1225,7 +1418,7 @@ export class RampsController extends BaseController {
|
|
|
1225
1418
|
* @returns The quotes response containing success, sorted, error, and customActions.
|
|
1226
1419
|
*/
|
|
1227
1420
|
async getQuotes(options) {
|
|
1228
|
-
const regionToUse = options.region ??
|
|
1421
|
+
const regionToUse = options.region ?? this.#requireRegion();
|
|
1229
1422
|
const fiatToUse = options.fiat ?? this.state.userRegion?.country?.currency;
|
|
1230
1423
|
const paymentMethodsToUse = options.paymentMethods ??
|
|
1231
1424
|
this.state.paymentMethods.data.map((pm) => pm.id);
|
|
@@ -1250,14 +1443,14 @@ export class RampsController extends BaseController {
|
|
|
1250
1443
|
// the same `RemoteFeatureFlagController` state snapshot, so a flag edit
|
|
1251
1444
|
// during the awaited quote fetch cannot produce a mixed read.
|
|
1252
1445
|
const { enabled: allProvidersEnabled, allowlist: providerAllowlist } = wantsAutoSelection
|
|
1253
|
-
?
|
|
1446
|
+
? this.#resolveAllProvidersFlag()
|
|
1254
1447
|
: { enabled: false, allowlist: undefined };
|
|
1255
1448
|
const widenToAllProviders = wantsAutoSelection && allProvidersEnabled;
|
|
1256
1449
|
let providersToUse;
|
|
1257
1450
|
let widenedProviderCatalog = this.state.providers.data;
|
|
1258
1451
|
if (options.providers) {
|
|
1259
1452
|
providersToUse = options.restrictToKnownOrNativeProviders
|
|
1260
|
-
? await
|
|
1453
|
+
? await this.#filterProviderIdsBySupport({
|
|
1261
1454
|
providerIds: options.providers,
|
|
1262
1455
|
assetId: normalizedAssetIdForValidation,
|
|
1263
1456
|
region: regionToUse,
|
|
@@ -1268,7 +1461,7 @@ export class RampsController extends BaseController {
|
|
|
1268
1461
|
// `#getSupportingProvidersForRegion` also hydrates the provider catalog
|
|
1269
1462
|
// when controller state is empty, so all-provider quoting cannot silently
|
|
1270
1463
|
// return zero providers here.
|
|
1271
|
-
const { supporting } = await
|
|
1464
|
+
const { supporting } = await this.#getSupportingProvidersForRegion({
|
|
1272
1465
|
assetId: normalizedAssetIdForValidation,
|
|
1273
1466
|
region: regionToUse,
|
|
1274
1467
|
});
|
|
@@ -1280,7 +1473,7 @@ export class RampsController extends BaseController {
|
|
|
1280
1473
|
// The restriction flag implies resolution: it must narrow the provider
|
|
1281
1474
|
// set even when `autoSelectProvider` was not explicitly passed, otherwise
|
|
1282
1475
|
// it would be silently ignored and every provider quoted.
|
|
1283
|
-
providersToUse = await
|
|
1476
|
+
providersToUse = await this.#resolveProviderIdsForQuote({
|
|
1284
1477
|
assetId: normalizedAssetIdForValidation,
|
|
1285
1478
|
region: regionToUse,
|
|
1286
1479
|
preferredProviderIds: options.preferredProviderIds,
|
|
@@ -1363,7 +1556,7 @@ export class RampsController extends BaseController {
|
|
|
1363
1556
|
// and place it at `success[0]`, since single-pick consumers
|
|
1364
1557
|
// (`getRampsQuote` -> `success?.[0]`) rely on index 0 while `success[]`
|
|
1365
1558
|
// order is server-defined rather than ranked.
|
|
1366
|
-
const selectedQuote =
|
|
1559
|
+
const selectedQuote = this.#pickWidenedQuote(response, {
|
|
1367
1560
|
amount: options.amount,
|
|
1368
1561
|
fiat: normalizedFiat,
|
|
1369
1562
|
providers: widenedProviderCatalog,
|
|
@@ -1387,6 +1580,261 @@ export class RampsController extends BaseController {
|
|
|
1387
1580
|
],
|
|
1388
1581
|
};
|
|
1389
1582
|
}
|
|
1583
|
+
/**
|
|
1584
|
+
* Selects the best quote from a widened multi-provider response.
|
|
1585
|
+
*
|
|
1586
|
+
* Every provider class is eligible (native, in-app WebView aggregator, and
|
|
1587
|
+
* external-browser / custom-action). When the feature flag payload carries a
|
|
1588
|
+
* provider allowlist, candidates from unlisted providers are dropped first.
|
|
1589
|
+
* Enforces per-provider fiat limits up front, then orders by reliability and
|
|
1590
|
+
* falls back to price using the server-provided `sorted` order. Returns
|
|
1591
|
+
* `undefined` when no quote survives.
|
|
1592
|
+
*
|
|
1593
|
+
* @param response - The multi-provider quotes response.
|
|
1594
|
+
* @param options - Selection inputs.
|
|
1595
|
+
* @param options.amount - Fiat amount, for the limit-fit check.
|
|
1596
|
+
* @param options.fiat - Lowercased fiat short code, for the limit lookup.
|
|
1597
|
+
* @param options.providers - Provider catalog for the limit lookup.
|
|
1598
|
+
* @param options.allowlist - Optional provider ids (either `/providers/x`
|
|
1599
|
+
* or bare form) the pick is restricted to.
|
|
1600
|
+
* @returns The selected quote, or `undefined` when none is usable.
|
|
1601
|
+
*/
|
|
1602
|
+
#pickWidenedQuote(response, { amount, fiat, providers, allowlist, }) {
|
|
1603
|
+
const providerByCode = new Map(providers.map((provider) => [provider.id, provider]));
|
|
1604
|
+
const allowedProviderIds = allowlist && allowlist.length > 0
|
|
1605
|
+
? new Set(allowlist.map(normalizeHeadlessProviderId))
|
|
1606
|
+
: undefined;
|
|
1607
|
+
const isAllowedProvider = (quote) => !allowedProviderIds ||
|
|
1608
|
+
allowedProviderIds.has(normalizeHeadlessProviderId(quote.provider));
|
|
1609
|
+
const fitsProviderLimits = (quote) => {
|
|
1610
|
+
const provider = providerByCode.get(quote.provider);
|
|
1611
|
+
const limit = provider?.limits?.fiat?.[fiat]?.[quote.quote.paymentMethod];
|
|
1612
|
+
if (!limit) {
|
|
1613
|
+
// No published limits for this provider/payment method: treat as
|
|
1614
|
+
// eligible and let the provider enforce limits at checkout.
|
|
1615
|
+
return true;
|
|
1616
|
+
}
|
|
1617
|
+
return amount >= limit.minAmount && amount <= limit.maxAmount;
|
|
1618
|
+
};
|
|
1619
|
+
const candidates = response.success.filter((quote) => isAllowedProvider(quote) && fitsProviderLimits(quote));
|
|
1620
|
+
if (candidates.length === 0) {
|
|
1621
|
+
return undefined;
|
|
1622
|
+
}
|
|
1623
|
+
const candidateByCode = new Map(candidates.map((quote) => [quote.provider, quote]));
|
|
1624
|
+
const pickBySortOrder = (sortBy) => {
|
|
1625
|
+
const order = response.sorted.find((entry) => entry.sortBy === sortBy)?.ids;
|
|
1626
|
+
if (!order) {
|
|
1627
|
+
return undefined;
|
|
1628
|
+
}
|
|
1629
|
+
for (const providerId of order) {
|
|
1630
|
+
const match = candidateByCode.get(providerId);
|
|
1631
|
+
if (match) {
|
|
1632
|
+
return match;
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
return undefined;
|
|
1636
|
+
};
|
|
1637
|
+
// Reliability first, then price, then the first surviving candidate.
|
|
1638
|
+
return (pickBySortOrder('reliability') ??
|
|
1639
|
+
pickBySortOrder('price') ??
|
|
1640
|
+
candidates[0]);
|
|
1641
|
+
}
|
|
1642
|
+
/**
|
|
1643
|
+
* Returns the region's providers that support the given asset, plus the full
|
|
1644
|
+
* region provider list. Uses cached providers only when the request targets
|
|
1645
|
+
* the current region; otherwise fetches for the requested region, since
|
|
1646
|
+
* `getProviders` does not persist results for a non-current region. Does not
|
|
1647
|
+
* mutate state.
|
|
1648
|
+
*
|
|
1649
|
+
* @param options - The options.
|
|
1650
|
+
* @param options.assetId - CAIP-19 asset type identifier to resolve for.
|
|
1651
|
+
* @param options.region - Region to resolve providers for.
|
|
1652
|
+
* @returns The supporting providers and the full region provider list.
|
|
1653
|
+
*/
|
|
1654
|
+
async #getSupportingProvidersForRegion({ assetId, region, }) {
|
|
1655
|
+
const normalizedRegion = region.toLowerCase().trim();
|
|
1656
|
+
let providers;
|
|
1657
|
+
if (this.#isRegionCurrent(normalizedRegion) &&
|
|
1658
|
+
this.state.providers.data.length > 0) {
|
|
1659
|
+
providers = this.state.providers.data;
|
|
1660
|
+
}
|
|
1661
|
+
else {
|
|
1662
|
+
({ providers } = await this.getProviders(normalizedRegion));
|
|
1663
|
+
}
|
|
1664
|
+
// Case-insensitive CAIP-19 matching is shared with headless-buy consumers
|
|
1665
|
+
// via `getProvidersServingAsset`, so the controller and the UI region gate
|
|
1666
|
+
// cannot disagree about which providers serve the asset.
|
|
1667
|
+
const supporting = getProvidersServingAsset(providers, assetId);
|
|
1668
|
+
return { supporting, all: providers };
|
|
1669
|
+
}
|
|
1670
|
+
/**
|
|
1671
|
+
* Filters an explicitly-requested provider ID list down to those that support
|
|
1672
|
+
* the asset in the region. Used for headless-buy gating so an explicitly
|
|
1673
|
+
* passed provider that cannot serve the region/asset yields no providers
|
|
1674
|
+
* rather than being trusted blindly.
|
|
1675
|
+
*
|
|
1676
|
+
* @param options - The options.
|
|
1677
|
+
* @param options.providerIds - Explicitly requested provider IDs.
|
|
1678
|
+
* @param options.assetId - CAIP-19 asset type identifier to resolve for.
|
|
1679
|
+
* @param options.region - Region to resolve providers for.
|
|
1680
|
+
* @returns The subset of `providerIds` supporting the asset in the region.
|
|
1681
|
+
*/
|
|
1682
|
+
async #filterProviderIdsBySupport({ providerIds, assetId, region, }) {
|
|
1683
|
+
const { supporting } = await this.#getSupportingProvidersForRegion({
|
|
1684
|
+
assetId,
|
|
1685
|
+
region,
|
|
1686
|
+
});
|
|
1687
|
+
const supportingIds = new Set(supporting.map((provider) => provider.id));
|
|
1688
|
+
return providerIds.filter((id) => supportingIds.has(id));
|
|
1689
|
+
}
|
|
1690
|
+
/**
|
|
1691
|
+
* Resolves the provider IDs to use for a single quote request, scoped to the
|
|
1692
|
+
* given asset and region. Does not mutate `providers.selected` or any other
|
|
1693
|
+
* state.
|
|
1694
|
+
*
|
|
1695
|
+
* Resolves against the region's supporting providers using this precedence:
|
|
1696
|
+
* 1. The currently selected provider, if it is in the supporting set.
|
|
1697
|
+
* 2. The first preferred provider that supports the asset, where the
|
|
1698
|
+
* preference is taken from `preferredProviderIds` when supplied, otherwise
|
|
1699
|
+
* derived from the user's completed-order history (most recent first).
|
|
1700
|
+
* This takes priority over Transak Native to preserve an existing KYC
|
|
1701
|
+
* relationship.
|
|
1702
|
+
* 3. A native provider (e.g. Transak Native).
|
|
1703
|
+
* 4. The first supporting provider — unless `restrictToKnownOrNative` is set, in
|
|
1704
|
+
* which case no further provider is introduced and nothing is returned.
|
|
1705
|
+
*
|
|
1706
|
+
* When `restrictToKnownOrNative` is unset and no provider supports the asset, falls
|
|
1707
|
+
* back to all known provider IDs so the request behaves as if no
|
|
1708
|
+
* auto-selection occurred.
|
|
1709
|
+
*
|
|
1710
|
+
* @param options - The options.
|
|
1711
|
+
* @param options.assetId - CAIP-19 asset type identifier to resolve for.
|
|
1712
|
+
* @param options.region - Region to resolve providers for.
|
|
1713
|
+
* @param options.preferredProviderIds - Provider IDs to prefer, in order.
|
|
1714
|
+
* @param options.restrictToKnownOrNative - When true, resolve only a native provider
|
|
1715
|
+
* (or no providers when none supports the asset).
|
|
1716
|
+
* @returns Provider IDs for this request only.
|
|
1717
|
+
*/
|
|
1718
|
+
async #resolveProviderIdsForQuote({ assetId, region, preferredProviderIds, restrictToKnownOrNative, }) {
|
|
1719
|
+
const { supporting, all } = await this.#getSupportingProvidersForRegion({
|
|
1720
|
+
assetId,
|
|
1721
|
+
region,
|
|
1722
|
+
});
|
|
1723
|
+
// When not restricted and nothing supports the asset, behave as if no
|
|
1724
|
+
// auto-selection occurred (quote against all known providers).
|
|
1725
|
+
if (!restrictToKnownOrNative && supporting.length === 0) {
|
|
1726
|
+
return all.map((provider) => provider.id);
|
|
1727
|
+
}
|
|
1728
|
+
// 1. The currently selected provider, if it supports the asset.
|
|
1729
|
+
const { selected } = this.state.providers;
|
|
1730
|
+
if (selected &&
|
|
1731
|
+
supporting.some((provider) => provider.id === selected.id)) {
|
|
1732
|
+
return [selected.id];
|
|
1733
|
+
}
|
|
1734
|
+
// 2. A provider the user has transacted with before — from
|
|
1735
|
+
// `preferredProviderIds` when supplied, otherwise completed-order
|
|
1736
|
+
// history. Takes priority over Transak Native to preserve an existing
|
|
1737
|
+
// KYC relationship and avoid churn.
|
|
1738
|
+
const preferred = preferredProviderIds ?? this.#getPreferredProviderIdsFromOrders();
|
|
1739
|
+
for (const preferredId of preferred) {
|
|
1740
|
+
const match = supporting.find((provider) => provider.id === preferredId);
|
|
1741
|
+
if (match) {
|
|
1742
|
+
return [match.id];
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
// 3. A native provider (e.g. Transak Native).
|
|
1746
|
+
const nativeProvider = supporting.find((provider) => provider.type === 'native');
|
|
1747
|
+
if (nativeProvider) {
|
|
1748
|
+
return [nativeProvider.id];
|
|
1749
|
+
}
|
|
1750
|
+
// 4. Fallback. Under headless gating, introduce no other provider — return
|
|
1751
|
+
// nothing so the caller surfaces an "unavailable" state. Otherwise the
|
|
1752
|
+
// aggregator and all other providers are treated equally: first wins.
|
|
1753
|
+
if (restrictToKnownOrNative) {
|
|
1754
|
+
return [];
|
|
1755
|
+
}
|
|
1756
|
+
return [supporting[0].id];
|
|
1757
|
+
}
|
|
1758
|
+
/**
|
|
1759
|
+
* Resolves provider IDs that should contribute payment methods for a
|
|
1760
|
+
* quoting context. Mirrors {@link getQuotes} provider-set selection, with
|
|
1761
|
+
* one intentional difference on the widened path: when the all-providers
|
|
1762
|
+
* flag allowlist is non-empty, returns supporting providers intersected
|
|
1763
|
+
* with that allowlist (pick survivors for the picker). Does not mutate
|
|
1764
|
+
* state.
|
|
1765
|
+
*
|
|
1766
|
+
* @param options - Resolution inputs aligned with `getQuotes`.
|
|
1767
|
+
* @param options.assetId - CAIP-19 asset type identifier to resolve for.
|
|
1768
|
+
* @param options.region - Region to resolve providers for.
|
|
1769
|
+
* @param options.providers - Explicit provider IDs, when provided.
|
|
1770
|
+
* @param options.autoSelectProvider - Resolve providers like `getQuotes`.
|
|
1771
|
+
* @param options.preferredProviderIds - Preferred provider IDs in order.
|
|
1772
|
+
* @param options.restrictToKnownOrNativeProviders - Headless gating.
|
|
1773
|
+
* @returns Provider IDs for this request only.
|
|
1774
|
+
*/
|
|
1775
|
+
async #resolveProviderIdsForPaymentMethods({ assetId, region, providers, autoSelectProvider, preferredProviderIds, restrictToKnownOrNativeProviders, }) {
|
|
1776
|
+
const wantsAutoSelection = !providers &&
|
|
1777
|
+
(autoSelectProvider === true ||
|
|
1778
|
+
restrictToKnownOrNativeProviders === true);
|
|
1779
|
+
const { enabled: allProvidersEnabled, allowlist: providerAllowlist } = wantsAutoSelection
|
|
1780
|
+
? this.#resolveAllProvidersFlag()
|
|
1781
|
+
: { enabled: false, allowlist: undefined };
|
|
1782
|
+
const widenToAllProviders = wantsAutoSelection && allProvidersEnabled;
|
|
1783
|
+
if (providers) {
|
|
1784
|
+
return restrictToKnownOrNativeProviders
|
|
1785
|
+
? this.#filterProviderIdsBySupport({
|
|
1786
|
+
providerIds: providers,
|
|
1787
|
+
assetId,
|
|
1788
|
+
region,
|
|
1789
|
+
})
|
|
1790
|
+
: providers;
|
|
1791
|
+
}
|
|
1792
|
+
if (widenToAllProviders) {
|
|
1793
|
+
const { supporting } = await this.#getSupportingProvidersForRegion({
|
|
1794
|
+
assetId,
|
|
1795
|
+
region,
|
|
1796
|
+
});
|
|
1797
|
+
if (providerAllowlist && providerAllowlist.length > 0) {
|
|
1798
|
+
const allowedProviderIds = new Set(providerAllowlist.map(normalizeHeadlessProviderId));
|
|
1799
|
+
return supporting
|
|
1800
|
+
.filter((provider) => allowedProviderIds.has(normalizeHeadlessProviderId(provider.id)))
|
|
1801
|
+
.map((provider) => provider.id);
|
|
1802
|
+
}
|
|
1803
|
+
return supporting.map((provider) => provider.id);
|
|
1804
|
+
}
|
|
1805
|
+
if (autoSelectProvider || restrictToKnownOrNativeProviders) {
|
|
1806
|
+
return this.#resolveProviderIdsForQuote({
|
|
1807
|
+
assetId,
|
|
1808
|
+
region,
|
|
1809
|
+
preferredProviderIds,
|
|
1810
|
+
restrictToKnownOrNative: restrictToKnownOrNativeProviders,
|
|
1811
|
+
});
|
|
1812
|
+
}
|
|
1813
|
+
const selectedId = this.state.providers.selected?.id;
|
|
1814
|
+
return selectedId ? [selectedId] : [];
|
|
1815
|
+
}
|
|
1816
|
+
/**
|
|
1817
|
+
* Derives an ordered list of provider IDs from the user's completed-order
|
|
1818
|
+
* history, most recently completed first, with duplicates removed.
|
|
1819
|
+
*
|
|
1820
|
+
* Reads only this controller's own normalized order state, so it carries no
|
|
1821
|
+
* dependency on any client-specific order representation.
|
|
1822
|
+
*
|
|
1823
|
+
* @returns Provider IDs ordered by most recent completed order.
|
|
1824
|
+
*/
|
|
1825
|
+
#getPreferredProviderIdsFromOrders() {
|
|
1826
|
+
const orderedIds = [];
|
|
1827
|
+
const completedOrders = this.state.orders
|
|
1828
|
+
.filter((order) => order.status === RampsOrderStatus.Completed && order.provider?.id)
|
|
1829
|
+
.sort((orderA, orderB) => orderB.createdAt - orderA.createdAt);
|
|
1830
|
+
for (const order of completedOrders) {
|
|
1831
|
+
const id = order.provider?.id;
|
|
1832
|
+
if (id && !orderedIds.includes(id)) {
|
|
1833
|
+
orderedIds.push(id);
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
return orderedIds;
|
|
1837
|
+
}
|
|
1390
1838
|
// === ORDER MANAGEMENT ===
|
|
1391
1839
|
/**
|
|
1392
1840
|
* Adds or updates a V2 order in controller state.
|
|
@@ -1424,7 +1872,7 @@ export class RampsController extends BaseController {
|
|
|
1424
1872
|
this.update((state) => {
|
|
1425
1873
|
state.orders = state.orders.filter((order) => order.providerOrderId !== providerOrderId);
|
|
1426
1874
|
});
|
|
1427
|
-
|
|
1875
|
+
this.#orderPollingMeta.delete(providerOrderId);
|
|
1428
1876
|
}
|
|
1429
1877
|
/**
|
|
1430
1878
|
* Adds or updates a local autoramp last-seen cursor (e.g. after create).
|
|
@@ -1468,7 +1916,7 @@ export class RampsController extends BaseController {
|
|
|
1468
1916
|
const customerId = await this.resolveAutorampCustomerId();
|
|
1469
1917
|
const body = { ...request, customer_id: customerId };
|
|
1470
1918
|
const remote = await this.messenger.call('NeoBankService:createAutoramp', body, options);
|
|
1471
|
-
return
|
|
1919
|
+
return this.#applyAutorampRemoteSnapshot(remote);
|
|
1472
1920
|
}
|
|
1473
1921
|
/**
|
|
1474
1922
|
* Resolves the vendor `customer_id` for autoramp / Money Account operations.
|
|
@@ -1687,7 +2135,7 @@ export class RampsController extends BaseController {
|
|
|
1687
2135
|
* @returns The updated local account.
|
|
1688
2136
|
*/
|
|
1689
2137
|
applyAutorampStatusFromPush(remote) {
|
|
1690
|
-
return
|
|
2138
|
+
return this.#applyAutorampRemoteSnapshot(remote);
|
|
1691
2139
|
}
|
|
1692
2140
|
/**
|
|
1693
2141
|
* Fetches one autoramp from the neo-bank proxy and applies it to the
|
|
@@ -1700,7 +2148,7 @@ export class RampsController extends BaseController {
|
|
|
1700
2148
|
*/
|
|
1701
2149
|
async refreshAutoramp(autorampId) {
|
|
1702
2150
|
const remote = await this.messenger.call('NeoBankService:getAutoramp', autorampId);
|
|
1703
|
-
return
|
|
2151
|
+
return this.#applyAutorampRemoteSnapshot(remote, { allowInsert: false });
|
|
1704
2152
|
}
|
|
1705
2153
|
/**
|
|
1706
2154
|
* Refreshes all known local autoramps from MoonPay.
|
|
@@ -1724,27 +2172,137 @@ export class RampsController extends BaseController {
|
|
|
1724
2172
|
}
|
|
1725
2173
|
return updated;
|
|
1726
2174
|
}
|
|
2175
|
+
/**
|
|
2176
|
+
* Applies a remote snapshot to the last-seen cursor.
|
|
2177
|
+
*
|
|
2178
|
+
* @param remote - MoonPay snapshot.
|
|
2179
|
+
* @param options - Apply options.
|
|
2180
|
+
* @param options.allowInsert - When false, a missing local cursor is not
|
|
2181
|
+
* recreated (used by refresh so a concurrent `removeAutoramp` wins).
|
|
2182
|
+
* @returns The applied account. When `allowInsert` is false and no local
|
|
2183
|
+
* cursor exists, the snapshot is returned without writing state.
|
|
2184
|
+
*/
|
|
2185
|
+
#applyAutorampRemoteSnapshot(remote, { allowInsert = true } = {}) {
|
|
2186
|
+
const local = this.state.autoramps.find((autoramp) => autoramp.id === remote.id) ??
|
|
2187
|
+
null;
|
|
2188
|
+
if (local === null && !allowInsert) {
|
|
2189
|
+
return applyAutorampRemoteStatus(null, remote).account;
|
|
2190
|
+
}
|
|
2191
|
+
const result = applyAutorampRemoteStatus(local, remote);
|
|
2192
|
+
this.update((state) => {
|
|
2193
|
+
const idx = state.autoramps.findIndex((autoramp) => autoramp.id === result.account.id);
|
|
2194
|
+
if (idx === -1) {
|
|
2195
|
+
state.autoramps.push(result.account);
|
|
2196
|
+
}
|
|
2197
|
+
else {
|
|
2198
|
+
state.autoramps[idx] = result.account;
|
|
2199
|
+
}
|
|
2200
|
+
});
|
|
2201
|
+
if (result.statusChanged) {
|
|
2202
|
+
this.messenger.publish('RampsController:autorampStatusChanged', {
|
|
2203
|
+
autoramp: result.account,
|
|
2204
|
+
previousStatus: result.previousStatus,
|
|
2205
|
+
shouldNotify: result.shouldNotify,
|
|
2206
|
+
});
|
|
2207
|
+
}
|
|
2208
|
+
return (this.state.autoramps.find((autoramp) => autoramp.id === result.account.id) ?? result.account);
|
|
2209
|
+
}
|
|
2210
|
+
/**
|
|
2211
|
+
* Refreshes a single order via the V2 API and updates it in state.
|
|
2212
|
+
* Publishes orderStatusChanged if the status transitioned.
|
|
2213
|
+
*
|
|
2214
|
+
* @param order - The order to refresh (needs provider and providerOrderId).
|
|
2215
|
+
*/
|
|
2216
|
+
async #refreshOrder(order) {
|
|
2217
|
+
const providerCode = order.provider?.id ?? '';
|
|
2218
|
+
if (!providerCode || !order.providerOrderId || !order.walletAddress) {
|
|
2219
|
+
return;
|
|
2220
|
+
}
|
|
2221
|
+
const previousStatus = order.status;
|
|
2222
|
+
try {
|
|
2223
|
+
const updatedOrder = await this.getOrder(providerCode, order.providerOrderId, order.walletAddress);
|
|
2224
|
+
const meta = this.#orderPollingMeta.get(order.providerOrderId) ?? {
|
|
2225
|
+
lastTimeFetched: 0,
|
|
2226
|
+
errorCount: 0,
|
|
2227
|
+
};
|
|
2228
|
+
if (updatedOrder.status === RampsOrderStatus.Unknown) {
|
|
2229
|
+
meta.errorCount = Math.min(meta.errorCount + 1, MAX_ERROR_COUNT);
|
|
2230
|
+
}
|
|
2231
|
+
else {
|
|
2232
|
+
meta.errorCount = 0;
|
|
2233
|
+
}
|
|
2234
|
+
meta.lastTimeFetched = Date.now();
|
|
2235
|
+
this.#orderPollingMeta.set(order.providerOrderId, meta);
|
|
2236
|
+
if (previousStatus !== updatedOrder.status &&
|
|
2237
|
+
previousStatus !== undefined) {
|
|
2238
|
+
this.messenger.publish('RampsController:orderStatusChanged', {
|
|
2239
|
+
order: updatedOrder,
|
|
2240
|
+
previousStatus,
|
|
2241
|
+
});
|
|
2242
|
+
}
|
|
2243
|
+
if (TERMINAL_ORDER_STATUSES.has(updatedOrder.status)) {
|
|
2244
|
+
this.#orderPollingMeta.delete(order.providerOrderId);
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
catch {
|
|
2248
|
+
const meta = this.#orderPollingMeta.get(order.providerOrderId) ?? {
|
|
2249
|
+
lastTimeFetched: 0,
|
|
2250
|
+
errorCount: 0,
|
|
2251
|
+
};
|
|
2252
|
+
meta.errorCount = Math.min(meta.errorCount + 1, MAX_ERROR_COUNT);
|
|
2253
|
+
meta.lastTimeFetched = Date.now();
|
|
2254
|
+
this.#orderPollingMeta.set(order.providerOrderId, meta);
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
1727
2257
|
/**
|
|
1728
2258
|
* Starts polling all pending V2 orders at a fixed interval.
|
|
1729
2259
|
* Each poll cycle iterates orders with non-terminal statuses,
|
|
1730
2260
|
* respects pollingSecondsMinimum and backoff from error count.
|
|
1731
2261
|
*/
|
|
1732
2262
|
startOrderPolling() {
|
|
1733
|
-
if (
|
|
2263
|
+
if (this.#orderPollingTimer) {
|
|
1734
2264
|
return;
|
|
1735
2265
|
}
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
}, DEFAULT_POLLING_INTERVAL_MS)
|
|
1739
|
-
|
|
2266
|
+
this.#orderPollingTimer = setInterval(() => {
|
|
2267
|
+
this.#pollPendingOrders().catch(() => undefined);
|
|
2268
|
+
}, DEFAULT_POLLING_INTERVAL_MS);
|
|
2269
|
+
this.#pollPendingOrders().catch(() => undefined);
|
|
1740
2270
|
}
|
|
1741
2271
|
/**
|
|
1742
2272
|
* Stops order polling and clears the interval.
|
|
1743
2273
|
*/
|
|
1744
2274
|
stopOrderPolling() {
|
|
1745
|
-
if (
|
|
1746
|
-
clearInterval(
|
|
1747
|
-
|
|
2275
|
+
if (this.#orderPollingTimer) {
|
|
2276
|
+
clearInterval(this.#orderPollingTimer);
|
|
2277
|
+
this.#orderPollingTimer = null;
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
async #pollPendingOrders() {
|
|
2281
|
+
if (this.#isPolling) {
|
|
2282
|
+
return;
|
|
2283
|
+
}
|
|
2284
|
+
this.#isPolling = true;
|
|
2285
|
+
try {
|
|
2286
|
+
const pendingOrders = this.state.orders.filter((order) => PENDING_ORDER_STATUSES.has(order.status));
|
|
2287
|
+
const now = Date.now();
|
|
2288
|
+
await Promise.allSettled(pendingOrders.map(async (order) => {
|
|
2289
|
+
const meta = this.#orderPollingMeta.get(order.providerOrderId);
|
|
2290
|
+
if (meta) {
|
|
2291
|
+
const backoffMs = meta.errorCount > 0
|
|
2292
|
+
? Math.min(DEFAULT_POLLING_INTERVAL_MS *
|
|
2293
|
+
Math.pow(2, meta.errorCount - 1), 5 * 60 * 1000)
|
|
2294
|
+
: 0;
|
|
2295
|
+
const pollingMinMs = (order.pollingSecondsMinimum ?? 0) * 1000;
|
|
2296
|
+
const minWait = Math.max(backoffMs, pollingMinMs);
|
|
2297
|
+
if (now - meta.lastTimeFetched < minWait) {
|
|
2298
|
+
return;
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
await this.#refreshOrder(order);
|
|
2302
|
+
}));
|
|
2303
|
+
}
|
|
2304
|
+
finally {
|
|
2305
|
+
this.#isPolling = false;
|
|
1748
2306
|
}
|
|
1749
2307
|
}
|
|
1750
2308
|
/**
|
|
@@ -1875,6 +2433,30 @@ export class RampsController extends BaseController {
|
|
|
1875
2433
|
const order = await this.messenger.call('RampsService:getOrderFromCallback', providerCode, callbackUrl, wallet);
|
|
1876
2434
|
return order;
|
|
1877
2435
|
}
|
|
2436
|
+
// === TRANSAK METHODS ===
|
|
2437
|
+
//
|
|
2438
|
+
// Auth state is managed at two levels:
|
|
2439
|
+
// - TransakService stores the access token (needed for API calls)
|
|
2440
|
+
// - RampsController stores isAuthenticated (needed for UI state)
|
|
2441
|
+
// Both are kept in sync by the controller methods below.
|
|
2442
|
+
/**
|
|
2443
|
+
* Checks whether an error is a 401 HTTP error (expired/missing token) and,
|
|
2444
|
+
* if so, marks the Transak session as unauthenticated so the UI stays in
|
|
2445
|
+
* sync with the cleared token inside TransakService.
|
|
2446
|
+
*
|
|
2447
|
+
* @param error - The caught error to inspect.
|
|
2448
|
+
*/
|
|
2449
|
+
#syncTransakAuthOnError(error) {
|
|
2450
|
+
if (hasHttpStatus(error) && error.httpStatus === 401) {
|
|
2451
|
+
this.transakSetAuthenticated(false);
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
#getNormalizedTransakError(error, options = {}) {
|
|
2455
|
+
if (options.syncAuth) {
|
|
2456
|
+
this.#syncTransakAuthOnError(error);
|
|
2457
|
+
}
|
|
2458
|
+
return getNormalizedRampsError(error);
|
|
2459
|
+
}
|
|
1878
2460
|
/**
|
|
1879
2461
|
* Sets the Transak API key used for all Transak API requests.
|
|
1880
2462
|
*
|
|
@@ -1930,7 +2512,7 @@ export class RampsController extends BaseController {
|
|
|
1930
2512
|
return await this.messenger.call('TransakService:sendUserOtp', email);
|
|
1931
2513
|
}
|
|
1932
2514
|
catch (error) {
|
|
1933
|
-
throw
|
|
2515
|
+
throw this.#getNormalizedTransakError(error).normalizedError;
|
|
1934
2516
|
}
|
|
1935
2517
|
}
|
|
1936
2518
|
/**
|
|
@@ -1949,7 +2531,7 @@ export class RampsController extends BaseController {
|
|
|
1949
2531
|
return token;
|
|
1950
2532
|
}
|
|
1951
2533
|
catch (error) {
|
|
1952
|
-
throw
|
|
2534
|
+
throw this.#getNormalizedTransakError(error).normalizedError;
|
|
1953
2535
|
}
|
|
1954
2536
|
}
|
|
1955
2537
|
/**
|
|
@@ -1963,7 +2545,7 @@ export class RampsController extends BaseController {
|
|
|
1963
2545
|
return await this.messenger.call('TransakService:logout');
|
|
1964
2546
|
}
|
|
1965
2547
|
catch (error) {
|
|
1966
|
-
throw
|
|
2548
|
+
throw this.#getNormalizedTransakError(error).normalizedError;
|
|
1967
2549
|
}
|
|
1968
2550
|
finally {
|
|
1969
2551
|
this.transakClearAccessToken();
|
|
@@ -1993,7 +2575,7 @@ export class RampsController extends BaseController {
|
|
|
1993
2575
|
return details;
|
|
1994
2576
|
}
|
|
1995
2577
|
catch (error) {
|
|
1996
|
-
const { errorInfo, normalizedError } =
|
|
2578
|
+
const { errorInfo, normalizedError } = this.#getNormalizedTransakError(error, {
|
|
1997
2579
|
syncAuth: true,
|
|
1998
2580
|
});
|
|
1999
2581
|
this.update((state) => {
|
|
@@ -2030,7 +2612,7 @@ export class RampsController extends BaseController {
|
|
|
2030
2612
|
return quote;
|
|
2031
2613
|
}
|
|
2032
2614
|
catch (error) {
|
|
2033
|
-
const { errorInfo, normalizedError } =
|
|
2615
|
+
const { errorInfo, normalizedError } = this.#getNormalizedTransakError(error);
|
|
2034
2616
|
this.update((state) => {
|
|
2035
2617
|
state.nativeProviders.transak.buyQuote.isLoading = false;
|
|
2036
2618
|
state.nativeProviders.transak.buyQuote.error = errorInfo.message;
|
|
@@ -2061,7 +2643,7 @@ export class RampsController extends BaseController {
|
|
|
2061
2643
|
return requirement;
|
|
2062
2644
|
}
|
|
2063
2645
|
catch (error) {
|
|
2064
|
-
const { errorInfo, normalizedError } =
|
|
2646
|
+
const { errorInfo, normalizedError } = this.#getNormalizedTransakError(error, {
|
|
2065
2647
|
syncAuth: true,
|
|
2066
2648
|
});
|
|
2067
2649
|
this.update((state) => {
|
|
@@ -2084,7 +2666,7 @@ export class RampsController extends BaseController {
|
|
|
2084
2666
|
return await this.messenger.call('TransakService:getAdditionalRequirements', quoteId);
|
|
2085
2667
|
}
|
|
2086
2668
|
catch (error) {
|
|
2087
|
-
throw
|
|
2669
|
+
throw this.#getNormalizedTransakError(error, { syncAuth: true })
|
|
2088
2670
|
.normalizedError;
|
|
2089
2671
|
}
|
|
2090
2672
|
}
|
|
@@ -2102,7 +2684,7 @@ export class RampsController extends BaseController {
|
|
|
2102
2684
|
return await this.messenger.call('TransakService:createOrder', quoteId, walletAddress, paymentMethodId);
|
|
2103
2685
|
}
|
|
2104
2686
|
catch (error) {
|
|
2105
|
-
throw
|
|
2687
|
+
throw this.#getNormalizedTransakError(error, { syncAuth: true })
|
|
2106
2688
|
.normalizedError;
|
|
2107
2689
|
}
|
|
2108
2690
|
}
|
|
@@ -2119,7 +2701,7 @@ export class RampsController extends BaseController {
|
|
|
2119
2701
|
return await this.messenger.call('TransakService:getOrder', orderId, wallet, paymentDetails);
|
|
2120
2702
|
}
|
|
2121
2703
|
catch (error) {
|
|
2122
|
-
throw
|
|
2704
|
+
throw this.#getNormalizedTransakError(error).normalizedError;
|
|
2123
2705
|
}
|
|
2124
2706
|
}
|
|
2125
2707
|
/**
|
|
@@ -2135,7 +2717,7 @@ export class RampsController extends BaseController {
|
|
|
2135
2717
|
return await this.messenger.call('TransakService:getUserLimits', fiatCurrency, paymentMethod, kycType);
|
|
2136
2718
|
}
|
|
2137
2719
|
catch (error) {
|
|
2138
|
-
throw
|
|
2720
|
+
throw this.#getNormalizedTransakError(error, { syncAuth: true })
|
|
2139
2721
|
.normalizedError;
|
|
2140
2722
|
}
|
|
2141
2723
|
}
|
|
@@ -2149,7 +2731,7 @@ export class RampsController extends BaseController {
|
|
|
2149
2731
|
return await this.messenger.call('TransakService:requestOtt');
|
|
2150
2732
|
}
|
|
2151
2733
|
catch (error) {
|
|
2152
|
-
throw
|
|
2734
|
+
throw this.#getNormalizedTransakError(error, { syncAuth: true })
|
|
2153
2735
|
.normalizedError;
|
|
2154
2736
|
}
|
|
2155
2737
|
}
|
|
@@ -2180,7 +2762,7 @@ export class RampsController extends BaseController {
|
|
|
2180
2762
|
return await this.messenger.call('TransakService:createWidgetUrl', quote, walletAddress, extraParams);
|
|
2181
2763
|
}
|
|
2182
2764
|
catch (error) {
|
|
2183
|
-
throw
|
|
2765
|
+
throw this.#getNormalizedTransakError(error, { syncAuth: true })
|
|
2184
2766
|
.normalizedError;
|
|
2185
2767
|
}
|
|
2186
2768
|
}
|
|
@@ -2195,7 +2777,7 @@ export class RampsController extends BaseController {
|
|
|
2195
2777
|
return await this.messenger.call('TransakService:submitPurposeOfUsageForm', purpose);
|
|
2196
2778
|
}
|
|
2197
2779
|
catch (error) {
|
|
2198
|
-
throw
|
|
2780
|
+
throw this.#getNormalizedTransakError(error, { syncAuth: true })
|
|
2199
2781
|
.normalizedError;
|
|
2200
2782
|
}
|
|
2201
2783
|
}
|
|
@@ -2210,7 +2792,7 @@ export class RampsController extends BaseController {
|
|
|
2210
2792
|
return await this.messenger.call('TransakService:patchUser', data);
|
|
2211
2793
|
}
|
|
2212
2794
|
catch (error) {
|
|
2213
|
-
throw
|
|
2795
|
+
throw this.#getNormalizedTransakError(error, { syncAuth: true })
|
|
2214
2796
|
.normalizedError;
|
|
2215
2797
|
}
|
|
2216
2798
|
}
|
|
@@ -2226,7 +2808,7 @@ export class RampsController extends BaseController {
|
|
|
2226
2808
|
return await this.messenger.call('TransakService:submitSsnDetails', ssn, quoteId);
|
|
2227
2809
|
}
|
|
2228
2810
|
catch (error) {
|
|
2229
|
-
throw
|
|
2811
|
+
throw this.#getNormalizedTransakError(error, { syncAuth: true })
|
|
2230
2812
|
.normalizedError;
|
|
2231
2813
|
}
|
|
2232
2814
|
}
|
|
@@ -2242,7 +2824,7 @@ export class RampsController extends BaseController {
|
|
|
2242
2824
|
return await this.messenger.call('TransakService:confirmPayment', orderId, paymentMethodId);
|
|
2243
2825
|
}
|
|
2244
2826
|
catch (error) {
|
|
2245
|
-
throw
|
|
2827
|
+
throw this.#getNormalizedTransakError(error, { syncAuth: true })
|
|
2246
2828
|
.normalizedError;
|
|
2247
2829
|
}
|
|
2248
2830
|
}
|
|
@@ -2257,7 +2839,7 @@ export class RampsController extends BaseController {
|
|
|
2257
2839
|
return await this.messenger.call('TransakService:getTranslation', request);
|
|
2258
2840
|
}
|
|
2259
2841
|
catch (error) {
|
|
2260
|
-
throw
|
|
2842
|
+
throw this.#getNormalizedTransakError(error).normalizedError;
|
|
2261
2843
|
}
|
|
2262
2844
|
}
|
|
2263
2845
|
/**
|
|
@@ -2271,7 +2853,7 @@ export class RampsController extends BaseController {
|
|
|
2271
2853
|
return await this.messenger.call('TransakService:getIdProofStatus', workFlowRunId);
|
|
2272
2854
|
}
|
|
2273
2855
|
catch (error) {
|
|
2274
|
-
throw
|
|
2856
|
+
throw this.#getNormalizedTransakError(error, { syncAuth: true })
|
|
2275
2857
|
.normalizedError;
|
|
2276
2858
|
}
|
|
2277
2859
|
}
|
|
@@ -2286,7 +2868,7 @@ export class RampsController extends BaseController {
|
|
|
2286
2868
|
return await this.messenger.call('TransakService:cancelOrder', depositOrderId);
|
|
2287
2869
|
}
|
|
2288
2870
|
catch (error) {
|
|
2289
|
-
throw
|
|
2871
|
+
throw this.#getNormalizedTransakError(error, { syncAuth: true })
|
|
2290
2872
|
.normalizedError;
|
|
2291
2873
|
}
|
|
2292
2874
|
}
|
|
@@ -2301,7 +2883,7 @@ export class RampsController extends BaseController {
|
|
|
2301
2883
|
return await this.messenger.call('TransakService:cancelAllActiveOrders');
|
|
2302
2884
|
}
|
|
2303
2885
|
catch (error) {
|
|
2304
|
-
throw
|
|
2886
|
+
throw this.#getNormalizedTransakError(error, { syncAuth: true })
|
|
2305
2887
|
.normalizedError;
|
|
2306
2888
|
}
|
|
2307
2889
|
}
|
|
@@ -2315,479 +2897,9 @@ export class RampsController extends BaseController {
|
|
|
2315
2897
|
return await this.messenger.call('TransakService:getActiveOrders');
|
|
2316
2898
|
}
|
|
2317
2899
|
catch (error) {
|
|
2318
|
-
throw
|
|
2900
|
+
throw this.#getNormalizedTransakError(error, { syncAuth: true })
|
|
2319
2901
|
.normalizedError;
|
|
2320
2902
|
}
|
|
2321
2903
|
}
|
|
2322
2904
|
}
|
|
2323
|
-
|
|
2324
|
-
for (const resourceType of DEPENDENT_RESOURCE_KEYS) {
|
|
2325
|
-
__classPrivateFieldGet(this, _RampsController_pendingResourceCount, "f").delete(resourceType);
|
|
2326
|
-
const generation = __classPrivateFieldGet(this, _RampsController_pendingResourceGeneration, "f").get(resourceType) ?? 0;
|
|
2327
|
-
__classPrivateFieldGet(this, _RampsController_pendingResourceGeneration, "f").set(resourceType, generation + 1);
|
|
2328
|
-
}
|
|
2329
|
-
}, _RampsController_abortDependentRequests = function _RampsController_abortDependentRequests() {
|
|
2330
|
-
for (const [cacheKey, pending] of __classPrivateFieldGet(this, _RampsController_pendingRequests, "f").entries()) {
|
|
2331
|
-
if (pending.resourceType &&
|
|
2332
|
-
DEPENDENT_RESOURCE_KEYS_SET.has(pending.resourceType)) {
|
|
2333
|
-
pending.abortController.abort();
|
|
2334
|
-
__classPrivateFieldGet(this, _RampsController_pendingRequests, "f").delete(cacheKey);
|
|
2335
|
-
__classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_removeRequestState).call(this, cacheKey);
|
|
2336
|
-
}
|
|
2337
|
-
}
|
|
2338
|
-
}, _RampsController_resolveAllProvidersFlag = function _RampsController_resolveAllProvidersFlag() {
|
|
2339
|
-
try {
|
|
2340
|
-
const remoteFeatureFlagState = this.messenger.call('RemoteFeatureFlagController:getState');
|
|
2341
|
-
return {
|
|
2342
|
-
enabled: isHeadlessAllProvidersEnabled(remoteFeatureFlagState),
|
|
2343
|
-
allowlist: getHeadlessProviderAllowlist(remoteFeatureFlagState),
|
|
2344
|
-
};
|
|
2345
|
-
}
|
|
2346
|
-
catch {
|
|
2347
|
-
return { enabled: false };
|
|
2348
|
-
}
|
|
2349
|
-
}, _RampsController_mutateRequests = function _RampsController_mutateRequests(fn) {
|
|
2350
|
-
this.update((state) => {
|
|
2351
|
-
const requests = state.requests;
|
|
2352
|
-
fn(requests);
|
|
2353
|
-
});
|
|
2354
|
-
}, _RampsController_removeRequestState = function _RampsController_removeRequestState(cacheKey) {
|
|
2355
|
-
__classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_mutateRequests).call(this, (requests) => {
|
|
2356
|
-
delete requests[cacheKey];
|
|
2357
|
-
});
|
|
2358
|
-
}, _RampsController_cleanupState = function _RampsController_cleanupState() {
|
|
2359
|
-
__classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_abortDependentRequests).call(this);
|
|
2360
|
-
__classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_clearPendingResourceCountForDependentResources).call(this);
|
|
2361
|
-
this.update((state) => resetDependentResources(state, { clearUserRegionData: true }));
|
|
2362
|
-
}, _RampsController_requireRegion = function _RampsController_requireRegion() {
|
|
2363
|
-
const regionCode = this.state.userRegion?.regionCode;
|
|
2364
|
-
if (!regionCode) {
|
|
2365
|
-
throw new Error('Region is required. Cannot proceed without valid region information.');
|
|
2366
|
-
}
|
|
2367
|
-
return regionCode;
|
|
2368
|
-
}, _RampsController_isRegionCurrent = function _RampsController_isRegionCurrent(normalizedRegion) {
|
|
2369
|
-
const current = this.state.userRegion?.regionCode;
|
|
2370
|
-
return current === undefined || current === normalizedRegion;
|
|
2371
|
-
}, _RampsController_isTokenCurrent = function _RampsController_isTokenCurrent(normalizedAssetId) {
|
|
2372
|
-
const current = this.state.tokens.selected?.assetId ?? '';
|
|
2373
|
-
return current === normalizedAssetId;
|
|
2374
|
-
}, _RampsController_isProviderCurrent = function _RampsController_isProviderCurrent(normalizedProviderId) {
|
|
2375
|
-
const current = this.state.providers.selected?.id ?? '';
|
|
2376
|
-
return current === normalizedProviderId;
|
|
2377
|
-
}, _RampsController_updateResourceField = function _RampsController_updateResourceField(resourceType, field, value) {
|
|
2378
|
-
this.update((state) => {
|
|
2379
|
-
const resource = getResourceState(state, resourceType);
|
|
2380
|
-
resource[field] = value;
|
|
2381
|
-
});
|
|
2382
|
-
}, _RampsController_setResourceLoading = function _RampsController_setResourceLoading(resourceType, loading) {
|
|
2383
|
-
__classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_updateResourceField).call(this, resourceType, 'isLoading', loading);
|
|
2384
|
-
}, _RampsController_setResourceError = function _RampsController_setResourceError(resourceType, errorInfo) {
|
|
2385
|
-
this.update((state) => {
|
|
2386
|
-
const resource = getResourceState(state, resourceType);
|
|
2387
|
-
resource.error = errorInfo?.message ?? null;
|
|
2388
|
-
resource.errorKey = errorInfo?.errorKey ?? null;
|
|
2389
|
-
});
|
|
2390
|
-
}, _RampsController_updateRequestState = function _RampsController_updateRequestState(cacheKey, requestState) {
|
|
2391
|
-
const maxSize = __classPrivateFieldGet(this, _RampsController_requestCacheMaxSize, "f");
|
|
2392
|
-
const ttl = __classPrivateFieldGet(this, _RampsController_requestCacheTTL, "f");
|
|
2393
|
-
__classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_mutateRequests).call(this, (requests) => {
|
|
2394
|
-
requests[cacheKey] = requestState;
|
|
2395
|
-
const keys = Object.keys(requests);
|
|
2396
|
-
for (const key of keys) {
|
|
2397
|
-
const entry = requests[key];
|
|
2398
|
-
if (entry?.status === RequestStatus.SUCCESS &&
|
|
2399
|
-
isCacheExpired(entry, ttl)) {
|
|
2400
|
-
delete requests[key];
|
|
2401
|
-
}
|
|
2402
|
-
}
|
|
2403
|
-
const remainingKeys = Object.keys(requests);
|
|
2404
|
-
if (remainingKeys.length > maxSize) {
|
|
2405
|
-
const sortedKeys = remainingKeys.sort((a, b) => {
|
|
2406
|
-
const aTime = requests[a]?.timestamp ?? 0;
|
|
2407
|
-
const bTime = requests[b]?.timestamp ?? 0;
|
|
2408
|
-
return aTime - bTime;
|
|
2409
|
-
});
|
|
2410
|
-
const entriesToRemove = remainingKeys.length - maxSize;
|
|
2411
|
-
for (let i = 0; i < entriesToRemove; i++) {
|
|
2412
|
-
const keyToRemove = sortedKeys[i];
|
|
2413
|
-
if (keyToRemove) {
|
|
2414
|
-
delete requests[keyToRemove];
|
|
2415
|
-
}
|
|
2416
|
-
}
|
|
2417
|
-
}
|
|
2418
|
-
});
|
|
2419
|
-
}, _RampsController_runInit = async function _RampsController_runInit(options) {
|
|
2420
|
-
// Force-refetch the catalog on startup so region preset amounts stay
|
|
2421
|
-
// current, bypassing the in-session request cache. The catalog is not
|
|
2422
|
-
// persisted, so a cold start always re-fetches it regardless.
|
|
2423
|
-
await this.getCountries({ ...options, forceRefresh: true });
|
|
2424
|
-
// Always prefer the user's persisted region. Geolocation is only used to
|
|
2425
|
-
// seed the initial value; once the user (or a prior init) has set a region
|
|
2426
|
-
// we must respect that choice — even on forceRefresh.
|
|
2427
|
-
const persistedRegionCode = this.state.userRegion?.regionCode;
|
|
2428
|
-
const regionCode = persistedRegionCode ??
|
|
2429
|
-
(await this.messenger.call('RampsService:getGeolocation'));
|
|
2430
|
-
if (!regionCode) {
|
|
2431
|
-
throw new Error('Failed to fetch geolocation. Cannot initialize controller without valid region information.');
|
|
2432
|
-
}
|
|
2433
|
-
// For an already-persisted region, getCountries() has already re-synced it
|
|
2434
|
-
// from the fresh catalog (see #syncUserRegionFromCountriesCatalog). Calling
|
|
2435
|
-
// setUserRegion here would re-validate against that catalog and, if it is
|
|
2436
|
-
// momentarily empty or no longer lists the region (e.g. a transient/partial
|
|
2437
|
-
// catalog response or a region with no current provider coverage), throw and
|
|
2438
|
-
// wipe the persisted region via #cleanupState. Preserve the existing region
|
|
2439
|
-
// instead; only resolve a brand-new region (from geolocation) strictly.
|
|
2440
|
-
if (persistedRegionCode) {
|
|
2441
|
-
return;
|
|
2442
|
-
}
|
|
2443
|
-
await this.setUserRegion(regionCode, options);
|
|
2444
|
-
}, _RampsController_syncUserRegionFromCountriesCatalog = function _RampsController_syncUserRegionFromCountriesCatalog() {
|
|
2445
|
-
const regionCode = this.state.userRegion?.regionCode;
|
|
2446
|
-
if (!regionCode) {
|
|
2447
|
-
return;
|
|
2448
|
-
}
|
|
2449
|
-
const countriesData = this.state.countries.data;
|
|
2450
|
-
if (!countriesData.length) {
|
|
2451
|
-
return;
|
|
2452
|
-
}
|
|
2453
|
-
const userRegion = findRegionFromCode(regionCode, countriesData);
|
|
2454
|
-
if (!userRegion) {
|
|
2455
|
-
return;
|
|
2456
|
-
}
|
|
2457
|
-
this.update((state) => {
|
|
2458
|
-
state.userRegion = userRegion;
|
|
2459
|
-
});
|
|
2460
|
-
}, _RampsController_pickWidenedQuote = function _RampsController_pickWidenedQuote(response, { amount, fiat, providers, allowlist, }) {
|
|
2461
|
-
const providerByCode = new Map(providers.map((provider) => [provider.id, provider]));
|
|
2462
|
-
const allowedProviderIds = allowlist && allowlist.length > 0
|
|
2463
|
-
? new Set(allowlist.map(normalizeHeadlessProviderId))
|
|
2464
|
-
: undefined;
|
|
2465
|
-
const isAllowedProvider = (quote) => !allowedProviderIds ||
|
|
2466
|
-
allowedProviderIds.has(normalizeHeadlessProviderId(quote.provider));
|
|
2467
|
-
const fitsProviderLimits = (quote) => {
|
|
2468
|
-
const provider = providerByCode.get(quote.provider);
|
|
2469
|
-
const limit = provider?.limits?.fiat?.[fiat]?.[quote.quote.paymentMethod];
|
|
2470
|
-
if (!limit) {
|
|
2471
|
-
// No published limits for this provider/payment method: treat as
|
|
2472
|
-
// eligible and let the provider enforce limits at checkout.
|
|
2473
|
-
return true;
|
|
2474
|
-
}
|
|
2475
|
-
return amount >= limit.minAmount && amount <= limit.maxAmount;
|
|
2476
|
-
};
|
|
2477
|
-
const candidates = response.success.filter((quote) => isAllowedProvider(quote) && fitsProviderLimits(quote));
|
|
2478
|
-
if (candidates.length === 0) {
|
|
2479
|
-
return undefined;
|
|
2480
|
-
}
|
|
2481
|
-
const candidateByCode = new Map(candidates.map((quote) => [quote.provider, quote]));
|
|
2482
|
-
const pickBySortOrder = (sortBy) => {
|
|
2483
|
-
const order = response.sorted.find((entry) => entry.sortBy === sortBy)?.ids;
|
|
2484
|
-
if (!order) {
|
|
2485
|
-
return undefined;
|
|
2486
|
-
}
|
|
2487
|
-
for (const providerId of order) {
|
|
2488
|
-
const match = candidateByCode.get(providerId);
|
|
2489
|
-
if (match) {
|
|
2490
|
-
return match;
|
|
2491
|
-
}
|
|
2492
|
-
}
|
|
2493
|
-
return undefined;
|
|
2494
|
-
};
|
|
2495
|
-
// Reliability first, then price, then the first surviving candidate.
|
|
2496
|
-
return (pickBySortOrder('reliability') ??
|
|
2497
|
-
pickBySortOrder('price') ??
|
|
2498
|
-
candidates[0]);
|
|
2499
|
-
}, _RampsController_getSupportingProvidersForRegion =
|
|
2500
|
-
/**
|
|
2501
|
-
* Returns the region's providers that support the given asset, plus the full
|
|
2502
|
-
* region provider list. Uses cached providers only when the request targets
|
|
2503
|
-
* the current region; otherwise fetches for the requested region, since
|
|
2504
|
-
* `getProviders` does not persist results for a non-current region. Does not
|
|
2505
|
-
* mutate state.
|
|
2506
|
-
*
|
|
2507
|
-
* @param options - The options.
|
|
2508
|
-
* @param options.assetId - CAIP-19 asset type identifier to resolve for.
|
|
2509
|
-
* @param options.region - Region to resolve providers for.
|
|
2510
|
-
* @returns The supporting providers and the full region provider list.
|
|
2511
|
-
*/
|
|
2512
|
-
async function _RampsController_getSupportingProvidersForRegion({ assetId, region, }) {
|
|
2513
|
-
const normalizedRegion = region.toLowerCase().trim();
|
|
2514
|
-
let providers;
|
|
2515
|
-
if (__classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_isRegionCurrent).call(this, normalizedRegion) &&
|
|
2516
|
-
this.state.providers.data.length > 0) {
|
|
2517
|
-
providers = this.state.providers.data;
|
|
2518
|
-
}
|
|
2519
|
-
else {
|
|
2520
|
-
({ providers } = await this.getProviders(normalizedRegion));
|
|
2521
|
-
}
|
|
2522
|
-
// Case-insensitive CAIP-19 matching is shared with headless-buy consumers
|
|
2523
|
-
// via `getProvidersServingAsset`, so the controller and the UI region gate
|
|
2524
|
-
// cannot disagree about which providers serve the asset.
|
|
2525
|
-
const supporting = getProvidersServingAsset(providers, assetId);
|
|
2526
|
-
return { supporting, all: providers };
|
|
2527
|
-
}, _RampsController_filterProviderIdsBySupport =
|
|
2528
|
-
/**
|
|
2529
|
-
* Filters an explicitly-requested provider ID list down to those that support
|
|
2530
|
-
* the asset in the region. Used for headless-buy gating so an explicitly
|
|
2531
|
-
* passed provider that cannot serve the region/asset yields no providers
|
|
2532
|
-
* rather than being trusted blindly.
|
|
2533
|
-
*
|
|
2534
|
-
* @param options - The options.
|
|
2535
|
-
* @param options.providerIds - Explicitly requested provider IDs.
|
|
2536
|
-
* @param options.assetId - CAIP-19 asset type identifier to resolve for.
|
|
2537
|
-
* @param options.region - Region to resolve providers for.
|
|
2538
|
-
* @returns The subset of `providerIds` supporting the asset in the region.
|
|
2539
|
-
*/
|
|
2540
|
-
async function _RampsController_filterProviderIdsBySupport({ providerIds, assetId, region, }) {
|
|
2541
|
-
const { supporting } = await __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_getSupportingProvidersForRegion).call(this, {
|
|
2542
|
-
assetId,
|
|
2543
|
-
region,
|
|
2544
|
-
});
|
|
2545
|
-
const supportingIds = new Set(supporting.map((provider) => provider.id));
|
|
2546
|
-
return providerIds.filter((id) => supportingIds.has(id));
|
|
2547
|
-
}, _RampsController_resolveProviderIdsForQuote =
|
|
2548
|
-
/**
|
|
2549
|
-
* Resolves the provider IDs to use for a single quote request, scoped to the
|
|
2550
|
-
* given asset and region. Does not mutate `providers.selected` or any other
|
|
2551
|
-
* state.
|
|
2552
|
-
*
|
|
2553
|
-
* Resolves against the region's supporting providers using this precedence:
|
|
2554
|
-
* 1. The currently selected provider, if it is in the supporting set.
|
|
2555
|
-
* 2. The first preferred provider that supports the asset, where the
|
|
2556
|
-
* preference is taken from `preferredProviderIds` when supplied, otherwise
|
|
2557
|
-
* derived from the user's completed-order history (most recent first).
|
|
2558
|
-
* This takes priority over Transak Native to preserve an existing KYC
|
|
2559
|
-
* relationship.
|
|
2560
|
-
* 3. A native provider (e.g. Transak Native).
|
|
2561
|
-
* 4. The first supporting provider — unless `restrictToKnownOrNative` is set, in
|
|
2562
|
-
* which case no further provider is introduced and nothing is returned.
|
|
2563
|
-
*
|
|
2564
|
-
* When `restrictToKnownOrNative` is unset and no provider supports the asset, falls
|
|
2565
|
-
* back to all known provider IDs so the request behaves as if no
|
|
2566
|
-
* auto-selection occurred.
|
|
2567
|
-
*
|
|
2568
|
-
* @param options - The options.
|
|
2569
|
-
* @param options.assetId - CAIP-19 asset type identifier to resolve for.
|
|
2570
|
-
* @param options.region - Region to resolve providers for.
|
|
2571
|
-
* @param options.preferredProviderIds - Provider IDs to prefer, in order.
|
|
2572
|
-
* @param options.restrictToKnownOrNative - When true, resolve only a native provider
|
|
2573
|
-
* (or no providers when none supports the asset).
|
|
2574
|
-
* @returns Provider IDs for this request only.
|
|
2575
|
-
*/
|
|
2576
|
-
async function _RampsController_resolveProviderIdsForQuote({ assetId, region, preferredProviderIds, restrictToKnownOrNative, }) {
|
|
2577
|
-
const { supporting, all } = await __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_getSupportingProvidersForRegion).call(this, {
|
|
2578
|
-
assetId,
|
|
2579
|
-
region,
|
|
2580
|
-
});
|
|
2581
|
-
// When not restricted and nothing supports the asset, behave as if no
|
|
2582
|
-
// auto-selection occurred (quote against all known providers).
|
|
2583
|
-
if (!restrictToKnownOrNative && supporting.length === 0) {
|
|
2584
|
-
return all.map((provider) => provider.id);
|
|
2585
|
-
}
|
|
2586
|
-
// 1. The currently selected provider, if it supports the asset.
|
|
2587
|
-
const { selected } = this.state.providers;
|
|
2588
|
-
if (selected &&
|
|
2589
|
-
supporting.some((provider) => provider.id === selected.id)) {
|
|
2590
|
-
return [selected.id];
|
|
2591
|
-
}
|
|
2592
|
-
// 2. A provider the user has transacted with before — from
|
|
2593
|
-
// `preferredProviderIds` when supplied, otherwise completed-order
|
|
2594
|
-
// history. Takes priority over Transak Native to preserve an existing
|
|
2595
|
-
// KYC relationship and avoid churn.
|
|
2596
|
-
const preferred = preferredProviderIds ?? __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_getPreferredProviderIdsFromOrders).call(this);
|
|
2597
|
-
for (const preferredId of preferred) {
|
|
2598
|
-
const match = supporting.find((provider) => provider.id === preferredId);
|
|
2599
|
-
if (match) {
|
|
2600
|
-
return [match.id];
|
|
2601
|
-
}
|
|
2602
|
-
}
|
|
2603
|
-
// 3. A native provider (e.g. Transak Native).
|
|
2604
|
-
const nativeProvider = supporting.find((provider) => provider.type === 'native');
|
|
2605
|
-
if (nativeProvider) {
|
|
2606
|
-
return [nativeProvider.id];
|
|
2607
|
-
}
|
|
2608
|
-
// 4. Fallback. Under headless gating, introduce no other provider — return
|
|
2609
|
-
// nothing so the caller surfaces an "unavailable" state. Otherwise the
|
|
2610
|
-
// aggregator and all other providers are treated equally: first wins.
|
|
2611
|
-
if (restrictToKnownOrNative) {
|
|
2612
|
-
return [];
|
|
2613
|
-
}
|
|
2614
|
-
return [supporting[0].id];
|
|
2615
|
-
}, _RampsController_resolveProviderIdsForPaymentMethods =
|
|
2616
|
-
/**
|
|
2617
|
-
* Resolves provider IDs that should contribute payment methods for a
|
|
2618
|
-
* quoting context. Mirrors {@link getQuotes} provider-set selection, with
|
|
2619
|
-
* one intentional difference on the widened path: when the all-providers
|
|
2620
|
-
* flag allowlist is non-empty, returns supporting providers intersected
|
|
2621
|
-
* with that allowlist (pick survivors for the picker). Does not mutate
|
|
2622
|
-
* state.
|
|
2623
|
-
*
|
|
2624
|
-
* @param options - Resolution inputs aligned with `getQuotes`.
|
|
2625
|
-
* @param options.assetId - CAIP-19 asset type identifier to resolve for.
|
|
2626
|
-
* @param options.region - Region to resolve providers for.
|
|
2627
|
-
* @param options.providers - Explicit provider IDs, when provided.
|
|
2628
|
-
* @param options.autoSelectProvider - Resolve providers like `getQuotes`.
|
|
2629
|
-
* @param options.preferredProviderIds - Preferred provider IDs in order.
|
|
2630
|
-
* @param options.restrictToKnownOrNativeProviders - Headless gating.
|
|
2631
|
-
* @returns Provider IDs for this request only.
|
|
2632
|
-
*/
|
|
2633
|
-
async function _RampsController_resolveProviderIdsForPaymentMethods({ assetId, region, providers, autoSelectProvider, preferredProviderIds, restrictToKnownOrNativeProviders, }) {
|
|
2634
|
-
const wantsAutoSelection = !providers &&
|
|
2635
|
-
(autoSelectProvider === true ||
|
|
2636
|
-
restrictToKnownOrNativeProviders === true);
|
|
2637
|
-
const { enabled: allProvidersEnabled, allowlist: providerAllowlist } = wantsAutoSelection
|
|
2638
|
-
? __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_resolveAllProvidersFlag).call(this)
|
|
2639
|
-
: { enabled: false, allowlist: undefined };
|
|
2640
|
-
const widenToAllProviders = wantsAutoSelection && allProvidersEnabled;
|
|
2641
|
-
if (providers) {
|
|
2642
|
-
return restrictToKnownOrNativeProviders
|
|
2643
|
-
? __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_filterProviderIdsBySupport).call(this, {
|
|
2644
|
-
providerIds: providers,
|
|
2645
|
-
assetId,
|
|
2646
|
-
region,
|
|
2647
|
-
})
|
|
2648
|
-
: providers;
|
|
2649
|
-
}
|
|
2650
|
-
if (widenToAllProviders) {
|
|
2651
|
-
const { supporting } = await __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_getSupportingProvidersForRegion).call(this, {
|
|
2652
|
-
assetId,
|
|
2653
|
-
region,
|
|
2654
|
-
});
|
|
2655
|
-
if (providerAllowlist && providerAllowlist.length > 0) {
|
|
2656
|
-
const allowedProviderIds = new Set(providerAllowlist.map(normalizeHeadlessProviderId));
|
|
2657
|
-
return supporting
|
|
2658
|
-
.filter((provider) => allowedProviderIds.has(normalizeHeadlessProviderId(provider.id)))
|
|
2659
|
-
.map((provider) => provider.id);
|
|
2660
|
-
}
|
|
2661
|
-
return supporting.map((provider) => provider.id);
|
|
2662
|
-
}
|
|
2663
|
-
if (autoSelectProvider || restrictToKnownOrNativeProviders) {
|
|
2664
|
-
return __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_resolveProviderIdsForQuote).call(this, {
|
|
2665
|
-
assetId,
|
|
2666
|
-
region,
|
|
2667
|
-
preferredProviderIds,
|
|
2668
|
-
restrictToKnownOrNative: restrictToKnownOrNativeProviders,
|
|
2669
|
-
});
|
|
2670
|
-
}
|
|
2671
|
-
const selectedId = this.state.providers.selected?.id;
|
|
2672
|
-
return selectedId ? [selectedId] : [];
|
|
2673
|
-
}, _RampsController_getPreferredProviderIdsFromOrders = function _RampsController_getPreferredProviderIdsFromOrders() {
|
|
2674
|
-
const orderedIds = [];
|
|
2675
|
-
const completedOrders = this.state.orders
|
|
2676
|
-
.filter((order) => order.status === RampsOrderStatus.Completed && order.provider?.id)
|
|
2677
|
-
.sort((orderA, orderB) => orderB.createdAt - orderA.createdAt);
|
|
2678
|
-
for (const order of completedOrders) {
|
|
2679
|
-
const id = order.provider?.id;
|
|
2680
|
-
if (id && !orderedIds.includes(id)) {
|
|
2681
|
-
orderedIds.push(id);
|
|
2682
|
-
}
|
|
2683
|
-
}
|
|
2684
|
-
return orderedIds;
|
|
2685
|
-
}, _RampsController_applyAutorampRemoteSnapshot = function _RampsController_applyAutorampRemoteSnapshot(remote, { allowInsert = true } = {}) {
|
|
2686
|
-
const local = this.state.autoramps.find((autoramp) => autoramp.id === remote.id) ??
|
|
2687
|
-
null;
|
|
2688
|
-
if (local === null && !allowInsert) {
|
|
2689
|
-
return applyAutorampRemoteStatus(null, remote).account;
|
|
2690
|
-
}
|
|
2691
|
-
const result = applyAutorampRemoteStatus(local, remote);
|
|
2692
|
-
this.update((state) => {
|
|
2693
|
-
const idx = state.autoramps.findIndex((autoramp) => autoramp.id === result.account.id);
|
|
2694
|
-
if (idx === -1) {
|
|
2695
|
-
state.autoramps.push(result.account);
|
|
2696
|
-
}
|
|
2697
|
-
else {
|
|
2698
|
-
state.autoramps[idx] = result.account;
|
|
2699
|
-
}
|
|
2700
|
-
});
|
|
2701
|
-
if (result.statusChanged) {
|
|
2702
|
-
this.messenger.publish('RampsController:autorampStatusChanged', {
|
|
2703
|
-
autoramp: result.account,
|
|
2704
|
-
previousStatus: result.previousStatus,
|
|
2705
|
-
shouldNotify: result.shouldNotify,
|
|
2706
|
-
});
|
|
2707
|
-
}
|
|
2708
|
-
return (this.state.autoramps.find((autoramp) => autoramp.id === result.account.id) ?? result.account);
|
|
2709
|
-
}, _RampsController_refreshOrder =
|
|
2710
|
-
/**
|
|
2711
|
-
* Refreshes a single order via the V2 API and updates it in state.
|
|
2712
|
-
* Publishes orderStatusChanged if the status transitioned.
|
|
2713
|
-
*
|
|
2714
|
-
* @param order - The order to refresh (needs provider and providerOrderId).
|
|
2715
|
-
*/
|
|
2716
|
-
async function _RampsController_refreshOrder(order) {
|
|
2717
|
-
const providerCode = order.provider?.id ?? '';
|
|
2718
|
-
if (!providerCode || !order.providerOrderId || !order.walletAddress) {
|
|
2719
|
-
return;
|
|
2720
|
-
}
|
|
2721
|
-
const previousStatus = order.status;
|
|
2722
|
-
try {
|
|
2723
|
-
const updatedOrder = await this.getOrder(providerCode, order.providerOrderId, order.walletAddress);
|
|
2724
|
-
const meta = __classPrivateFieldGet(this, _RampsController_orderPollingMeta, "f").get(order.providerOrderId) ?? {
|
|
2725
|
-
lastTimeFetched: 0,
|
|
2726
|
-
errorCount: 0,
|
|
2727
|
-
};
|
|
2728
|
-
if (updatedOrder.status === RampsOrderStatus.Unknown) {
|
|
2729
|
-
meta.errorCount = Math.min(meta.errorCount + 1, MAX_ERROR_COUNT);
|
|
2730
|
-
}
|
|
2731
|
-
else {
|
|
2732
|
-
meta.errorCount = 0;
|
|
2733
|
-
}
|
|
2734
|
-
meta.lastTimeFetched = Date.now();
|
|
2735
|
-
__classPrivateFieldGet(this, _RampsController_orderPollingMeta, "f").set(order.providerOrderId, meta);
|
|
2736
|
-
if (previousStatus !== updatedOrder.status &&
|
|
2737
|
-
previousStatus !== undefined) {
|
|
2738
|
-
this.messenger.publish('RampsController:orderStatusChanged', {
|
|
2739
|
-
order: updatedOrder,
|
|
2740
|
-
previousStatus,
|
|
2741
|
-
});
|
|
2742
|
-
}
|
|
2743
|
-
if (TERMINAL_ORDER_STATUSES.has(updatedOrder.status)) {
|
|
2744
|
-
__classPrivateFieldGet(this, _RampsController_orderPollingMeta, "f").delete(order.providerOrderId);
|
|
2745
|
-
}
|
|
2746
|
-
}
|
|
2747
|
-
catch {
|
|
2748
|
-
const meta = __classPrivateFieldGet(this, _RampsController_orderPollingMeta, "f").get(order.providerOrderId) ?? {
|
|
2749
|
-
lastTimeFetched: 0,
|
|
2750
|
-
errorCount: 0,
|
|
2751
|
-
};
|
|
2752
|
-
meta.errorCount = Math.min(meta.errorCount + 1, MAX_ERROR_COUNT);
|
|
2753
|
-
meta.lastTimeFetched = Date.now();
|
|
2754
|
-
__classPrivateFieldGet(this, _RampsController_orderPollingMeta, "f").set(order.providerOrderId, meta);
|
|
2755
|
-
}
|
|
2756
|
-
}, _RampsController_pollPendingOrders = async function _RampsController_pollPendingOrders() {
|
|
2757
|
-
if (__classPrivateFieldGet(this, _RampsController_isPolling, "f")) {
|
|
2758
|
-
return;
|
|
2759
|
-
}
|
|
2760
|
-
__classPrivateFieldSet(this, _RampsController_isPolling, true, "f");
|
|
2761
|
-
try {
|
|
2762
|
-
const pendingOrders = this.state.orders.filter((order) => PENDING_ORDER_STATUSES.has(order.status));
|
|
2763
|
-
const now = Date.now();
|
|
2764
|
-
await Promise.allSettled(pendingOrders.map(async (order) => {
|
|
2765
|
-
const meta = __classPrivateFieldGet(this, _RampsController_orderPollingMeta, "f").get(order.providerOrderId);
|
|
2766
|
-
if (meta) {
|
|
2767
|
-
const backoffMs = meta.errorCount > 0
|
|
2768
|
-
? Math.min(DEFAULT_POLLING_INTERVAL_MS *
|
|
2769
|
-
Math.pow(2, meta.errorCount - 1), 5 * 60 * 1000)
|
|
2770
|
-
: 0;
|
|
2771
|
-
const pollingMinMs = (order.pollingSecondsMinimum ?? 0) * 1000;
|
|
2772
|
-
const minWait = Math.max(backoffMs, pollingMinMs);
|
|
2773
|
-
if (now - meta.lastTimeFetched < minWait) {
|
|
2774
|
-
return;
|
|
2775
|
-
}
|
|
2776
|
-
}
|
|
2777
|
-
await __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_refreshOrder).call(this, order);
|
|
2778
|
-
}));
|
|
2779
|
-
}
|
|
2780
|
-
finally {
|
|
2781
|
-
__classPrivateFieldSet(this, _RampsController_isPolling, false, "f");
|
|
2782
|
-
}
|
|
2783
|
-
}, _RampsController_syncTransakAuthOnError = function _RampsController_syncTransakAuthOnError(error) {
|
|
2784
|
-
if (hasHttpStatus(error) && error.httpStatus === 401) {
|
|
2785
|
-
this.transakSetAuthenticated(false);
|
|
2786
|
-
}
|
|
2787
|
-
}, _RampsController_getNormalizedTransakError = function _RampsController_getNormalizedTransakError(error, options = {}) {
|
|
2788
|
-
if (options.syncAuth) {
|
|
2789
|
-
__classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_syncTransakAuthOnError).call(this, error);
|
|
2790
|
-
}
|
|
2791
|
-
return getNormalizedRampsError(error);
|
|
2792
|
-
};
|
|
2793
|
-
//# sourceMappingURL=RampsController.mjs.map
|
|
2905
|
+
//# sourceMappingURL=RampsController.js.map
|