@metamask/ramps-controller 20.3.0 → 22.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 +42 -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} +18 -3
- 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.mts → RampsController.d.ts} +86 -23
- package/dist/RampsController.d.ts.map +1 -0
- package/dist/{RampsController.mjs → RampsController.js} +976 -634
- 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} +11 -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} +37 -35
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/order-syncing/constants.d.ts +23 -0
- package/dist/order-syncing/constants.d.ts.map +1 -0
- package/dist/order-syncing/constants.js +23 -0
- package/dist/order-syncing/constants.js.map +1 -0
- package/dist/order-syncing/controller-integration.d.ts +91 -0
- package/dist/order-syncing/controller-integration.d.ts.map +1 -0
- package/dist/order-syncing/controller-integration.js +364 -0
- package/dist/order-syncing/controller-integration.js.map +1 -0
- package/dist/order-syncing/index.d.ts +7 -0
- package/dist/order-syncing/index.d.ts.map +1 -0
- package/dist/order-syncing/index.js +5 -0
- package/dist/order-syncing/index.js.map +1 -0
- package/dist/order-syncing/sync-utils.d.ts +9 -0
- package/dist/order-syncing/sync-utils.d.ts.map +1 -0
- package/dist/order-syncing/sync-utils.js +27 -0
- package/dist/order-syncing/sync-utils.js.map +1 -0
- package/dist/order-syncing/types.d.ts +66 -0
- package/dist/order-syncing/types.d.ts.map +1 -0
- package/dist/order-syncing/types.js +2 -0
- package/dist/order-syncing/types.js.map +1 -0
- package/dist/order-syncing/utils.d.ts +83 -0
- package/dist/order-syncing/utils.d.ts.map +1 -0
- package/dist/order-syncing/utils.js +144 -0
- package/dist/order-syncing/utils.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 +18 -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 +0 -968
- package/dist/RampsController.d.cts.map +0 -1
- 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,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
};
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
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 { areOrdersEqual, deleteOrderInUserStorage, syncOrdersWithUserStorage as syncOrdersWithUserStorageInternal, updateOrderInUserStorage, } from './order-syncing/index.js';
|
|
6
|
+
import { PENDING_ORDER_STATUSES, TERMINAL_ORDER_STATUSES, } from './orderStatus.js';
|
|
7
|
+
import { buildOwnershipMessage } from './ownership-message.js';
|
|
8
|
+
import { mergePaymentMethodsById, pickPaymentMethod, } from './paymentMethodMerge.js';
|
|
9
|
+
import { getProvidersServingAsset, normalizeRampsAssetId, providerServesAsset, } from './providerAvailability.js';
|
|
10
|
+
import { RAMPS_ERROR_CODES } from './rampsErrorCodes.js';
|
|
11
|
+
import { RampsOrderStatus } from './RampsService.js';
|
|
12
|
+
import { DEFAULT_REQUEST_CACHE_TTL, DEFAULT_REQUEST_CACHE_MAX_SIZE, createCacheKey, isCacheExpired, createLoadingState, createSuccessState, createErrorState, RequestStatus, } from './RequestCache.js';
|
|
13
|
+
import { createInitialState as createInitialWalletRegistrationState, transition as transitionWalletRegistration, } from './wallet-registration-machine.js';
|
|
14
|
+
import { createIdempotencyKey, WalletRegistrationError, } from './wallet-registration-service.js';
|
|
26
15
|
// === GENERAL ===
|
|
27
16
|
/**
|
|
28
17
|
* The name of the {@link RampsController}, used to namespace the
|
|
@@ -83,12 +72,17 @@ export const RAMPS_CONTROLLER_REQUIRED_SERVICE_ACTIONS = [
|
|
|
83
72
|
* `AuthenticationController:getSessionProfile` resolves the vendor customer
|
|
84
73
|
* identity from Profile Sync, and `KeyringController:signPersonalMessage` signs
|
|
85
74
|
* the EIP-191 ownership proof for Money Account self-hosted wallet
|
|
86
|
-
* registration; both are only exercised by the autoramp paths.
|
|
75
|
+
* registration; both are only exercised by the autoramp paths. User Storage
|
|
76
|
+
* and authentication actions support cross-client order syncing.
|
|
87
77
|
*/
|
|
88
78
|
export const RAMPS_CONTROLLER_REQUIRED_CONTROLLER_ACTIONS = [
|
|
89
79
|
'AuthenticationController:getSessionProfile',
|
|
80
|
+
'AuthenticationController:isSignedIn',
|
|
90
81
|
'KeyringController:signPersonalMessage',
|
|
91
82
|
'RemoteFeatureFlagController:getState',
|
|
83
|
+
'UserStorageController:getState',
|
|
84
|
+
'UserStorageController:performGetStorageAllFeatureEntries',
|
|
85
|
+
'UserStorageController:performBatchSetStorage',
|
|
92
86
|
];
|
|
93
87
|
/**
|
|
94
88
|
* Distinguishes an already-materialized {@link AutorampAccount} from the
|
|
@@ -381,18 +375,22 @@ function findRegionFromCode(regionCode, countries) {
|
|
|
381
375
|
*/
|
|
382
376
|
export function getInternalOrderCode(orderOrId) {
|
|
383
377
|
if (typeof orderOrId === 'string') {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
378
|
+
if (orderOrId.includes('/orders/')) {
|
|
379
|
+
return orderOrId.split('/orders/')[1]?.trim() || '';
|
|
380
|
+
}
|
|
381
|
+
return orderOrId.trim();
|
|
387
382
|
}
|
|
388
383
|
const { id, providerOrderId } = orderOrId;
|
|
389
384
|
if (id?.includes('/orders/')) {
|
|
390
|
-
|
|
385
|
+
const code = id.split('/orders/')[1]?.trim();
|
|
386
|
+
if (code) {
|
|
387
|
+
return code;
|
|
388
|
+
}
|
|
391
389
|
}
|
|
392
|
-
return providerOrderId;
|
|
390
|
+
return providerOrderId?.trim() ?? '';
|
|
393
391
|
}
|
|
394
392
|
// === ORDER POLLING CONSTANTS ===
|
|
395
|
-
const DEFAULT_POLLING_INTERVAL_MS =
|
|
393
|
+
const DEFAULT_POLLING_INTERVAL_MS = 30_000;
|
|
396
394
|
const MAX_ERROR_COUNT = 5;
|
|
397
395
|
// === CONTROLLER DEFINITION ===
|
|
398
396
|
const MESSENGER_EXPOSED_METHODS = [
|
|
@@ -454,6 +452,7 @@ const MESSENGER_EXPOSED_METHODS = [
|
|
|
454
452
|
'transakCancelOrder',
|
|
455
453
|
'transakCancelAllActiveOrders',
|
|
456
454
|
'transakGetActiveOrders',
|
|
455
|
+
'syncOrdersWithUserStorage',
|
|
457
456
|
];
|
|
458
457
|
/**
|
|
459
458
|
* Whether controller state still describes the context a payment-method
|
|
@@ -476,6 +475,104 @@ function contextStillMatches(state, context) {
|
|
|
476
475
|
(state.providers.selected?.id.trim() ?? '') === context.providerId);
|
|
477
476
|
}
|
|
478
477
|
export class RampsController extends BaseController {
|
|
478
|
+
/**
|
|
479
|
+
* Default TTL for cached requests.
|
|
480
|
+
*/
|
|
481
|
+
#requestCacheTTL;
|
|
482
|
+
/**
|
|
483
|
+
* Maximum number of entries in the request cache.
|
|
484
|
+
*/
|
|
485
|
+
#requestCacheMaxSize;
|
|
486
|
+
#onOrderSyncErroneousSituation;
|
|
487
|
+
#trace;
|
|
488
|
+
/**
|
|
489
|
+
* Map of pending requests for deduplication.
|
|
490
|
+
* Key is the cache key, value is the pending request with abort controller.
|
|
491
|
+
*/
|
|
492
|
+
#pendingRequests = new Map();
|
|
493
|
+
/**
|
|
494
|
+
* Count of in-flight requests per resource type.
|
|
495
|
+
* Used so isLoading is only cleared when the last request for that resource finishes.
|
|
496
|
+
*/
|
|
497
|
+
#pendingResourceCount = new Map();
|
|
498
|
+
/**
|
|
499
|
+
* Monotonic generation per resource type used to invalidate stale in-flight
|
|
500
|
+
* requests after region/token/provider dependent-resource resets.
|
|
501
|
+
*/
|
|
502
|
+
#pendingResourceGeneration = new Map();
|
|
503
|
+
#orderPollingMeta = new Map();
|
|
504
|
+
#orderPollingTimer = null;
|
|
505
|
+
#isPolling = false;
|
|
506
|
+
#initPromise = null;
|
|
507
|
+
/**
|
|
508
|
+
* Semaphore that prevents sync feedback loops while applying remote order changes.
|
|
509
|
+
*/
|
|
510
|
+
#isOrderSyncingInProgress = false;
|
|
511
|
+
/**
|
|
512
|
+
* Whether the full sync is applying its own local state changes.
|
|
513
|
+
*/
|
|
514
|
+
#isApplyingOrderSyncChanges = false;
|
|
515
|
+
/**
|
|
516
|
+
* Orders deleted locally while a full sync held the semaphore.
|
|
517
|
+
*/
|
|
518
|
+
#pendingRemoteDeletes = new Map();
|
|
519
|
+
/**
|
|
520
|
+
* Coalesces overlapping `syncOrdersWithUserStorage` calls into a follow-up run.
|
|
521
|
+
*/
|
|
522
|
+
#orderSyncQueued = false;
|
|
523
|
+
#orderSyncPromise = null;
|
|
524
|
+
/**
|
|
525
|
+
* Whether a full order sync is currently applying remote changes.
|
|
526
|
+
*
|
|
527
|
+
* @returns Whether order sync is in progress.
|
|
528
|
+
*/
|
|
529
|
+
get isOrderSyncingInProgress() {
|
|
530
|
+
return this.#isOrderSyncingInProgress;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Sets the order-syncing-in-progress semaphore.
|
|
534
|
+
* Used by the order-syncing module; hosts should not call this.
|
|
535
|
+
*
|
|
536
|
+
* @param value - Whether sync is in progress.
|
|
537
|
+
* @internal
|
|
538
|
+
*/
|
|
539
|
+
setIsOrderSyncingInProgress(value) {
|
|
540
|
+
this.#isOrderSyncingInProgress = value;
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* Distinguishes full sync's own state changes from external mutations.
|
|
544
|
+
*
|
|
545
|
+
* @param value - Whether sync changes are being applied.
|
|
546
|
+
* @internal
|
|
547
|
+
*/
|
|
548
|
+
setIsApplyingOrderSyncChanges(value) {
|
|
549
|
+
this.#isApplyingOrderSyncChanges = value;
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Returns orders deleted while a full sync was in progress.
|
|
553
|
+
* Used by the order-syncing module.
|
|
554
|
+
*
|
|
555
|
+
* @returns Pending deletes for remote tombstone upload.
|
|
556
|
+
* @internal
|
|
557
|
+
*/
|
|
558
|
+
getPendingRemoteDeletes() {
|
|
559
|
+
return [...this.#pendingRemoteDeletes.values()];
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Clears deletes whose tombstones were successfully persisted. A delete
|
|
563
|
+
* replaced while a write was in flight remains pending.
|
|
564
|
+
*
|
|
565
|
+
* @param orders - Deletes included in a successful remote write.
|
|
566
|
+
* @internal
|
|
567
|
+
*/
|
|
568
|
+
acknowledgePendingRemoteDeletes(orders) {
|
|
569
|
+
for (const order of orders) {
|
|
570
|
+
const key = getInternalOrderCode(order);
|
|
571
|
+
if (key && this.#pendingRemoteDeletes.get(key) === order) {
|
|
572
|
+
this.#pendingRemoteDeletes.delete(key);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
479
576
|
/**
|
|
480
577
|
* Clears the pending resource count map. Used only in tests to exercise the
|
|
481
578
|
* defensive path when get() returns undefined in the finally block.
|
|
@@ -483,7 +580,24 @@ export class RampsController extends BaseController {
|
|
|
483
580
|
* @internal
|
|
484
581
|
*/
|
|
485
582
|
clearPendingResourceCountForTest() {
|
|
486
|
-
|
|
583
|
+
this.#pendingResourceCount.clear();
|
|
584
|
+
}
|
|
585
|
+
#clearPendingResourceCountForDependentResources() {
|
|
586
|
+
for (const resourceType of DEPENDENT_RESOURCE_KEYS) {
|
|
587
|
+
this.#pendingResourceCount.delete(resourceType);
|
|
588
|
+
const generation = this.#pendingResourceGeneration.get(resourceType) ?? 0;
|
|
589
|
+
this.#pendingResourceGeneration.set(resourceType, generation + 1);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
#abortDependentRequests() {
|
|
593
|
+
for (const [cacheKey, pending] of this.#pendingRequests.entries()) {
|
|
594
|
+
if (pending.resourceType &&
|
|
595
|
+
DEPENDENT_RESOURCE_KEYS_SET.has(pending.resourceType)) {
|
|
596
|
+
pending.abortController.abort();
|
|
597
|
+
this.#pendingRequests.delete(cacheKey);
|
|
598
|
+
this.#removeRequestState(cacheKey);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
487
601
|
}
|
|
488
602
|
/**
|
|
489
603
|
* Constructs a new {@link RampsController}.
|
|
@@ -494,8 +608,10 @@ export class RampsController extends BaseController {
|
|
|
494
608
|
* controller. Missing properties will be filled in with defaults.
|
|
495
609
|
* @param args.requestCacheTTL - Time to live for cached requests in milliseconds.
|
|
496
610
|
* @param args.requestCacheMaxSize - Maximum number of entries in the request cache.
|
|
611
|
+
* @param args.onOrderSyncErroneousSituation - Optional order-sync error reporter.
|
|
612
|
+
* @param args.trace - Optional performance tracing callback for order sync.
|
|
497
613
|
*/
|
|
498
|
-
constructor({ messenger, state = {}, requestCacheTTL = DEFAULT_REQUEST_CACHE_TTL, requestCacheMaxSize = DEFAULT_REQUEST_CACHE_MAX_SIZE, }) {
|
|
614
|
+
constructor({ messenger, state = {}, requestCacheTTL = DEFAULT_REQUEST_CACHE_TTL, requestCacheMaxSize = DEFAULT_REQUEST_CACHE_MAX_SIZE, onOrderSyncErroneousSituation, trace, }) {
|
|
499
615
|
super({
|
|
500
616
|
messenger,
|
|
501
617
|
metadata: rampsControllerMetadata,
|
|
@@ -507,38 +623,41 @@ export class RampsController extends BaseController {
|
|
|
507
623
|
requests: {},
|
|
508
624
|
},
|
|
509
625
|
});
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
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");
|
|
626
|
+
this.#requestCacheTTL = requestCacheTTL;
|
|
627
|
+
this.#requestCacheMaxSize = requestCacheMaxSize;
|
|
628
|
+
this.#onOrderSyncErroneousSituation = onOrderSyncErroneousSituation;
|
|
629
|
+
this.#trace = trace;
|
|
540
630
|
this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
|
|
541
631
|
}
|
|
632
|
+
/**
|
|
633
|
+
* Resolves the Headless Buy all-providers feature flag: whether widening is
|
|
634
|
+
* enabled, and the provider-id allowlist the widened pick is restricted to
|
|
635
|
+
* (if the flag's object payload carries one).
|
|
636
|
+
*
|
|
637
|
+
* Reads `RemoteFeatureFlagController` state through the messenger on every
|
|
638
|
+
* call, so a remote flag fetch or a local dev override takes effect at
|
|
639
|
+
* runtime without reconstructing the controller. The single read keeps the
|
|
640
|
+
* enabled bit and the allowlist consistent even if the flag changes while a
|
|
641
|
+
* quote request is in flight. Key lookup, local-override merging, and value
|
|
642
|
+
* coercion live in the shared `isHeadlessAllProvidersEnabled` /
|
|
643
|
+
* `getHeadlessProviderAllowlist` helpers so UI consumers resolve the flag
|
|
644
|
+
* identically. Fails closed: when `RemoteFeatureFlagController:getState` is
|
|
645
|
+
* not wired up, quoting stays native-only.
|
|
646
|
+
*
|
|
647
|
+
* @returns The enabled bit and the optional provider-id allowlist.
|
|
648
|
+
*/
|
|
649
|
+
#resolveAllProvidersFlag() {
|
|
650
|
+
try {
|
|
651
|
+
const remoteFeatureFlagState = this.messenger.call('RemoteFeatureFlagController:getState');
|
|
652
|
+
return {
|
|
653
|
+
enabled: isHeadlessAllProvidersEnabled(remoteFeatureFlagState),
|
|
654
|
+
allowlist: getHeadlessProviderAllowlist(remoteFeatureFlagState),
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
catch {
|
|
658
|
+
return { enabled: false };
|
|
659
|
+
}
|
|
660
|
+
}
|
|
542
661
|
/**
|
|
543
662
|
* Executes a request with caching, deduplication, and at most one in-flight
|
|
544
663
|
* request per resource type.
|
|
@@ -565,11 +684,11 @@ export class RampsController extends BaseController {
|
|
|
565
684
|
*/
|
|
566
685
|
async executeRequest(cacheKey, fetcher, options) {
|
|
567
686
|
// Get TTL for verifying cache expiration
|
|
568
|
-
const ttl = options?.ttl ??
|
|
687
|
+
const ttl = options?.ttl ?? this.#requestCacheTTL;
|
|
569
688
|
// DEDUPLICATION:
|
|
570
689
|
// Check if a request is already in flight for this cache key
|
|
571
690
|
// If so, return the original promise for that request
|
|
572
|
-
const pending =
|
|
691
|
+
const pending = this.#pendingRequests.get(cacheKey);
|
|
573
692
|
if (pending) {
|
|
574
693
|
return pending.promise;
|
|
575
694
|
}
|
|
@@ -587,17 +706,17 @@ export class RampsController extends BaseController {
|
|
|
587
706
|
const lastFetchedAt = Date.now();
|
|
588
707
|
const { resourceType } = options ?? {};
|
|
589
708
|
const resourceGeneration = resourceType
|
|
590
|
-
? (
|
|
709
|
+
? (this.#pendingResourceGeneration.get(resourceType) ?? 0)
|
|
591
710
|
: undefined;
|
|
592
711
|
// Update state to loading
|
|
593
|
-
|
|
712
|
+
this.#updateRequestState(cacheKey, createLoadingState());
|
|
594
713
|
// Set resource-level loading state (only on cache miss). Ref-count so concurrent
|
|
595
714
|
// requests for the same resource type (different cache keys) keep isLoading true.
|
|
596
715
|
if (resourceType) {
|
|
597
|
-
const count =
|
|
598
|
-
|
|
716
|
+
const count = this.#pendingResourceCount.get(resourceType) ?? 0;
|
|
717
|
+
this.#pendingResourceCount.set(resourceType, count + 1);
|
|
599
718
|
if (count === 0) {
|
|
600
|
-
|
|
719
|
+
this.#setResourceLoading(resourceType, true);
|
|
601
720
|
}
|
|
602
721
|
}
|
|
603
722
|
// Create the fetch promise
|
|
@@ -607,11 +726,11 @@ export class RampsController extends BaseController {
|
|
|
607
726
|
if (abortController.signal.aborted) {
|
|
608
727
|
throw new Error('Request was aborted');
|
|
609
728
|
}
|
|
610
|
-
|
|
729
|
+
this.#updateRequestState(cacheKey, createSuccessState(data, lastFetchedAt));
|
|
611
730
|
if (resourceType) {
|
|
612
731
|
const isCurrent = !options?.isResultCurrent || options.isResultCurrent();
|
|
613
732
|
if (isCurrent) {
|
|
614
|
-
|
|
733
|
+
this.#setResourceError(resourceType, null);
|
|
615
734
|
}
|
|
616
735
|
}
|
|
617
736
|
return data;
|
|
@@ -621,38 +740,38 @@ export class RampsController extends BaseController {
|
|
|
621
740
|
throw error;
|
|
622
741
|
}
|
|
623
742
|
const { errorInfo, normalizedError } = getNormalizedRampsError(error);
|
|
624
|
-
|
|
743
|
+
this.#updateRequestState(cacheKey, createErrorState(errorInfo.message, lastFetchedAt, errorInfo.errorKey));
|
|
625
744
|
if (resourceType) {
|
|
626
745
|
const isCurrent = !options?.isResultCurrent || options.isResultCurrent();
|
|
627
746
|
if (isCurrent) {
|
|
628
|
-
|
|
747
|
+
this.#setResourceError(resourceType, errorInfo);
|
|
629
748
|
}
|
|
630
749
|
}
|
|
631
750
|
throw normalizedError;
|
|
632
751
|
}
|
|
633
752
|
finally {
|
|
634
|
-
if (
|
|
753
|
+
if (this.#pendingRequests.get(cacheKey)?.abortController ===
|
|
635
754
|
abortController) {
|
|
636
|
-
|
|
755
|
+
this.#pendingRequests.delete(cacheKey);
|
|
637
756
|
}
|
|
638
757
|
// Clear resource-level loading state only when no requests for this resource remain
|
|
639
758
|
if (resourceType && !abortController.signal.aborted) {
|
|
640
|
-
const currentGeneration =
|
|
759
|
+
const currentGeneration = this.#pendingResourceGeneration.get(resourceType) ?? 0;
|
|
641
760
|
if (currentGeneration === resourceGeneration) {
|
|
642
|
-
const count =
|
|
761
|
+
const count = this.#pendingResourceCount.get(resourceType) ?? 0;
|
|
643
762
|
const next = Math.max(0, count - 1);
|
|
644
763
|
if (next === 0) {
|
|
645
|
-
|
|
646
|
-
|
|
764
|
+
this.#pendingResourceCount.delete(resourceType);
|
|
765
|
+
this.#setResourceLoading(resourceType, false);
|
|
647
766
|
}
|
|
648
767
|
else {
|
|
649
|
-
|
|
768
|
+
this.#pendingResourceCount.set(resourceType, next);
|
|
650
769
|
}
|
|
651
770
|
}
|
|
652
771
|
}
|
|
653
772
|
}
|
|
654
773
|
})();
|
|
655
|
-
|
|
774
|
+
this.#pendingRequests.set(cacheKey, {
|
|
656
775
|
promise,
|
|
657
776
|
abortController,
|
|
658
777
|
resourceType,
|
|
@@ -666,15 +785,92 @@ export class RampsController extends BaseController {
|
|
|
666
785
|
* @returns True if a request was aborted.
|
|
667
786
|
*/
|
|
668
787
|
abortRequest(cacheKey) {
|
|
669
|
-
const pending =
|
|
788
|
+
const pending = this.#pendingRequests.get(cacheKey);
|
|
670
789
|
if (pending) {
|
|
671
790
|
pending.abortController.abort();
|
|
672
|
-
|
|
673
|
-
|
|
791
|
+
this.#pendingRequests.delete(cacheKey);
|
|
792
|
+
this.#removeRequestState(cacheKey);
|
|
674
793
|
return true;
|
|
675
794
|
}
|
|
676
795
|
return false;
|
|
677
796
|
}
|
|
797
|
+
/**
|
|
798
|
+
* Mutates state.requests inside update(); cast is centralized here.
|
|
799
|
+
*
|
|
800
|
+
* @param fn - Callback that mutates the requests record.
|
|
801
|
+
*/
|
|
802
|
+
#mutateRequests(fn) {
|
|
803
|
+
this.update((state) => {
|
|
804
|
+
const requests = state.requests;
|
|
805
|
+
fn(requests);
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
#removeRequestState(cacheKey) {
|
|
809
|
+
this.#mutateRequests((requests) => {
|
|
810
|
+
delete requests[cacheKey];
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
#cleanupState() {
|
|
814
|
+
this.#abortDependentRequests();
|
|
815
|
+
this.#clearPendingResourceCountForDependentResources();
|
|
816
|
+
this.update((state) => resetDependentResources(state, { clearUserRegionData: true }));
|
|
817
|
+
}
|
|
818
|
+
#requireRegion() {
|
|
819
|
+
const regionCode = this.state.userRegion?.regionCode;
|
|
820
|
+
if (!regionCode) {
|
|
821
|
+
throw new Error('Region is required. Cannot proceed without valid region information.');
|
|
822
|
+
}
|
|
823
|
+
return regionCode;
|
|
824
|
+
}
|
|
825
|
+
#isRegionCurrent(normalizedRegion) {
|
|
826
|
+
const current = this.state.userRegion?.regionCode;
|
|
827
|
+
return current === undefined || current === normalizedRegion;
|
|
828
|
+
}
|
|
829
|
+
#isTokenCurrent(normalizedAssetId) {
|
|
830
|
+
const current = this.state.tokens.selected?.assetId ?? '';
|
|
831
|
+
return current === normalizedAssetId;
|
|
832
|
+
}
|
|
833
|
+
#isProviderCurrent(normalizedProviderId) {
|
|
834
|
+
const current = this.state.providers.selected?.id ?? '';
|
|
835
|
+
return current === normalizedProviderId;
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* Updates a single field (isLoading or error) on a resource state.
|
|
839
|
+
* All resources share the same ResourceState structure, so we use
|
|
840
|
+
* dynamic property access to avoid duplicating switch statements.
|
|
841
|
+
*
|
|
842
|
+
* @param resourceType - The type of resource.
|
|
843
|
+
* @param field - The field to update ('isLoading' or 'error').
|
|
844
|
+
* @param value - The value to set.
|
|
845
|
+
*/
|
|
846
|
+
#updateResourceField(resourceType, field, value) {
|
|
847
|
+
this.update((state) => {
|
|
848
|
+
const resource = getResourceState(state, resourceType);
|
|
849
|
+
resource[field] = value;
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* Sets the loading state for a resource type.
|
|
854
|
+
*
|
|
855
|
+
* @param resourceType - The type of resource.
|
|
856
|
+
* @param loading - Whether the resource is loading.
|
|
857
|
+
*/
|
|
858
|
+
#setResourceLoading(resourceType, loading) {
|
|
859
|
+
this.#updateResourceField(resourceType, 'isLoading', loading);
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* Sets the error state for a resource type.
|
|
863
|
+
*
|
|
864
|
+
* @param resourceType - The type of resource.
|
|
865
|
+
* @param errorInfo - The error info, or null to clear.
|
|
866
|
+
*/
|
|
867
|
+
#setResourceError(resourceType, errorInfo) {
|
|
868
|
+
this.update((state) => {
|
|
869
|
+
const resource = getResourceState(state, resourceType);
|
|
870
|
+
resource.error = errorInfo?.message ?? null;
|
|
871
|
+
resource.errorKey = errorInfo?.errorKey ?? null;
|
|
872
|
+
});
|
|
873
|
+
}
|
|
678
874
|
/**
|
|
679
875
|
* Gets the state of a specific cached request.
|
|
680
876
|
*
|
|
@@ -684,6 +880,42 @@ export class RampsController extends BaseController {
|
|
|
684
880
|
getRequestState(cacheKey) {
|
|
685
881
|
return this.state.requests[cacheKey];
|
|
686
882
|
}
|
|
883
|
+
/**
|
|
884
|
+
* Updates the state for a specific request.
|
|
885
|
+
*
|
|
886
|
+
* @param cacheKey - The cache key.
|
|
887
|
+
* @param requestState - The new state for the request.
|
|
888
|
+
*/
|
|
889
|
+
#updateRequestState(cacheKey, requestState) {
|
|
890
|
+
const maxSize = this.#requestCacheMaxSize;
|
|
891
|
+
const ttl = this.#requestCacheTTL;
|
|
892
|
+
this.#mutateRequests((requests) => {
|
|
893
|
+
requests[cacheKey] = requestState;
|
|
894
|
+
const keys = Object.keys(requests);
|
|
895
|
+
for (const key of keys) {
|
|
896
|
+
const entry = requests[key];
|
|
897
|
+
if (entry?.status === RequestStatus.SUCCESS &&
|
|
898
|
+
isCacheExpired(entry, ttl)) {
|
|
899
|
+
delete requests[key];
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
const remainingKeys = Object.keys(requests);
|
|
903
|
+
if (remainingKeys.length > maxSize) {
|
|
904
|
+
const sortedKeys = remainingKeys.sort((a, b) => {
|
|
905
|
+
const aTime = requests[a]?.timestamp ?? 0;
|
|
906
|
+
const bTime = requests[b]?.timestamp ?? 0;
|
|
907
|
+
return aTime - bTime;
|
|
908
|
+
});
|
|
909
|
+
const entriesToRemove = remainingKeys.length - maxSize;
|
|
910
|
+
for (let i = 0; i < entriesToRemove; i++) {
|
|
911
|
+
const keyToRemove = sortedKeys[i];
|
|
912
|
+
if (keyToRemove) {
|
|
913
|
+
delete requests[keyToRemove];
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
});
|
|
918
|
+
}
|
|
687
919
|
/**
|
|
688
920
|
* Sets the user's region manually (without fetching geolocation).
|
|
689
921
|
* This allows users to override the detected region.
|
|
@@ -697,12 +929,12 @@ export class RampsController extends BaseController {
|
|
|
697
929
|
try {
|
|
698
930
|
const countriesData = this.state.countries.data;
|
|
699
931
|
if (!countriesData || countriesData.length === 0) {
|
|
700
|
-
|
|
932
|
+
this.#cleanupState();
|
|
701
933
|
throw new Error('No countries found. Cannot set user region without valid country information.');
|
|
702
934
|
}
|
|
703
935
|
const userRegion = findRegionFromCode(normalizedRegion, countriesData);
|
|
704
936
|
if (!userRegion) {
|
|
705
|
-
|
|
937
|
+
this.#cleanupState();
|
|
706
938
|
throw new Error(`Region "${normalizedRegion}" not found in countries data. Cannot set user region without valid country information.`);
|
|
707
939
|
}
|
|
708
940
|
const regionChanged = Boolean(options?.forceRefresh) ||
|
|
@@ -713,7 +945,7 @@ export class RampsController extends BaseController {
|
|
|
713
945
|
// user taps France → Finland → France quickly). Instead we let
|
|
714
946
|
// old requests complete naturally; isResultCurrent guards in
|
|
715
947
|
// getProviders/getPaymentMethods discard stale results.
|
|
716
|
-
|
|
948
|
+
this.#clearPendingResourceCountForDependentResources();
|
|
717
949
|
}
|
|
718
950
|
this.update((state) => {
|
|
719
951
|
if (regionChanged) {
|
|
@@ -724,7 +956,7 @@ export class RampsController extends BaseController {
|
|
|
724
956
|
return userRegion;
|
|
725
957
|
}
|
|
726
958
|
catch (error) {
|
|
727
|
-
|
|
959
|
+
this.#cleanupState();
|
|
728
960
|
throw error;
|
|
729
961
|
}
|
|
730
962
|
}
|
|
@@ -749,7 +981,7 @@ export class RampsController extends BaseController {
|
|
|
749
981
|
});
|
|
750
982
|
return;
|
|
751
983
|
}
|
|
752
|
-
|
|
984
|
+
this.#requireRegion();
|
|
753
985
|
// If a full Provider object is passed, store it directly (avoids
|
|
754
986
|
// depending on state.providers.data being populated).
|
|
755
987
|
if (typeof providerOrId !== 'string') {
|
|
@@ -829,21 +1061,68 @@ export class RampsController extends BaseController {
|
|
|
829
1061
|
* @returns Promise that resolves when initialization is complete.
|
|
830
1062
|
*/
|
|
831
1063
|
async init(options) {
|
|
832
|
-
if (!options?.forceRefresh &&
|
|
833
|
-
return
|
|
1064
|
+
if (!options?.forceRefresh && this.#initPromise !== null) {
|
|
1065
|
+
return this.#initPromise;
|
|
834
1066
|
}
|
|
835
1067
|
if (options?.forceRefresh) {
|
|
836
|
-
|
|
1068
|
+
this.#initPromise = null;
|
|
837
1069
|
}
|
|
838
|
-
const initPromise =
|
|
839
|
-
if (
|
|
840
|
-
|
|
1070
|
+
const initPromise = this.#runInit(options).then(() => undefined, (error) => {
|
|
1071
|
+
if (this.#initPromise === initPromise) {
|
|
1072
|
+
this.#initPromise = null;
|
|
841
1073
|
}
|
|
842
1074
|
throw error;
|
|
843
1075
|
});
|
|
844
|
-
|
|
1076
|
+
this.#initPromise = initPromise;
|
|
845
1077
|
return initPromise;
|
|
846
1078
|
}
|
|
1079
|
+
async #runInit(options) {
|
|
1080
|
+
// Force-refetch the catalog on startup so region preset amounts stay
|
|
1081
|
+
// current, bypassing the in-session request cache. The catalog is not
|
|
1082
|
+
// persisted, so a cold start always re-fetches it regardless.
|
|
1083
|
+
await this.getCountries({ ...options, forceRefresh: true });
|
|
1084
|
+
// Always prefer the user's persisted region. Geolocation is only used to
|
|
1085
|
+
// seed the initial value; once the user (or a prior init) has set a region
|
|
1086
|
+
// we must respect that choice — even on forceRefresh.
|
|
1087
|
+
const persistedRegionCode = this.state.userRegion?.regionCode;
|
|
1088
|
+
const regionCode = persistedRegionCode ??
|
|
1089
|
+
(await this.messenger.call('RampsService:getGeolocation'));
|
|
1090
|
+
if (!regionCode) {
|
|
1091
|
+
throw new Error('Failed to fetch geolocation. Cannot initialize controller without valid region information.');
|
|
1092
|
+
}
|
|
1093
|
+
// For an already-persisted region, getCountries() has already re-synced it
|
|
1094
|
+
// from the fresh catalog (see #syncUserRegionFromCountriesCatalog). Calling
|
|
1095
|
+
// setUserRegion here would re-validate against that catalog and, if it is
|
|
1096
|
+
// momentarily empty or no longer lists the region (e.g. a transient/partial
|
|
1097
|
+
// catalog response or a region with no current provider coverage), throw and
|
|
1098
|
+
// wipe the persisted region via #cleanupState. Preserve the existing region
|
|
1099
|
+
// instead; only resolve a brand-new region (from geolocation) strictly.
|
|
1100
|
+
if (persistedRegionCode) {
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1103
|
+
await this.setUserRegion(regionCode, options);
|
|
1104
|
+
}
|
|
1105
|
+
/**
|
|
1106
|
+
* Re-applies `userRegion` from the current countries catalog so preset
|
|
1107
|
+
* amounts and support flags stay in sync after a catalog refresh.
|
|
1108
|
+
*/
|
|
1109
|
+
#syncUserRegionFromCountriesCatalog() {
|
|
1110
|
+
const regionCode = this.state.userRegion?.regionCode;
|
|
1111
|
+
if (!regionCode) {
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1114
|
+
const countriesData = this.state.countries.data;
|
|
1115
|
+
if (!countriesData.length) {
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
const userRegion = findRegionFromCode(regionCode, countriesData);
|
|
1119
|
+
if (!userRegion) {
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
this.update((state) => {
|
|
1123
|
+
state.userRegion = userRegion;
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
847
1126
|
/**
|
|
848
1127
|
* Fetches the list of supported countries.
|
|
849
1128
|
* The API returns countries with support information for both buy and sell actions.
|
|
@@ -860,7 +1139,7 @@ export class RampsController extends BaseController {
|
|
|
860
1139
|
this.update((state) => {
|
|
861
1140
|
state.countries.data = Array.isArray(countries) ? [...countries] : [];
|
|
862
1141
|
});
|
|
863
|
-
|
|
1142
|
+
this.#syncUserRegionFromCountriesCatalog();
|
|
864
1143
|
return countries;
|
|
865
1144
|
}
|
|
866
1145
|
/**
|
|
@@ -874,7 +1153,7 @@ export class RampsController extends BaseController {
|
|
|
874
1153
|
* @returns The tokens response containing topTokens and allTokens.
|
|
875
1154
|
*/
|
|
876
1155
|
async getTokens(region, action = 'buy', options) {
|
|
877
|
-
const regionToUse = region ??
|
|
1156
|
+
const regionToUse = region ?? this.#requireRegion();
|
|
878
1157
|
const normalizedRegion = regionToUse.toLowerCase().trim();
|
|
879
1158
|
const cacheKey = createCacheKey('getTokens', [
|
|
880
1159
|
normalizedRegion,
|
|
@@ -888,7 +1167,7 @@ export class RampsController extends BaseController {
|
|
|
888
1167
|
}, {
|
|
889
1168
|
...options,
|
|
890
1169
|
resourceType: 'tokens',
|
|
891
|
-
isResultCurrent: () =>
|
|
1170
|
+
isResultCurrent: () => this.#isRegionCurrent(normalizedRegion),
|
|
892
1171
|
});
|
|
893
1172
|
this.update((state) => {
|
|
894
1173
|
const userRegionCode = state.userRegion?.regionCode;
|
|
@@ -913,7 +1192,7 @@ export class RampsController extends BaseController {
|
|
|
913
1192
|
});
|
|
914
1193
|
return;
|
|
915
1194
|
}
|
|
916
|
-
|
|
1195
|
+
this.#requireRegion();
|
|
917
1196
|
const tokens = this.state.tokens.data;
|
|
918
1197
|
if (!tokens) {
|
|
919
1198
|
throw new Error('Tokens not loaded. Cannot set selected token before tokens are fetched.');
|
|
@@ -939,7 +1218,7 @@ export class RampsController extends BaseController {
|
|
|
939
1218
|
* @returns The providers response containing providers array.
|
|
940
1219
|
*/
|
|
941
1220
|
async getProviders(region, options) {
|
|
942
|
-
const regionToUse = region ??
|
|
1221
|
+
const regionToUse = region ?? this.#requireRegion();
|
|
943
1222
|
const normalizedRegion = regionToUse.toLowerCase().trim();
|
|
944
1223
|
const cacheKey = createCacheKey('getProviders', [
|
|
945
1224
|
normalizedRegion,
|
|
@@ -956,7 +1235,7 @@ export class RampsController extends BaseController {
|
|
|
956
1235
|
}, {
|
|
957
1236
|
...options,
|
|
958
1237
|
resourceType: 'providers',
|
|
959
|
-
isResultCurrent: () =>
|
|
1238
|
+
isResultCurrent: () => this.#isRegionCurrent(normalizedRegion),
|
|
960
1239
|
});
|
|
961
1240
|
const { providers } = response;
|
|
962
1241
|
this.update((state) => {
|
|
@@ -978,7 +1257,7 @@ export class RampsController extends BaseController {
|
|
|
978
1257
|
* @returns The payment methods response containing payments array.
|
|
979
1258
|
*/
|
|
980
1259
|
async getPaymentMethods(region, options) {
|
|
981
|
-
const regionCode = region ??
|
|
1260
|
+
const regionCode = region ?? this.#requireRegion();
|
|
982
1261
|
const assetIdToUse = options?.assetId ?? this.state.tokens.selected?.assetId ?? '';
|
|
983
1262
|
const providerToUse = options?.provider ?? this.state.providers.selected?.id ?? '';
|
|
984
1263
|
const normalizedRegion = regionCode.toLowerCase().trim();
|
|
@@ -997,9 +1276,9 @@ export class RampsController extends BaseController {
|
|
|
997
1276
|
...options,
|
|
998
1277
|
resourceType: 'paymentMethods',
|
|
999
1278
|
isResultCurrent: () => {
|
|
1000
|
-
const regionMatch =
|
|
1001
|
-
const tokenMatch =
|
|
1002
|
-
const providerMatch =
|
|
1279
|
+
const regionMatch = this.#isRegionCurrent(normalizedRegion);
|
|
1280
|
+
const tokenMatch = this.#isTokenCurrent(assetIdToUse);
|
|
1281
|
+
const providerMatch = this.#isProviderCurrent(providerToUse);
|
|
1003
1282
|
return regionMatch && tokenMatch && providerMatch;
|
|
1004
1283
|
},
|
|
1005
1284
|
});
|
|
@@ -1062,7 +1341,7 @@ export class RampsController extends BaseController {
|
|
|
1062
1341
|
* provider ids that contributed.
|
|
1063
1342
|
*/
|
|
1064
1343
|
async getPaymentMethodsForContext(options) {
|
|
1065
|
-
const regionToUse = options.region ??
|
|
1344
|
+
const regionToUse = options.region ?? this.#requireRegion();
|
|
1066
1345
|
const normalizedRegion = regionToUse.toLowerCase().trim();
|
|
1067
1346
|
const assetId = options.assetId.trim();
|
|
1068
1347
|
if (assetId === '') {
|
|
@@ -1073,7 +1352,7 @@ export class RampsController extends BaseController {
|
|
|
1073
1352
|
const providerIdForState = options.providers?.length === 1
|
|
1074
1353
|
? options.providers[0].trim()
|
|
1075
1354
|
: (this.state.providers.selected?.id.trim() ?? '');
|
|
1076
|
-
const providerIds = await
|
|
1355
|
+
const providerIds = await this.#resolveProviderIdsForPaymentMethods({
|
|
1077
1356
|
assetId,
|
|
1078
1357
|
region: normalizedRegion,
|
|
1079
1358
|
providers: options.providers,
|
|
@@ -1225,7 +1504,7 @@ export class RampsController extends BaseController {
|
|
|
1225
1504
|
* @returns The quotes response containing success, sorted, error, and customActions.
|
|
1226
1505
|
*/
|
|
1227
1506
|
async getQuotes(options) {
|
|
1228
|
-
const regionToUse = options.region ??
|
|
1507
|
+
const regionToUse = options.region ?? this.#requireRegion();
|
|
1229
1508
|
const fiatToUse = options.fiat ?? this.state.userRegion?.country?.currency;
|
|
1230
1509
|
const paymentMethodsToUse = options.paymentMethods ??
|
|
1231
1510
|
this.state.paymentMethods.data.map((pm) => pm.id);
|
|
@@ -1250,14 +1529,14 @@ export class RampsController extends BaseController {
|
|
|
1250
1529
|
// the same `RemoteFeatureFlagController` state snapshot, so a flag edit
|
|
1251
1530
|
// during the awaited quote fetch cannot produce a mixed read.
|
|
1252
1531
|
const { enabled: allProvidersEnabled, allowlist: providerAllowlist } = wantsAutoSelection
|
|
1253
|
-
?
|
|
1532
|
+
? this.#resolveAllProvidersFlag()
|
|
1254
1533
|
: { enabled: false, allowlist: undefined };
|
|
1255
1534
|
const widenToAllProviders = wantsAutoSelection && allProvidersEnabled;
|
|
1256
1535
|
let providersToUse;
|
|
1257
1536
|
let widenedProviderCatalog = this.state.providers.data;
|
|
1258
1537
|
if (options.providers) {
|
|
1259
1538
|
providersToUse = options.restrictToKnownOrNativeProviders
|
|
1260
|
-
? await
|
|
1539
|
+
? await this.#filterProviderIdsBySupport({
|
|
1261
1540
|
providerIds: options.providers,
|
|
1262
1541
|
assetId: normalizedAssetIdForValidation,
|
|
1263
1542
|
region: regionToUse,
|
|
@@ -1268,7 +1547,7 @@ export class RampsController extends BaseController {
|
|
|
1268
1547
|
// `#getSupportingProvidersForRegion` also hydrates the provider catalog
|
|
1269
1548
|
// when controller state is empty, so all-provider quoting cannot silently
|
|
1270
1549
|
// return zero providers here.
|
|
1271
|
-
const { supporting } = await
|
|
1550
|
+
const { supporting } = await this.#getSupportingProvidersForRegion({
|
|
1272
1551
|
assetId: normalizedAssetIdForValidation,
|
|
1273
1552
|
region: regionToUse,
|
|
1274
1553
|
});
|
|
@@ -1280,7 +1559,7 @@ export class RampsController extends BaseController {
|
|
|
1280
1559
|
// The restriction flag implies resolution: it must narrow the provider
|
|
1281
1560
|
// set even when `autoSelectProvider` was not explicitly passed, otherwise
|
|
1282
1561
|
// it would be silently ignored and every provider quoted.
|
|
1283
|
-
providersToUse = await
|
|
1562
|
+
providersToUse = await this.#resolveProviderIdsForQuote({
|
|
1284
1563
|
assetId: normalizedAssetIdForValidation,
|
|
1285
1564
|
region: regionToUse,
|
|
1286
1565
|
preferredProviderIds: options.preferredProviderIds,
|
|
@@ -1363,7 +1642,7 @@ export class RampsController extends BaseController {
|
|
|
1363
1642
|
// and place it at `success[0]`, since single-pick consumers
|
|
1364
1643
|
// (`getRampsQuote` -> `success?.[0]`) rely on index 0 while `success[]`
|
|
1365
1644
|
// order is server-defined rather than ranked.
|
|
1366
|
-
const selectedQuote =
|
|
1645
|
+
const selectedQuote = this.#pickWidenedQuote(response, {
|
|
1367
1646
|
amount: options.amount,
|
|
1368
1647
|
fiat: normalizedFiat,
|
|
1369
1648
|
providers: widenedProviderCatalog,
|
|
@@ -1387,23 +1666,320 @@ export class RampsController extends BaseController {
|
|
|
1387
1666
|
],
|
|
1388
1667
|
};
|
|
1389
1668
|
}
|
|
1669
|
+
/**
|
|
1670
|
+
* Selects the best quote from a widened multi-provider response.
|
|
1671
|
+
*
|
|
1672
|
+
* Every provider class is eligible (native, in-app WebView aggregator, and
|
|
1673
|
+
* external-browser / custom-action). When the feature flag payload carries a
|
|
1674
|
+
* provider allowlist, candidates from unlisted providers are dropped first.
|
|
1675
|
+
* Enforces per-provider fiat limits up front, then orders by reliability and
|
|
1676
|
+
* falls back to price using the server-provided `sorted` order. Returns
|
|
1677
|
+
* `undefined` when no quote survives.
|
|
1678
|
+
*
|
|
1679
|
+
* @param response - The multi-provider quotes response.
|
|
1680
|
+
* @param options - Selection inputs.
|
|
1681
|
+
* @param options.amount - Fiat amount, for the limit-fit check.
|
|
1682
|
+
* @param options.fiat - Lowercased fiat short code, for the limit lookup.
|
|
1683
|
+
* @param options.providers - Provider catalog for the limit lookup.
|
|
1684
|
+
* @param options.allowlist - Optional provider ids (either `/providers/x`
|
|
1685
|
+
* or bare form) the pick is restricted to.
|
|
1686
|
+
* @returns The selected quote, or `undefined` when none is usable.
|
|
1687
|
+
*/
|
|
1688
|
+
#pickWidenedQuote(response, { amount, fiat, providers, allowlist, }) {
|
|
1689
|
+
const providerByCode = new Map(providers.map((provider) => [provider.id, provider]));
|
|
1690
|
+
const allowedProviderIds = allowlist && allowlist.length > 0
|
|
1691
|
+
? new Set(allowlist.map(normalizeHeadlessProviderId))
|
|
1692
|
+
: undefined;
|
|
1693
|
+
const isAllowedProvider = (quote) => !allowedProviderIds ||
|
|
1694
|
+
allowedProviderIds.has(normalizeHeadlessProviderId(quote.provider));
|
|
1695
|
+
const fitsProviderLimits = (quote) => {
|
|
1696
|
+
const provider = providerByCode.get(quote.provider);
|
|
1697
|
+
const limit = provider?.limits?.fiat?.[fiat]?.[quote.quote.paymentMethod];
|
|
1698
|
+
if (!limit) {
|
|
1699
|
+
// No published limits for this provider/payment method: treat as
|
|
1700
|
+
// eligible and let the provider enforce limits at checkout.
|
|
1701
|
+
return true;
|
|
1702
|
+
}
|
|
1703
|
+
return amount >= limit.minAmount && amount <= limit.maxAmount;
|
|
1704
|
+
};
|
|
1705
|
+
const candidates = response.success.filter((quote) => isAllowedProvider(quote) && fitsProviderLimits(quote));
|
|
1706
|
+
if (candidates.length === 0) {
|
|
1707
|
+
return undefined;
|
|
1708
|
+
}
|
|
1709
|
+
const candidateByCode = new Map(candidates.map((quote) => [quote.provider, quote]));
|
|
1710
|
+
const pickBySortOrder = (sortBy) => {
|
|
1711
|
+
const order = response.sorted.find((entry) => entry.sortBy === sortBy)?.ids;
|
|
1712
|
+
if (!order) {
|
|
1713
|
+
return undefined;
|
|
1714
|
+
}
|
|
1715
|
+
for (const providerId of order) {
|
|
1716
|
+
const match = candidateByCode.get(providerId);
|
|
1717
|
+
if (match) {
|
|
1718
|
+
return match;
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
return undefined;
|
|
1722
|
+
};
|
|
1723
|
+
// Reliability first, then price, then the first surviving candidate.
|
|
1724
|
+
return (pickBySortOrder('reliability') ??
|
|
1725
|
+
pickBySortOrder('price') ??
|
|
1726
|
+
candidates[0]);
|
|
1727
|
+
}
|
|
1728
|
+
/**
|
|
1729
|
+
* Returns the region's providers that support the given asset, plus the full
|
|
1730
|
+
* region provider list. Uses cached providers only when the request targets
|
|
1731
|
+
* the current region; otherwise fetches for the requested region, since
|
|
1732
|
+
* `getProviders` does not persist results for a non-current region. Does not
|
|
1733
|
+
* mutate state.
|
|
1734
|
+
*
|
|
1735
|
+
* @param options - The options.
|
|
1736
|
+
* @param options.assetId - CAIP-19 asset type identifier to resolve for.
|
|
1737
|
+
* @param options.region - Region to resolve providers for.
|
|
1738
|
+
* @returns The supporting providers and the full region provider list.
|
|
1739
|
+
*/
|
|
1740
|
+
async #getSupportingProvidersForRegion({ assetId, region, }) {
|
|
1741
|
+
const normalizedRegion = region.toLowerCase().trim();
|
|
1742
|
+
let providers;
|
|
1743
|
+
if (this.#isRegionCurrent(normalizedRegion) &&
|
|
1744
|
+
this.state.providers.data.length > 0) {
|
|
1745
|
+
providers = this.state.providers.data;
|
|
1746
|
+
}
|
|
1747
|
+
else {
|
|
1748
|
+
({ providers } = await this.getProviders(normalizedRegion));
|
|
1749
|
+
}
|
|
1750
|
+
// Case-insensitive CAIP-19 matching is shared with headless-buy consumers
|
|
1751
|
+
// via `getProvidersServingAsset`, so the controller and the UI region gate
|
|
1752
|
+
// cannot disagree about which providers serve the asset.
|
|
1753
|
+
const supporting = getProvidersServingAsset(providers, assetId);
|
|
1754
|
+
return { supporting, all: providers };
|
|
1755
|
+
}
|
|
1756
|
+
/**
|
|
1757
|
+
* Filters an explicitly-requested provider ID list down to those that support
|
|
1758
|
+
* the asset in the region. Used for headless-buy gating so an explicitly
|
|
1759
|
+
* passed provider that cannot serve the region/asset yields no providers
|
|
1760
|
+
* rather than being trusted blindly.
|
|
1761
|
+
*
|
|
1762
|
+
* @param options - The options.
|
|
1763
|
+
* @param options.providerIds - Explicitly requested provider IDs.
|
|
1764
|
+
* @param options.assetId - CAIP-19 asset type identifier to resolve for.
|
|
1765
|
+
* @param options.region - Region to resolve providers for.
|
|
1766
|
+
* @returns The subset of `providerIds` supporting the asset in the region.
|
|
1767
|
+
*/
|
|
1768
|
+
async #filterProviderIdsBySupport({ providerIds, assetId, region, }) {
|
|
1769
|
+
const { supporting } = await this.#getSupportingProvidersForRegion({
|
|
1770
|
+
assetId,
|
|
1771
|
+
region,
|
|
1772
|
+
});
|
|
1773
|
+
const supportingIds = new Set(supporting.map((provider) => provider.id));
|
|
1774
|
+
return providerIds.filter((id) => supportingIds.has(id));
|
|
1775
|
+
}
|
|
1776
|
+
/**
|
|
1777
|
+
* Resolves the provider IDs to use for a single quote request, scoped to the
|
|
1778
|
+
* given asset and region. Does not mutate `providers.selected` or any other
|
|
1779
|
+
* state.
|
|
1780
|
+
*
|
|
1781
|
+
* Resolves against the region's supporting providers using this precedence:
|
|
1782
|
+
* 1. The currently selected provider, if it is in the supporting set.
|
|
1783
|
+
* 2. The first preferred provider that supports the asset, where the
|
|
1784
|
+
* preference is taken from `preferredProviderIds` when supplied, otherwise
|
|
1785
|
+
* derived from the user's completed-order history (most recent first).
|
|
1786
|
+
* This takes priority over Transak Native to preserve an existing KYC
|
|
1787
|
+
* relationship.
|
|
1788
|
+
* 3. A native provider (e.g. Transak Native).
|
|
1789
|
+
* 4. The first supporting provider — unless `restrictToKnownOrNative` is set, in
|
|
1790
|
+
* which case no further provider is introduced and nothing is returned.
|
|
1791
|
+
*
|
|
1792
|
+
* When `restrictToKnownOrNative` is unset and no provider supports the asset, falls
|
|
1793
|
+
* back to all known provider IDs so the request behaves as if no
|
|
1794
|
+
* auto-selection occurred.
|
|
1795
|
+
*
|
|
1796
|
+
* @param options - The options.
|
|
1797
|
+
* @param options.assetId - CAIP-19 asset type identifier to resolve for.
|
|
1798
|
+
* @param options.region - Region to resolve providers for.
|
|
1799
|
+
* @param options.preferredProviderIds - Provider IDs to prefer, in order.
|
|
1800
|
+
* @param options.restrictToKnownOrNative - When true, resolve only a native provider
|
|
1801
|
+
* (or no providers when none supports the asset).
|
|
1802
|
+
* @returns Provider IDs for this request only.
|
|
1803
|
+
*/
|
|
1804
|
+
async #resolveProviderIdsForQuote({ assetId, region, preferredProviderIds, restrictToKnownOrNative, }) {
|
|
1805
|
+
const { supporting, all } = await this.#getSupportingProvidersForRegion({
|
|
1806
|
+
assetId,
|
|
1807
|
+
region,
|
|
1808
|
+
});
|
|
1809
|
+
// When not restricted and nothing supports the asset, behave as if no
|
|
1810
|
+
// auto-selection occurred (quote against all known providers).
|
|
1811
|
+
if (!restrictToKnownOrNative && supporting.length === 0) {
|
|
1812
|
+
return all.map((provider) => provider.id);
|
|
1813
|
+
}
|
|
1814
|
+
// 1. The currently selected provider, if it supports the asset.
|
|
1815
|
+
const { selected } = this.state.providers;
|
|
1816
|
+
if (selected &&
|
|
1817
|
+
supporting.some((provider) => provider.id === selected.id)) {
|
|
1818
|
+
return [selected.id];
|
|
1819
|
+
}
|
|
1820
|
+
// 2. A provider the user has transacted with before — from
|
|
1821
|
+
// `preferredProviderIds` when supplied, otherwise completed-order
|
|
1822
|
+
// history. Takes priority over Transak Native to preserve an existing
|
|
1823
|
+
// KYC relationship and avoid churn.
|
|
1824
|
+
const preferred = preferredProviderIds ?? this.#getPreferredProviderIdsFromOrders();
|
|
1825
|
+
for (const preferredId of preferred) {
|
|
1826
|
+
const match = supporting.find((provider) => provider.id === preferredId);
|
|
1827
|
+
if (match) {
|
|
1828
|
+
return [match.id];
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
// 3. A native provider (e.g. Transak Native).
|
|
1832
|
+
const nativeProvider = supporting.find((provider) => provider.type === 'native');
|
|
1833
|
+
if (nativeProvider) {
|
|
1834
|
+
return [nativeProvider.id];
|
|
1835
|
+
}
|
|
1836
|
+
// 4. Fallback. Under headless gating, introduce no other provider — return
|
|
1837
|
+
// nothing so the caller surfaces an "unavailable" state. Otherwise the
|
|
1838
|
+
// aggregator and all other providers are treated equally: first wins.
|
|
1839
|
+
if (restrictToKnownOrNative) {
|
|
1840
|
+
return [];
|
|
1841
|
+
}
|
|
1842
|
+
return [supporting[0].id];
|
|
1843
|
+
}
|
|
1844
|
+
/**
|
|
1845
|
+
* Resolves provider IDs that should contribute payment methods for a
|
|
1846
|
+
* quoting context. Mirrors {@link getQuotes} provider-set selection, with
|
|
1847
|
+
* one intentional difference on the widened path: when the all-providers
|
|
1848
|
+
* flag allowlist is non-empty, returns supporting providers intersected
|
|
1849
|
+
* with that allowlist (pick survivors for the picker). Does not mutate
|
|
1850
|
+
* state.
|
|
1851
|
+
*
|
|
1852
|
+
* @param options - Resolution inputs aligned with `getQuotes`.
|
|
1853
|
+
* @param options.assetId - CAIP-19 asset type identifier to resolve for.
|
|
1854
|
+
* @param options.region - Region to resolve providers for.
|
|
1855
|
+
* @param options.providers - Explicit provider IDs, when provided.
|
|
1856
|
+
* @param options.autoSelectProvider - Resolve providers like `getQuotes`.
|
|
1857
|
+
* @param options.preferredProviderIds - Preferred provider IDs in order.
|
|
1858
|
+
* @param options.restrictToKnownOrNativeProviders - Headless gating.
|
|
1859
|
+
* @returns Provider IDs for this request only.
|
|
1860
|
+
*/
|
|
1861
|
+
async #resolveProviderIdsForPaymentMethods({ assetId, region, providers, autoSelectProvider, preferredProviderIds, restrictToKnownOrNativeProviders, }) {
|
|
1862
|
+
const wantsAutoSelection = !providers &&
|
|
1863
|
+
(autoSelectProvider === true ||
|
|
1864
|
+
restrictToKnownOrNativeProviders === true);
|
|
1865
|
+
const { enabled: allProvidersEnabled, allowlist: providerAllowlist } = wantsAutoSelection
|
|
1866
|
+
? this.#resolveAllProvidersFlag()
|
|
1867
|
+
: { enabled: false, allowlist: undefined };
|
|
1868
|
+
const widenToAllProviders = wantsAutoSelection && allProvidersEnabled;
|
|
1869
|
+
if (providers) {
|
|
1870
|
+
return restrictToKnownOrNativeProviders
|
|
1871
|
+
? this.#filterProviderIdsBySupport({
|
|
1872
|
+
providerIds: providers,
|
|
1873
|
+
assetId,
|
|
1874
|
+
region,
|
|
1875
|
+
})
|
|
1876
|
+
: providers;
|
|
1877
|
+
}
|
|
1878
|
+
if (widenToAllProviders) {
|
|
1879
|
+
const { supporting } = await this.#getSupportingProvidersForRegion({
|
|
1880
|
+
assetId,
|
|
1881
|
+
region,
|
|
1882
|
+
});
|
|
1883
|
+
if (providerAllowlist && providerAllowlist.length > 0) {
|
|
1884
|
+
const allowedProviderIds = new Set(providerAllowlist.map(normalizeHeadlessProviderId));
|
|
1885
|
+
return supporting
|
|
1886
|
+
.filter((provider) => allowedProviderIds.has(normalizeHeadlessProviderId(provider.id)))
|
|
1887
|
+
.map((provider) => provider.id);
|
|
1888
|
+
}
|
|
1889
|
+
return supporting.map((provider) => provider.id);
|
|
1890
|
+
}
|
|
1891
|
+
if (autoSelectProvider || restrictToKnownOrNativeProviders) {
|
|
1892
|
+
return this.#resolveProviderIdsForQuote({
|
|
1893
|
+
assetId,
|
|
1894
|
+
region,
|
|
1895
|
+
preferredProviderIds,
|
|
1896
|
+
restrictToKnownOrNative: restrictToKnownOrNativeProviders,
|
|
1897
|
+
});
|
|
1898
|
+
}
|
|
1899
|
+
const selectedId = this.state.providers.selected?.id;
|
|
1900
|
+
return selectedId ? [selectedId] : [];
|
|
1901
|
+
}
|
|
1902
|
+
/**
|
|
1903
|
+
* Derives an ordered list of provider IDs from the user's completed-order
|
|
1904
|
+
* history, most recently completed first, with duplicates removed.
|
|
1905
|
+
*
|
|
1906
|
+
* Reads only this controller's own normalized order state, so it carries no
|
|
1907
|
+
* dependency on any client-specific order representation.
|
|
1908
|
+
*
|
|
1909
|
+
* @returns Provider IDs ordered by most recent completed order.
|
|
1910
|
+
*/
|
|
1911
|
+
#getPreferredProviderIdsFromOrders() {
|
|
1912
|
+
const orderedIds = [];
|
|
1913
|
+
const completedOrders = this.state.orders
|
|
1914
|
+
.filter((order) => order.status === RampsOrderStatus.Completed && order.provider?.id)
|
|
1915
|
+
.sort((orderA, orderB) => orderB.createdAt - orderA.createdAt);
|
|
1916
|
+
for (const order of completedOrders) {
|
|
1917
|
+
const id = order.provider?.id;
|
|
1918
|
+
if (id && !orderedIds.includes(id)) {
|
|
1919
|
+
orderedIds.push(id);
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
return orderedIds;
|
|
1923
|
+
}
|
|
1390
1924
|
// === ORDER MANAGEMENT ===
|
|
1391
1925
|
/**
|
|
1392
1926
|
* Adds or updates a V2 order in controller state.
|
|
1393
1927
|
* If an order with the same internal order code already exists, the incoming
|
|
1394
1928
|
* fields are merged on top of the existing order so that fields not present
|
|
1395
1929
|
* in the update (e.g. paymentDetails from the Transak API) are preserved.
|
|
1930
|
+
* Unchanged syncable payloads (including unchanged poll results) are ignored
|
|
1931
|
+
* so `lastUpdatedAt` is not bumped and User Storage is not rewritten.
|
|
1396
1932
|
*
|
|
1397
1933
|
* @param order - The RampsOrder to add or update.
|
|
1398
1934
|
*/
|
|
1399
1935
|
addOrder(order) {
|
|
1400
1936
|
const internalOrderCode = getInternalOrderCode(order);
|
|
1937
|
+
if (!internalOrderCode) {
|
|
1938
|
+
this.#onOrderSyncErroneousSituation?.('Unable to derive internal order code for addOrder', {});
|
|
1939
|
+
return;
|
|
1940
|
+
}
|
|
1941
|
+
const existing = this.state.orders.find((existingOrder) => getInternalOrderCode(existingOrder) === internalOrderCode);
|
|
1942
|
+
if (existing &&
|
|
1943
|
+
!this.#isApplyingOrderSyncChanges &&
|
|
1944
|
+
areOrdersEqual(existing, order)) {
|
|
1945
|
+
// Syncable payload unchanged; check if paymentDetails differ.
|
|
1946
|
+
// paymentDetails is local-only (never synced remotely), so if it's the
|
|
1947
|
+
// only change we merge it without bumping lastUpdatedAt or writing remotely.
|
|
1948
|
+
if (order.paymentDetails &&
|
|
1949
|
+
JSON.stringify(existing.paymentDetails) !==
|
|
1950
|
+
JSON.stringify(order.paymentDetails)) {
|
|
1951
|
+
this.update((state) => {
|
|
1952
|
+
const idx = state.orders.findIndex((stateOrder) => getInternalOrderCode(stateOrder) === internalOrderCode);
|
|
1953
|
+
if (idx !== -1) {
|
|
1954
|
+
state.orders[idx] = {
|
|
1955
|
+
...state.orders[idx],
|
|
1956
|
+
paymentDetails: order.paymentDetails,
|
|
1957
|
+
};
|
|
1958
|
+
}
|
|
1959
|
+
});
|
|
1960
|
+
}
|
|
1961
|
+
return;
|
|
1962
|
+
}
|
|
1963
|
+
const incomingLastUpdatedAt = order.lastUpdatedAt;
|
|
1964
|
+
// Local edits always bump lastUpdatedAt so full-sync LWW can prefer them
|
|
1965
|
+
// over stale remote copies when an incremental push was skipped/failed.
|
|
1966
|
+
// This includes external edits mid-sync (e.g. polling via `getOrder`),
|
|
1967
|
+
// which the queued follow-up sync must not lose under LWW.
|
|
1968
|
+
// Only when sync applies its own imported orders do we preserve the remote
|
|
1969
|
+
// `lu` / `createdAt` (never invent "now" for missing `lu`, or stale remotes
|
|
1970
|
+
// win later LWW comparisons).
|
|
1401
1971
|
const healedOrder = {
|
|
1402
1972
|
...order,
|
|
1403
1973
|
providerOrderId: internalOrderCode,
|
|
1974
|
+
lastUpdatedAt: this.#isApplyingOrderSyncChanges
|
|
1975
|
+
? (incomingLastUpdatedAt ?? order.createdAt ?? 0)
|
|
1976
|
+
: Date.now(),
|
|
1404
1977
|
};
|
|
1978
|
+
if (!this.#isApplyingOrderSyncChanges) {
|
|
1979
|
+
this.#pendingRemoteDeletes.delete(internalOrderCode);
|
|
1980
|
+
}
|
|
1405
1981
|
this.update((state) => {
|
|
1406
|
-
const idx = state.orders.findIndex((
|
|
1982
|
+
const idx = state.orders.findIndex((stateOrder) => getInternalOrderCode(stateOrder) === internalOrderCode);
|
|
1407
1983
|
if (idx === -1) {
|
|
1408
1984
|
state.orders.push(healedOrder);
|
|
1409
1985
|
}
|
|
@@ -1414,6 +1990,22 @@ export class RampsController extends BaseController {
|
|
|
1414
1990
|
};
|
|
1415
1991
|
}
|
|
1416
1992
|
});
|
|
1993
|
+
if (this.#isOrderSyncingInProgress && !this.#isApplyingOrderSyncChanges) {
|
|
1994
|
+
// Incremental push is suppressed during full sync; queue another full
|
|
1995
|
+
// sync pass so mutations during the upload await are not dropped.
|
|
1996
|
+
this.#orderSyncQueued = true;
|
|
1997
|
+
}
|
|
1998
|
+
else if (!this.#isOrderSyncingInProgress) {
|
|
1999
|
+
updateOrderInUserStorage(healedOrder, {
|
|
2000
|
+
getRampsControllerInstance: () => this,
|
|
2001
|
+
getMessenger: () => this.messenger,
|
|
2002
|
+
}, {
|
|
2003
|
+
onOrderSyncErroneousSituation: this.#onOrderSyncErroneousSituation,
|
|
2004
|
+
}).catch((error) => {
|
|
2005
|
+
console.error('Error updating ramps order in remote storage:', error);
|
|
2006
|
+
this.#onOrderSyncErroneousSituation?.('Error updating ramps order in remote storage', { error });
|
|
2007
|
+
});
|
|
2008
|
+
}
|
|
1417
2009
|
}
|
|
1418
2010
|
/**
|
|
1419
2011
|
* Removes a V2 order from controller state by providerOrderId.
|
|
@@ -1421,10 +2013,101 @@ export class RampsController extends BaseController {
|
|
|
1421
2013
|
* @param providerOrderId - The provider order ID to remove.
|
|
1422
2014
|
*/
|
|
1423
2015
|
removeOrder(providerOrderId) {
|
|
2016
|
+
const orderToRemove = this.state.orders.find((order) => order.providerOrderId === providerOrderId ||
|
|
2017
|
+
getInternalOrderCode(order) === providerOrderId);
|
|
1424
2018
|
this.update((state) => {
|
|
1425
|
-
state.orders = state.orders.filter((order) => order.providerOrderId !== providerOrderId
|
|
2019
|
+
state.orders = state.orders.filter((order) => order.providerOrderId !== providerOrderId &&
|
|
2020
|
+
getInternalOrderCode(order) !== providerOrderId);
|
|
1426
2021
|
});
|
|
1427
|
-
|
|
2022
|
+
this.#orderPollingMeta.delete(providerOrderId);
|
|
2023
|
+
if (orderToRemove) {
|
|
2024
|
+
if (orderToRemove.providerOrderId) {
|
|
2025
|
+
this.#orderPollingMeta.delete(orderToRemove.providerOrderId);
|
|
2026
|
+
}
|
|
2027
|
+
const internalOrderCode = getInternalOrderCode(orderToRemove);
|
|
2028
|
+
this.#orderPollingMeta.delete(internalOrderCode);
|
|
2029
|
+
}
|
|
2030
|
+
if (orderToRemove) {
|
|
2031
|
+
const deleteKey = getInternalOrderCode(orderToRemove);
|
|
2032
|
+
const isLocalDeletion = !this.#isApplyingOrderSyncChanges;
|
|
2033
|
+
if (isLocalDeletion && deleteKey) {
|
|
2034
|
+
// Retain the delete until a full sync confirms its tombstone was
|
|
2035
|
+
// persisted. This prevents a failed incremental write from allowing
|
|
2036
|
+
// the still-active remote copy to be imported again.
|
|
2037
|
+
this.#pendingRemoteDeletes.set(deleteKey, orderToRemove);
|
|
2038
|
+
}
|
|
2039
|
+
if (this.#isOrderSyncingInProgress) {
|
|
2040
|
+
if (isLocalDeletion) {
|
|
2041
|
+
// Incremental remote deletes are gated off during full sync; queue a
|
|
2042
|
+
// tombstone write and another full sync pass so deletes during the
|
|
2043
|
+
// upload await are not dropped.
|
|
2044
|
+
this.#orderSyncQueued = true;
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
else if (isLocalDeletion) {
|
|
2048
|
+
deleteOrderInUserStorage(orderToRemove, {
|
|
2049
|
+
getRampsControllerInstance: () => this,
|
|
2050
|
+
getMessenger: () => this.messenger,
|
|
2051
|
+
}, {
|
|
2052
|
+
onOrderSyncErroneousSituation: this.#onOrderSyncErroneousSituation,
|
|
2053
|
+
}).catch((error) => {
|
|
2054
|
+
console.error('Error deleting ramps order from remote storage:', error);
|
|
2055
|
+
this.#onOrderSyncErroneousSituation?.('Error deleting ramps order from remote storage', { error });
|
|
2056
|
+
});
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
/**
|
|
2061
|
+
* Bidirectionally syncs V2 ramps orders with User Storage.
|
|
2062
|
+
* Hosts should call this on unlock / when ramps syncing is enabled.
|
|
2063
|
+
*
|
|
2064
|
+
* Overlapping calls are coalesced into the in-flight worker. After the worker
|
|
2065
|
+
* settles, this method loops when `#orderSyncQueued` is still set so a
|
|
2066
|
+
* request that arrived between the worker's last loop check and promise
|
|
2067
|
+
* resolution is not dropped.
|
|
2068
|
+
*/
|
|
2069
|
+
async syncOrdersWithUserStorage() {
|
|
2070
|
+
this.#orderSyncQueued = true;
|
|
2071
|
+
let syncError;
|
|
2072
|
+
while (this.#orderSyncQueued || this.#orderSyncPromise) {
|
|
2073
|
+
if (this.#orderSyncPromise) {
|
|
2074
|
+
try {
|
|
2075
|
+
await this.#orderSyncPromise;
|
|
2076
|
+
}
|
|
2077
|
+
catch (error) {
|
|
2078
|
+
syncError ??=
|
|
2079
|
+
error instanceof Error ? error : new Error(String(error));
|
|
2080
|
+
}
|
|
2081
|
+
continue;
|
|
2082
|
+
}
|
|
2083
|
+
this.#orderSyncPromise = (async () => {
|
|
2084
|
+
while (this.#orderSyncQueued) {
|
|
2085
|
+
this.#orderSyncQueued = false;
|
|
2086
|
+
await syncOrdersWithUserStorageInternal({
|
|
2087
|
+
onOrderSyncErroneousSituation: this.#onOrderSyncErroneousSituation,
|
|
2088
|
+
}, {
|
|
2089
|
+
getRampsControllerInstance: () => this,
|
|
2090
|
+
getMessenger: () => this.messenger,
|
|
2091
|
+
trace: this.#trace,
|
|
2092
|
+
});
|
|
2093
|
+
}
|
|
2094
|
+
// Yield so a caller can set `#orderSyncQueued` after the inner while
|
|
2095
|
+
// check and still be observed by the outer loop.
|
|
2096
|
+
await Promise.resolve();
|
|
2097
|
+
})();
|
|
2098
|
+
try {
|
|
2099
|
+
await this.#orderSyncPromise;
|
|
2100
|
+
}
|
|
2101
|
+
catch (error) {
|
|
2102
|
+
syncError ??= error instanceof Error ? error : new Error(String(error));
|
|
2103
|
+
}
|
|
2104
|
+
finally {
|
|
2105
|
+
this.#orderSyncPromise = null;
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
if (syncError) {
|
|
2109
|
+
throw syncError;
|
|
2110
|
+
}
|
|
1428
2111
|
}
|
|
1429
2112
|
/**
|
|
1430
2113
|
* Adds or updates a local autoramp last-seen cursor (e.g. after create).
|
|
@@ -1468,7 +2151,7 @@ export class RampsController extends BaseController {
|
|
|
1468
2151
|
const customerId = await this.resolveAutorampCustomerId();
|
|
1469
2152
|
const body = { ...request, customer_id: customerId };
|
|
1470
2153
|
const remote = await this.messenger.call('NeoBankService:createAutoramp', body, options);
|
|
1471
|
-
return
|
|
2154
|
+
return this.#applyAutorampRemoteSnapshot(remote);
|
|
1472
2155
|
}
|
|
1473
2156
|
/**
|
|
1474
2157
|
* Resolves the vendor `customer_id` for autoramp / Money Account operations.
|
|
@@ -1687,7 +2370,7 @@ export class RampsController extends BaseController {
|
|
|
1687
2370
|
* @returns The updated local account.
|
|
1688
2371
|
*/
|
|
1689
2372
|
applyAutorampStatusFromPush(remote) {
|
|
1690
|
-
return
|
|
2373
|
+
return this.#applyAutorampRemoteSnapshot(remote);
|
|
1691
2374
|
}
|
|
1692
2375
|
/**
|
|
1693
2376
|
* Fetches one autoramp from the neo-bank proxy and applies it to the
|
|
@@ -1700,7 +2383,7 @@ export class RampsController extends BaseController {
|
|
|
1700
2383
|
*/
|
|
1701
2384
|
async refreshAutoramp(autorampId) {
|
|
1702
2385
|
const remote = await this.messenger.call('NeoBankService:getAutoramp', autorampId);
|
|
1703
|
-
return
|
|
2386
|
+
return this.#applyAutorampRemoteSnapshot(remote, { allowInsert: false });
|
|
1704
2387
|
}
|
|
1705
2388
|
/**
|
|
1706
2389
|
* Refreshes all known local autoramps from MoonPay.
|
|
@@ -1724,27 +2407,137 @@ export class RampsController extends BaseController {
|
|
|
1724
2407
|
}
|
|
1725
2408
|
return updated;
|
|
1726
2409
|
}
|
|
2410
|
+
/**
|
|
2411
|
+
* Applies a remote snapshot to the last-seen cursor.
|
|
2412
|
+
*
|
|
2413
|
+
* @param remote - MoonPay snapshot.
|
|
2414
|
+
* @param options - Apply options.
|
|
2415
|
+
* @param options.allowInsert - When false, a missing local cursor is not
|
|
2416
|
+
* recreated (used by refresh so a concurrent `removeAutoramp` wins).
|
|
2417
|
+
* @returns The applied account. When `allowInsert` is false and no local
|
|
2418
|
+
* cursor exists, the snapshot is returned without writing state.
|
|
2419
|
+
*/
|
|
2420
|
+
#applyAutorampRemoteSnapshot(remote, { allowInsert = true } = {}) {
|
|
2421
|
+
const local = this.state.autoramps.find((autoramp) => autoramp.id === remote.id) ??
|
|
2422
|
+
null;
|
|
2423
|
+
if (local === null && !allowInsert) {
|
|
2424
|
+
return applyAutorampRemoteStatus(null, remote).account;
|
|
2425
|
+
}
|
|
2426
|
+
const result = applyAutorampRemoteStatus(local, remote);
|
|
2427
|
+
this.update((state) => {
|
|
2428
|
+
const idx = state.autoramps.findIndex((autoramp) => autoramp.id === result.account.id);
|
|
2429
|
+
if (idx === -1) {
|
|
2430
|
+
state.autoramps.push(result.account);
|
|
2431
|
+
}
|
|
2432
|
+
else {
|
|
2433
|
+
state.autoramps[idx] = result.account;
|
|
2434
|
+
}
|
|
2435
|
+
});
|
|
2436
|
+
if (result.statusChanged) {
|
|
2437
|
+
this.messenger.publish('RampsController:autorampStatusChanged', {
|
|
2438
|
+
autoramp: result.account,
|
|
2439
|
+
previousStatus: result.previousStatus,
|
|
2440
|
+
shouldNotify: result.shouldNotify,
|
|
2441
|
+
});
|
|
2442
|
+
}
|
|
2443
|
+
return (this.state.autoramps.find((autoramp) => autoramp.id === result.account.id) ?? result.account);
|
|
2444
|
+
}
|
|
2445
|
+
/**
|
|
2446
|
+
* Refreshes a single order via the V2 API and updates it in state.
|
|
2447
|
+
* Publishes orderStatusChanged if the status transitioned.
|
|
2448
|
+
*
|
|
2449
|
+
* @param order - The order to refresh (needs provider and providerOrderId).
|
|
2450
|
+
*/
|
|
2451
|
+
async #refreshOrder(order) {
|
|
2452
|
+
const providerCode = order.provider?.id ?? '';
|
|
2453
|
+
if (!providerCode || !order.providerOrderId || !order.walletAddress) {
|
|
2454
|
+
return;
|
|
2455
|
+
}
|
|
2456
|
+
const previousStatus = order.status;
|
|
2457
|
+
try {
|
|
2458
|
+
const updatedOrder = await this.getOrder(providerCode, order.providerOrderId, order.walletAddress);
|
|
2459
|
+
const meta = this.#orderPollingMeta.get(order.providerOrderId) ?? {
|
|
2460
|
+
lastTimeFetched: 0,
|
|
2461
|
+
errorCount: 0,
|
|
2462
|
+
};
|
|
2463
|
+
if (updatedOrder.status === RampsOrderStatus.Unknown) {
|
|
2464
|
+
meta.errorCount = Math.min(meta.errorCount + 1, MAX_ERROR_COUNT);
|
|
2465
|
+
}
|
|
2466
|
+
else {
|
|
2467
|
+
meta.errorCount = 0;
|
|
2468
|
+
}
|
|
2469
|
+
meta.lastTimeFetched = Date.now();
|
|
2470
|
+
this.#orderPollingMeta.set(order.providerOrderId, meta);
|
|
2471
|
+
if (previousStatus !== updatedOrder.status &&
|
|
2472
|
+
previousStatus !== undefined) {
|
|
2473
|
+
this.messenger.publish('RampsController:orderStatusChanged', {
|
|
2474
|
+
order: updatedOrder,
|
|
2475
|
+
previousStatus,
|
|
2476
|
+
});
|
|
2477
|
+
}
|
|
2478
|
+
if (TERMINAL_ORDER_STATUSES.has(updatedOrder.status)) {
|
|
2479
|
+
this.#orderPollingMeta.delete(order.providerOrderId);
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
catch {
|
|
2483
|
+
const meta = this.#orderPollingMeta.get(order.providerOrderId) ?? {
|
|
2484
|
+
lastTimeFetched: 0,
|
|
2485
|
+
errorCount: 0,
|
|
2486
|
+
};
|
|
2487
|
+
meta.errorCount = Math.min(meta.errorCount + 1, MAX_ERROR_COUNT);
|
|
2488
|
+
meta.lastTimeFetched = Date.now();
|
|
2489
|
+
this.#orderPollingMeta.set(order.providerOrderId, meta);
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
1727
2492
|
/**
|
|
1728
2493
|
* Starts polling all pending V2 orders at a fixed interval.
|
|
1729
2494
|
* Each poll cycle iterates orders with non-terminal statuses,
|
|
1730
2495
|
* respects pollingSecondsMinimum and backoff from error count.
|
|
1731
2496
|
*/
|
|
1732
2497
|
startOrderPolling() {
|
|
1733
|
-
if (
|
|
2498
|
+
if (this.#orderPollingTimer) {
|
|
1734
2499
|
return;
|
|
1735
2500
|
}
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
}, DEFAULT_POLLING_INTERVAL_MS)
|
|
1739
|
-
|
|
2501
|
+
this.#orderPollingTimer = setInterval(() => {
|
|
2502
|
+
this.#pollPendingOrders().catch(() => undefined);
|
|
2503
|
+
}, DEFAULT_POLLING_INTERVAL_MS);
|
|
2504
|
+
this.#pollPendingOrders().catch(() => undefined);
|
|
1740
2505
|
}
|
|
1741
2506
|
/**
|
|
1742
2507
|
* Stops order polling and clears the interval.
|
|
1743
2508
|
*/
|
|
1744
2509
|
stopOrderPolling() {
|
|
1745
|
-
if (
|
|
1746
|
-
clearInterval(
|
|
1747
|
-
|
|
2510
|
+
if (this.#orderPollingTimer) {
|
|
2511
|
+
clearInterval(this.#orderPollingTimer);
|
|
2512
|
+
this.#orderPollingTimer = null;
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
async #pollPendingOrders() {
|
|
2516
|
+
if (this.#isPolling) {
|
|
2517
|
+
return;
|
|
2518
|
+
}
|
|
2519
|
+
this.#isPolling = true;
|
|
2520
|
+
try {
|
|
2521
|
+
const pendingOrders = this.state.orders.filter((order) => PENDING_ORDER_STATUSES.has(order.status));
|
|
2522
|
+
const now = Date.now();
|
|
2523
|
+
await Promise.allSettled(pendingOrders.map(async (order) => {
|
|
2524
|
+
const meta = this.#orderPollingMeta.get(order.providerOrderId);
|
|
2525
|
+
if (meta) {
|
|
2526
|
+
const backoffMs = meta.errorCount > 0
|
|
2527
|
+
? Math.min(DEFAULT_POLLING_INTERVAL_MS *
|
|
2528
|
+
Math.pow(2, meta.errorCount - 1), 5 * 60 * 1000)
|
|
2529
|
+
: 0;
|
|
2530
|
+
const pollingMinMs = (order.pollingSecondsMinimum ?? 0) * 1000;
|
|
2531
|
+
const minWait = Math.max(backoffMs, pollingMinMs);
|
|
2532
|
+
if (now - meta.lastTimeFetched < minWait) {
|
|
2533
|
+
return;
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2536
|
+
await this.#refreshOrder(order);
|
|
2537
|
+
}));
|
|
2538
|
+
}
|
|
2539
|
+
finally {
|
|
2540
|
+
this.#isPolling = false;
|
|
1748
2541
|
}
|
|
1749
2542
|
}
|
|
1750
2543
|
/**
|
|
@@ -1835,6 +2628,7 @@ export class RampsController extends BaseController {
|
|
|
1835
2628
|
* @returns The unified order data.
|
|
1836
2629
|
*/
|
|
1837
2630
|
async getOrder(providerCode, orderCode, wallet) {
|
|
2631
|
+
const hadOrderAtRequestStart = this.state.orders.some((existingOrder) => getInternalOrderCode(existingOrder) === orderCode);
|
|
1838
2632
|
const order = await this.messenger.call('RampsService:getOrder', providerCode, orderCode, wallet);
|
|
1839
2633
|
const healedWalletAddress = order.walletAddress || wallet;
|
|
1840
2634
|
const internalOrderCode = getInternalOrderCode({
|
|
@@ -1846,18 +2640,12 @@ export class RampsController extends BaseController {
|
|
|
1846
2640
|
walletAddress: healedWalletAddress,
|
|
1847
2641
|
providerOrderId: internalOrderCode,
|
|
1848
2642
|
};
|
|
1849
|
-
this.
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
state.orders[idx] = {
|
|
1856
|
-
...state.orders[idx],
|
|
1857
|
-
...healedOrder,
|
|
1858
|
-
};
|
|
1859
|
-
}
|
|
1860
|
-
});
|
|
2643
|
+
const orderStillExists = this.state.orders.some((existingOrder) => getInternalOrderCode(existingOrder) === internalOrderCode);
|
|
2644
|
+
// A polling request can finish after removeOrder. Do not let that stale
|
|
2645
|
+
// response recreate the local order and overwrite its remote tombstone.
|
|
2646
|
+
if (!hadOrderAtRequestStart || orderStillExists) {
|
|
2647
|
+
this.addOrder(healedOrder);
|
|
2648
|
+
}
|
|
1861
2649
|
return healedOrder;
|
|
1862
2650
|
}
|
|
1863
2651
|
/**
|
|
@@ -1875,6 +2663,30 @@ export class RampsController extends BaseController {
|
|
|
1875
2663
|
const order = await this.messenger.call('RampsService:getOrderFromCallback', providerCode, callbackUrl, wallet);
|
|
1876
2664
|
return order;
|
|
1877
2665
|
}
|
|
2666
|
+
// === TRANSAK METHODS ===
|
|
2667
|
+
//
|
|
2668
|
+
// Auth state is managed at two levels:
|
|
2669
|
+
// - TransakService stores the access token (needed for API calls)
|
|
2670
|
+
// - RampsController stores isAuthenticated (needed for UI state)
|
|
2671
|
+
// Both are kept in sync by the controller methods below.
|
|
2672
|
+
/**
|
|
2673
|
+
* Checks whether an error is a 401 HTTP error (expired/missing token) and,
|
|
2674
|
+
* if so, marks the Transak session as unauthenticated so the UI stays in
|
|
2675
|
+
* sync with the cleared token inside TransakService.
|
|
2676
|
+
*
|
|
2677
|
+
* @param error - The caught error to inspect.
|
|
2678
|
+
*/
|
|
2679
|
+
#syncTransakAuthOnError(error) {
|
|
2680
|
+
if (hasHttpStatus(error) && error.httpStatus === 401) {
|
|
2681
|
+
this.transakSetAuthenticated(false);
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
#getNormalizedTransakError(error, options = {}) {
|
|
2685
|
+
if (options.syncAuth) {
|
|
2686
|
+
this.#syncTransakAuthOnError(error);
|
|
2687
|
+
}
|
|
2688
|
+
return getNormalizedRampsError(error);
|
|
2689
|
+
}
|
|
1878
2690
|
/**
|
|
1879
2691
|
* Sets the Transak API key used for all Transak API requests.
|
|
1880
2692
|
*
|
|
@@ -1930,7 +2742,7 @@ export class RampsController extends BaseController {
|
|
|
1930
2742
|
return await this.messenger.call('TransakService:sendUserOtp', email);
|
|
1931
2743
|
}
|
|
1932
2744
|
catch (error) {
|
|
1933
|
-
throw
|
|
2745
|
+
throw this.#getNormalizedTransakError(error).normalizedError;
|
|
1934
2746
|
}
|
|
1935
2747
|
}
|
|
1936
2748
|
/**
|
|
@@ -1949,7 +2761,7 @@ export class RampsController extends BaseController {
|
|
|
1949
2761
|
return token;
|
|
1950
2762
|
}
|
|
1951
2763
|
catch (error) {
|
|
1952
|
-
throw
|
|
2764
|
+
throw this.#getNormalizedTransakError(error).normalizedError;
|
|
1953
2765
|
}
|
|
1954
2766
|
}
|
|
1955
2767
|
/**
|
|
@@ -1963,7 +2775,7 @@ export class RampsController extends BaseController {
|
|
|
1963
2775
|
return await this.messenger.call('TransakService:logout');
|
|
1964
2776
|
}
|
|
1965
2777
|
catch (error) {
|
|
1966
|
-
throw
|
|
2778
|
+
throw this.#getNormalizedTransakError(error).normalizedError;
|
|
1967
2779
|
}
|
|
1968
2780
|
finally {
|
|
1969
2781
|
this.transakClearAccessToken();
|
|
@@ -1993,7 +2805,7 @@ export class RampsController extends BaseController {
|
|
|
1993
2805
|
return details;
|
|
1994
2806
|
}
|
|
1995
2807
|
catch (error) {
|
|
1996
|
-
const { errorInfo, normalizedError } =
|
|
2808
|
+
const { errorInfo, normalizedError } = this.#getNormalizedTransakError(error, {
|
|
1997
2809
|
syncAuth: true,
|
|
1998
2810
|
});
|
|
1999
2811
|
this.update((state) => {
|
|
@@ -2030,7 +2842,7 @@ export class RampsController extends BaseController {
|
|
|
2030
2842
|
return quote;
|
|
2031
2843
|
}
|
|
2032
2844
|
catch (error) {
|
|
2033
|
-
const { errorInfo, normalizedError } =
|
|
2845
|
+
const { errorInfo, normalizedError } = this.#getNormalizedTransakError(error);
|
|
2034
2846
|
this.update((state) => {
|
|
2035
2847
|
state.nativeProviders.transak.buyQuote.isLoading = false;
|
|
2036
2848
|
state.nativeProviders.transak.buyQuote.error = errorInfo.message;
|
|
@@ -2061,7 +2873,7 @@ export class RampsController extends BaseController {
|
|
|
2061
2873
|
return requirement;
|
|
2062
2874
|
}
|
|
2063
2875
|
catch (error) {
|
|
2064
|
-
const { errorInfo, normalizedError } =
|
|
2876
|
+
const { errorInfo, normalizedError } = this.#getNormalizedTransakError(error, {
|
|
2065
2877
|
syncAuth: true,
|
|
2066
2878
|
});
|
|
2067
2879
|
this.update((state) => {
|
|
@@ -2084,7 +2896,7 @@ export class RampsController extends BaseController {
|
|
|
2084
2896
|
return await this.messenger.call('TransakService:getAdditionalRequirements', quoteId);
|
|
2085
2897
|
}
|
|
2086
2898
|
catch (error) {
|
|
2087
|
-
throw
|
|
2899
|
+
throw this.#getNormalizedTransakError(error, { syncAuth: true })
|
|
2088
2900
|
.normalizedError;
|
|
2089
2901
|
}
|
|
2090
2902
|
}
|
|
@@ -2102,7 +2914,7 @@ export class RampsController extends BaseController {
|
|
|
2102
2914
|
return await this.messenger.call('TransakService:createOrder', quoteId, walletAddress, paymentMethodId);
|
|
2103
2915
|
}
|
|
2104
2916
|
catch (error) {
|
|
2105
|
-
throw
|
|
2917
|
+
throw this.#getNormalizedTransakError(error, { syncAuth: true })
|
|
2106
2918
|
.normalizedError;
|
|
2107
2919
|
}
|
|
2108
2920
|
}
|
|
@@ -2119,7 +2931,7 @@ export class RampsController extends BaseController {
|
|
|
2119
2931
|
return await this.messenger.call('TransakService:getOrder', orderId, wallet, paymentDetails);
|
|
2120
2932
|
}
|
|
2121
2933
|
catch (error) {
|
|
2122
|
-
throw
|
|
2934
|
+
throw this.#getNormalizedTransakError(error).normalizedError;
|
|
2123
2935
|
}
|
|
2124
2936
|
}
|
|
2125
2937
|
/**
|
|
@@ -2135,7 +2947,7 @@ export class RampsController extends BaseController {
|
|
|
2135
2947
|
return await this.messenger.call('TransakService:getUserLimits', fiatCurrency, paymentMethod, kycType);
|
|
2136
2948
|
}
|
|
2137
2949
|
catch (error) {
|
|
2138
|
-
throw
|
|
2950
|
+
throw this.#getNormalizedTransakError(error, { syncAuth: true })
|
|
2139
2951
|
.normalizedError;
|
|
2140
2952
|
}
|
|
2141
2953
|
}
|
|
@@ -2149,7 +2961,7 @@ export class RampsController extends BaseController {
|
|
|
2149
2961
|
return await this.messenger.call('TransakService:requestOtt');
|
|
2150
2962
|
}
|
|
2151
2963
|
catch (error) {
|
|
2152
|
-
throw
|
|
2964
|
+
throw this.#getNormalizedTransakError(error, { syncAuth: true })
|
|
2153
2965
|
.normalizedError;
|
|
2154
2966
|
}
|
|
2155
2967
|
}
|
|
@@ -2180,7 +2992,7 @@ export class RampsController extends BaseController {
|
|
|
2180
2992
|
return await this.messenger.call('TransakService:createWidgetUrl', quote, walletAddress, extraParams);
|
|
2181
2993
|
}
|
|
2182
2994
|
catch (error) {
|
|
2183
|
-
throw
|
|
2995
|
+
throw this.#getNormalizedTransakError(error, { syncAuth: true })
|
|
2184
2996
|
.normalizedError;
|
|
2185
2997
|
}
|
|
2186
2998
|
}
|
|
@@ -2195,7 +3007,7 @@ export class RampsController extends BaseController {
|
|
|
2195
3007
|
return await this.messenger.call('TransakService:submitPurposeOfUsageForm', purpose);
|
|
2196
3008
|
}
|
|
2197
3009
|
catch (error) {
|
|
2198
|
-
throw
|
|
3010
|
+
throw this.#getNormalizedTransakError(error, { syncAuth: true })
|
|
2199
3011
|
.normalizedError;
|
|
2200
3012
|
}
|
|
2201
3013
|
}
|
|
@@ -2210,7 +3022,7 @@ export class RampsController extends BaseController {
|
|
|
2210
3022
|
return await this.messenger.call('TransakService:patchUser', data);
|
|
2211
3023
|
}
|
|
2212
3024
|
catch (error) {
|
|
2213
|
-
throw
|
|
3025
|
+
throw this.#getNormalizedTransakError(error, { syncAuth: true })
|
|
2214
3026
|
.normalizedError;
|
|
2215
3027
|
}
|
|
2216
3028
|
}
|
|
@@ -2226,7 +3038,7 @@ export class RampsController extends BaseController {
|
|
|
2226
3038
|
return await this.messenger.call('TransakService:submitSsnDetails', ssn, quoteId);
|
|
2227
3039
|
}
|
|
2228
3040
|
catch (error) {
|
|
2229
|
-
throw
|
|
3041
|
+
throw this.#getNormalizedTransakError(error, { syncAuth: true })
|
|
2230
3042
|
.normalizedError;
|
|
2231
3043
|
}
|
|
2232
3044
|
}
|
|
@@ -2242,7 +3054,7 @@ export class RampsController extends BaseController {
|
|
|
2242
3054
|
return await this.messenger.call('TransakService:confirmPayment', orderId, paymentMethodId);
|
|
2243
3055
|
}
|
|
2244
3056
|
catch (error) {
|
|
2245
|
-
throw
|
|
3057
|
+
throw this.#getNormalizedTransakError(error, { syncAuth: true })
|
|
2246
3058
|
.normalizedError;
|
|
2247
3059
|
}
|
|
2248
3060
|
}
|
|
@@ -2257,7 +3069,7 @@ export class RampsController extends BaseController {
|
|
|
2257
3069
|
return await this.messenger.call('TransakService:getTranslation', request);
|
|
2258
3070
|
}
|
|
2259
3071
|
catch (error) {
|
|
2260
|
-
throw
|
|
3072
|
+
throw this.#getNormalizedTransakError(error).normalizedError;
|
|
2261
3073
|
}
|
|
2262
3074
|
}
|
|
2263
3075
|
/**
|
|
@@ -2271,7 +3083,7 @@ export class RampsController extends BaseController {
|
|
|
2271
3083
|
return await this.messenger.call('TransakService:getIdProofStatus', workFlowRunId);
|
|
2272
3084
|
}
|
|
2273
3085
|
catch (error) {
|
|
2274
|
-
throw
|
|
3086
|
+
throw this.#getNormalizedTransakError(error, { syncAuth: true })
|
|
2275
3087
|
.normalizedError;
|
|
2276
3088
|
}
|
|
2277
3089
|
}
|
|
@@ -2286,7 +3098,7 @@ export class RampsController extends BaseController {
|
|
|
2286
3098
|
return await this.messenger.call('TransakService:cancelOrder', depositOrderId);
|
|
2287
3099
|
}
|
|
2288
3100
|
catch (error) {
|
|
2289
|
-
throw
|
|
3101
|
+
throw this.#getNormalizedTransakError(error, { syncAuth: true })
|
|
2290
3102
|
.normalizedError;
|
|
2291
3103
|
}
|
|
2292
3104
|
}
|
|
@@ -2301,7 +3113,7 @@ export class RampsController extends BaseController {
|
|
|
2301
3113
|
return await this.messenger.call('TransakService:cancelAllActiveOrders');
|
|
2302
3114
|
}
|
|
2303
3115
|
catch (error) {
|
|
2304
|
-
throw
|
|
3116
|
+
throw this.#getNormalizedTransakError(error, { syncAuth: true })
|
|
2305
3117
|
.normalizedError;
|
|
2306
3118
|
}
|
|
2307
3119
|
}
|
|
@@ -2315,479 +3127,9 @@ export class RampsController extends BaseController {
|
|
|
2315
3127
|
return await this.messenger.call('TransakService:getActiveOrders');
|
|
2316
3128
|
}
|
|
2317
3129
|
catch (error) {
|
|
2318
|
-
throw
|
|
3130
|
+
throw this.#getNormalizedTransakError(error, { syncAuth: true })
|
|
2319
3131
|
.normalizedError;
|
|
2320
3132
|
}
|
|
2321
3133
|
}
|
|
2322
3134
|
}
|
|
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
|
|
3135
|
+
//# sourceMappingURL=RampsController.js.map
|