@metamask/ramps-controller 5.1.0 → 6.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 +8 -1
- package/dist/RampsController.cjs +230 -190
- package/dist/RampsController.cjs.map +1 -1
- package/dist/RampsController.d.cts +52 -106
- package/dist/RampsController.d.cts.map +1 -1
- package/dist/RampsController.d.mts +52 -106
- package/dist/RampsController.d.mts.map +1 -1
- package/dist/RampsController.mjs +229 -189
- package/dist/RampsController.mjs.map +1 -1
- package/dist/RequestCache.cjs.map +1 -1
- package/dist/RequestCache.d.cts +11 -0
- package/dist/RequestCache.d.cts.map +1 -1
- package/dist/RequestCache.d.mts +11 -0
- package/dist/RequestCache.d.mts.map +1 -1
- package/dist/RequestCache.mjs.map +1 -1
- package/dist/index.cjs +2 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -4
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +4 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/RampsController.mjs
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
2
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
3
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
4
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
5
|
+
};
|
|
1
6
|
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
2
7
|
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
3
8
|
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
4
9
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
5
10
|
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
6
11
|
};
|
|
7
|
-
var
|
|
8
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
9
|
-
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
|
-
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
|
-
};
|
|
12
|
-
var _RampsController_instances, _RampsController_requestCacheTTL, _RampsController_requestCacheMaxSize, _RampsController_pendingRequests, _RampsController_removeRequestState, _RampsController_cleanupState, _RampsController_updateRequestState;
|
|
12
|
+
var _RampsController_instances, _RampsController_requestCacheTTL, _RampsController_requestCacheMaxSize, _RampsController_pendingRequests, _RampsController_pendingResourceCount, _RampsController_clearPendingResourceCountForDependentResources, _RampsController_removeRequestState, _RampsController_cleanupState, _RampsController_fireAndForget, _RampsController_updateResourceField, _RampsController_setResourceLoading, _RampsController_setResourceError, _RampsController_updateRequestState;
|
|
13
13
|
import { BaseController } from "@metamask/base-controller";
|
|
14
14
|
import { DEFAULT_REQUEST_CACHE_TTL, DEFAULT_REQUEST_CACHE_MAX_SIZE, createCacheKey, isCacheExpired, createLoadingState, createSuccessState, createErrorState, RequestStatus } from "./RequestCache.mjs";
|
|
15
15
|
// === GENERAL ===
|
|
@@ -19,6 +19,19 @@ import { DEFAULT_REQUEST_CACHE_TTL, DEFAULT_REQUEST_CACHE_MAX_SIZE, createCacheK
|
|
|
19
19
|
* when composed with other controllers.
|
|
20
20
|
*/
|
|
21
21
|
export const controllerName = 'RampsController';
|
|
22
|
+
/**
|
|
23
|
+
* RampsService action types that RampsController calls via the messenger.
|
|
24
|
+
* Any host (e.g. mobile) that creates a RampsController messenger must delegate
|
|
25
|
+
* these actions from the root messenger so the controller can function.
|
|
26
|
+
*/
|
|
27
|
+
export const RAMPS_CONTROLLER_REQUIRED_SERVICE_ACTIONS = [
|
|
28
|
+
'RampsService:getGeolocation',
|
|
29
|
+
'RampsService:getCountries',
|
|
30
|
+
'RampsService:getTokens',
|
|
31
|
+
'RampsService:getProviders',
|
|
32
|
+
'RampsService:getPaymentMethods',
|
|
33
|
+
'RampsService:getQuotes',
|
|
34
|
+
];
|
|
22
35
|
/**
|
|
23
36
|
* Default TTL for quotes requests (15 seconds).
|
|
24
37
|
* Quotes are time-sensitive and should have a shorter cache duration.
|
|
@@ -34,12 +47,6 @@ const rampsControllerMetadata = {
|
|
|
34
47
|
includeInStateLogs: true,
|
|
35
48
|
usedInUi: true,
|
|
36
49
|
},
|
|
37
|
-
selectedProvider: {
|
|
38
|
-
persist: false,
|
|
39
|
-
includeInDebugSnapshot: true,
|
|
40
|
-
includeInStateLogs: true,
|
|
41
|
-
usedInUi: true,
|
|
42
|
-
},
|
|
43
50
|
countries: {
|
|
44
51
|
persist: true,
|
|
45
52
|
includeInDebugSnapshot: true,
|
|
@@ -58,24 +65,12 @@ const rampsControllerMetadata = {
|
|
|
58
65
|
includeInStateLogs: true,
|
|
59
66
|
usedInUi: true,
|
|
60
67
|
},
|
|
61
|
-
selectedToken: {
|
|
62
|
-
persist: false,
|
|
63
|
-
includeInDebugSnapshot: true,
|
|
64
|
-
includeInStateLogs: true,
|
|
65
|
-
usedInUi: true,
|
|
66
|
-
},
|
|
67
68
|
paymentMethods: {
|
|
68
69
|
persist: false,
|
|
69
70
|
includeInDebugSnapshot: true,
|
|
70
71
|
includeInStateLogs: true,
|
|
71
72
|
usedInUi: true,
|
|
72
73
|
},
|
|
73
|
-
selectedPaymentMethod: {
|
|
74
|
-
persist: false,
|
|
75
|
-
includeInDebugSnapshot: true,
|
|
76
|
-
includeInStateLogs: true,
|
|
77
|
-
usedInUi: true,
|
|
78
|
-
},
|
|
79
74
|
quotes: {
|
|
80
75
|
persist: false,
|
|
81
76
|
includeInDebugSnapshot: true,
|
|
@@ -89,6 +84,23 @@ const rampsControllerMetadata = {
|
|
|
89
84
|
usedInUi: true,
|
|
90
85
|
},
|
|
91
86
|
};
|
|
87
|
+
/**
|
|
88
|
+
* Creates a default resource state object.
|
|
89
|
+
*
|
|
90
|
+
* @template TData - The type of the resource data.
|
|
91
|
+
* @template TSelected - The type of the selected item.
|
|
92
|
+
* @param data - The initial data value.
|
|
93
|
+
* @param selected - The initial selected value.
|
|
94
|
+
* @returns A ResourceState object with default loading and error values.
|
|
95
|
+
*/
|
|
96
|
+
function createDefaultResourceState(data, selected = null) {
|
|
97
|
+
return {
|
|
98
|
+
data,
|
|
99
|
+
selected,
|
|
100
|
+
isLoading: false,
|
|
101
|
+
error: null,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
92
104
|
/**
|
|
93
105
|
* Constructs the default {@link RampsController} state. This allows
|
|
94
106
|
* consumers to provide a partial state object when initializing the controller
|
|
@@ -100,17 +112,42 @@ const rampsControllerMetadata = {
|
|
|
100
112
|
export function getDefaultRampsControllerState() {
|
|
101
113
|
return {
|
|
102
114
|
userRegion: null,
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
paymentMethods: [],
|
|
109
|
-
selectedPaymentMethod: null,
|
|
110
|
-
quotes: null,
|
|
115
|
+
countries: createDefaultResourceState([]),
|
|
116
|
+
providers: createDefaultResourceState([], null),
|
|
117
|
+
tokens: createDefaultResourceState(null, null),
|
|
118
|
+
paymentMethods: createDefaultResourceState([], null),
|
|
119
|
+
quotes: createDefaultResourceState(null),
|
|
111
120
|
requests: {},
|
|
112
121
|
};
|
|
113
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Resets region-dependent resources (userRegion, providers, tokens, paymentMethods, quotes).
|
|
125
|
+
* Mutates state in place; use from within controller update() for atomic updates.
|
|
126
|
+
*
|
|
127
|
+
* @param state - The state object to mutate.
|
|
128
|
+
* @param options - Options for the reset.
|
|
129
|
+
* @param options.clearUserRegionData - When true, sets userRegion to null (e.g. for full cleanup).
|
|
130
|
+
*/
|
|
131
|
+
function resetDependentResources(state, options) {
|
|
132
|
+
if (options?.clearUserRegionData) {
|
|
133
|
+
state.userRegion = null;
|
|
134
|
+
}
|
|
135
|
+
state.providers.selected = null;
|
|
136
|
+
state.providers.data = [];
|
|
137
|
+
state.providers.isLoading = false;
|
|
138
|
+
state.providers.error = null;
|
|
139
|
+
state.tokens.selected = null;
|
|
140
|
+
state.tokens.data = null;
|
|
141
|
+
state.tokens.isLoading = false;
|
|
142
|
+
state.tokens.error = null;
|
|
143
|
+
state.paymentMethods.data = [];
|
|
144
|
+
state.paymentMethods.selected = null;
|
|
145
|
+
state.paymentMethods.isLoading = false;
|
|
146
|
+
state.paymentMethods.error = null;
|
|
147
|
+
state.quotes.data = null;
|
|
148
|
+
state.quotes.isLoading = false;
|
|
149
|
+
state.quotes.error = null;
|
|
150
|
+
}
|
|
114
151
|
// === HELPER FUNCTIONS ===
|
|
115
152
|
/**
|
|
116
153
|
* Finds a country and state from a region code string.
|
|
@@ -169,6 +206,15 @@ function findRegionFromCode(regionCode, countries) {
|
|
|
169
206
|
* Manages cryptocurrency on/off ramps functionality.
|
|
170
207
|
*/
|
|
171
208
|
export class RampsController extends BaseController {
|
|
209
|
+
/**
|
|
210
|
+
* Clears the pending resource count map. Used only in tests to exercise the
|
|
211
|
+
* defensive path when get() returns undefined in the finally block.
|
|
212
|
+
*
|
|
213
|
+
* @internal
|
|
214
|
+
*/
|
|
215
|
+
clearPendingResourceCountForTest() {
|
|
216
|
+
__classPrivateFieldGet(this, _RampsController_pendingResourceCount, "f").clear();
|
|
217
|
+
}
|
|
172
218
|
/**
|
|
173
219
|
* Constructs a new {@link RampsController}.
|
|
174
220
|
*
|
|
@@ -205,6 +251,11 @@ export class RampsController extends BaseController {
|
|
|
205
251
|
* Key is the cache key, value is the pending request with abort controller.
|
|
206
252
|
*/
|
|
207
253
|
_RampsController_pendingRequests.set(this, new Map());
|
|
254
|
+
/**
|
|
255
|
+
* Count of in-flight requests per resource type.
|
|
256
|
+
* Used so isLoading is only cleared when the last request for that resource finishes.
|
|
257
|
+
*/
|
|
258
|
+
_RampsController_pendingResourceCount.set(this, new Map());
|
|
208
259
|
__classPrivateFieldSet(this, _RampsController_requestCacheTTL, requestCacheTTL, "f");
|
|
209
260
|
__classPrivateFieldSet(this, _RampsController_requestCacheMaxSize, requestCacheMaxSize, "f");
|
|
210
261
|
}
|
|
@@ -236,8 +287,18 @@ export class RampsController extends BaseController {
|
|
|
236
287
|
// Create abort controller for this request
|
|
237
288
|
const abortController = new AbortController();
|
|
238
289
|
const lastFetchedAt = Date.now();
|
|
290
|
+
const { resourceType } = options ?? {};
|
|
239
291
|
// Update state to loading
|
|
240
292
|
__classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_updateRequestState).call(this, cacheKey, createLoadingState());
|
|
293
|
+
// Set resource-level loading state (only on cache miss). Ref-count so concurrent
|
|
294
|
+
// requests for the same resource type (different cache keys) keep isLoading true.
|
|
295
|
+
if (resourceType) {
|
|
296
|
+
const count = __classPrivateFieldGet(this, _RampsController_pendingResourceCount, "f").get(resourceType) ?? 0;
|
|
297
|
+
__classPrivateFieldGet(this, _RampsController_pendingResourceCount, "f").set(resourceType, count + 1);
|
|
298
|
+
if (count === 0) {
|
|
299
|
+
__classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_setResourceLoading).call(this, resourceType, true);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
241
302
|
// Create the fetch promise
|
|
242
303
|
const promise = (async () => {
|
|
243
304
|
try {
|
|
@@ -247,6 +308,15 @@ export class RampsController extends BaseController {
|
|
|
247
308
|
throw new Error('Request was aborted');
|
|
248
309
|
}
|
|
249
310
|
__classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_updateRequestState).call(this, cacheKey, createSuccessState(data, lastFetchedAt));
|
|
311
|
+
if (resourceType) {
|
|
312
|
+
// We need the extra logic because there are two situations where we’re allowed to clear the error:
|
|
313
|
+
// No callback → always clear
|
|
314
|
+
// Callback present → clear only when isResultCurrent() returns true.
|
|
315
|
+
const isCurrent = !options?.isResultCurrent || options.isResultCurrent();
|
|
316
|
+
if (isCurrent) {
|
|
317
|
+
__classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_setResourceError).call(this, resourceType, null);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
250
320
|
return data;
|
|
251
321
|
}
|
|
252
322
|
catch (error) {
|
|
@@ -254,8 +324,14 @@ export class RampsController extends BaseController {
|
|
|
254
324
|
if (abortController.signal.aborted) {
|
|
255
325
|
throw error;
|
|
256
326
|
}
|
|
257
|
-
const errorMessage = error?.message;
|
|
258
|
-
__classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_updateRequestState).call(this, cacheKey, createErrorState(errorMessage
|
|
327
|
+
const errorMessage = error?.message ?? 'Unknown error';
|
|
328
|
+
__classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_updateRequestState).call(this, cacheKey, createErrorState(errorMessage, lastFetchedAt));
|
|
329
|
+
if (resourceType) {
|
|
330
|
+
const isCurrent = !options?.isResultCurrent || options.isResultCurrent();
|
|
331
|
+
if (isCurrent) {
|
|
332
|
+
__classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_setResourceError).call(this, resourceType, errorMessage);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
259
335
|
throw error;
|
|
260
336
|
}
|
|
261
337
|
finally {
|
|
@@ -264,6 +340,18 @@ export class RampsController extends BaseController {
|
|
|
264
340
|
if (currentPending?.abortController === abortController) {
|
|
265
341
|
__classPrivateFieldGet(this, _RampsController_pendingRequests, "f").delete(cacheKey);
|
|
266
342
|
}
|
|
343
|
+
// Clear resource-level loading state only when no requests for this resource remain
|
|
344
|
+
if (resourceType) {
|
|
345
|
+
const count = __classPrivateFieldGet(this, _RampsController_pendingResourceCount, "f").get(resourceType) ?? 0;
|
|
346
|
+
const next = Math.max(0, count - 1);
|
|
347
|
+
if (next === 0) {
|
|
348
|
+
__classPrivateFieldGet(this, _RampsController_pendingResourceCount, "f").delete(resourceType);
|
|
349
|
+
__classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_setResourceLoading).call(this, resourceType, false);
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
__classPrivateFieldGet(this, _RampsController_pendingResourceCount, "f").set(resourceType, next);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
267
355
|
}
|
|
268
356
|
})();
|
|
269
357
|
// Store pending request for deduplication
|
|
@@ -306,37 +394,40 @@ export class RampsController extends BaseController {
|
|
|
306
394
|
async setUserRegion(region, options) {
|
|
307
395
|
const normalizedRegion = region.toLowerCase().trim();
|
|
308
396
|
try {
|
|
309
|
-
const
|
|
310
|
-
if (!
|
|
397
|
+
const countriesData = this.state.countries.data;
|
|
398
|
+
if (!countriesData || countriesData.length === 0) {
|
|
311
399
|
__classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_cleanupState).call(this);
|
|
312
400
|
throw new Error('No countries found. Cannot set user region without valid country information.');
|
|
313
401
|
}
|
|
314
|
-
const userRegion = findRegionFromCode(normalizedRegion,
|
|
402
|
+
const userRegion = findRegionFromCode(normalizedRegion, countriesData);
|
|
315
403
|
if (!userRegion) {
|
|
316
404
|
__classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_cleanupState).call(this);
|
|
317
405
|
throw new Error(`Region "${normalizedRegion}" not found in countries data. Cannot set user region without valid country information.`);
|
|
318
406
|
}
|
|
319
|
-
// Only cleanup state if region is actually changing
|
|
320
407
|
const regionChanged = normalizedRegion !== this.state.userRegion?.regionCode;
|
|
321
|
-
|
|
408
|
+
const needsRefetch = regionChanged ||
|
|
409
|
+
!this.state.tokens.data ||
|
|
410
|
+
this.state.providers.data.length === 0;
|
|
411
|
+
if (regionChanged) {
|
|
412
|
+
__classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_clearPendingResourceCountForDependentResources).call(this);
|
|
413
|
+
}
|
|
322
414
|
this.update((state) => {
|
|
323
415
|
if (regionChanged) {
|
|
324
|
-
state
|
|
325
|
-
state.selectedToken = null;
|
|
326
|
-
state.tokens = null;
|
|
327
|
-
state.providers = [];
|
|
328
|
-
state.paymentMethods = [];
|
|
329
|
-
state.selectedPaymentMethod = null;
|
|
330
|
-
state.quotes = null;
|
|
416
|
+
resetDependentResources(state);
|
|
331
417
|
}
|
|
332
418
|
state.userRegion = userRegion;
|
|
333
419
|
});
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
this.
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
this.
|
|
420
|
+
if (needsRefetch) {
|
|
421
|
+
const refetchPromises = [];
|
|
422
|
+
if (regionChanged || !this.state.tokens.data) {
|
|
423
|
+
refetchPromises.push(this.getTokens(userRegion.regionCode, 'buy', options));
|
|
424
|
+
}
|
|
425
|
+
if (regionChanged || this.state.providers.data.length === 0) {
|
|
426
|
+
refetchPromises.push(this.getProviders(userRegion.regionCode, options));
|
|
427
|
+
}
|
|
428
|
+
if (refetchPromises.length > 0) {
|
|
429
|
+
__classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_fireAndForget).call(this, Promise.all(refetchPromises));
|
|
430
|
+
}
|
|
340
431
|
}
|
|
341
432
|
return userRegion;
|
|
342
433
|
}
|
|
@@ -356,9 +447,9 @@ export class RampsController extends BaseController {
|
|
|
356
447
|
setSelectedProvider(providerId) {
|
|
357
448
|
if (providerId === null) {
|
|
358
449
|
this.update((state) => {
|
|
359
|
-
state.
|
|
360
|
-
state.paymentMethods = [];
|
|
361
|
-
state.
|
|
450
|
+
state.providers.selected = null;
|
|
451
|
+
state.paymentMethods.data = [];
|
|
452
|
+
state.paymentMethods.selected = null;
|
|
362
453
|
});
|
|
363
454
|
return;
|
|
364
455
|
}
|
|
@@ -366,7 +457,7 @@ export class RampsController extends BaseController {
|
|
|
366
457
|
if (!regionCode) {
|
|
367
458
|
throw new Error('Region is required. Cannot set selected provider without valid region information.');
|
|
368
459
|
}
|
|
369
|
-
const
|
|
460
|
+
const providers = this.state.providers.data;
|
|
370
461
|
if (!providers || providers.length === 0) {
|
|
371
462
|
throw new Error('Providers not loaded. Cannot set selected provider before providers are fetched.');
|
|
372
463
|
}
|
|
@@ -375,16 +466,11 @@ export class RampsController extends BaseController {
|
|
|
375
466
|
throw new Error(`Provider with ID "${providerId}" not found in available providers.`);
|
|
376
467
|
}
|
|
377
468
|
this.update((state) => {
|
|
378
|
-
state.
|
|
379
|
-
state.paymentMethods = [];
|
|
380
|
-
state.
|
|
381
|
-
});
|
|
382
|
-
// fetch payment methods for the new provider
|
|
383
|
-
// this is needed because you can change providers without changing the token
|
|
384
|
-
// (getPaymentMethods will use state as its default)
|
|
385
|
-
this.triggerGetPaymentMethods(regionCode, {
|
|
386
|
-
provider: provider.id,
|
|
469
|
+
state.providers.selected = provider;
|
|
470
|
+
state.paymentMethods.data = [];
|
|
471
|
+
state.paymentMethods.selected = null;
|
|
387
472
|
});
|
|
473
|
+
__classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_fireAndForget).call(this, this.getPaymentMethods(regionCode, { provider: provider.id }));
|
|
388
474
|
}
|
|
389
475
|
/**
|
|
390
476
|
* Initializes the controller by fetching the user's region from geolocation.
|
|
@@ -410,8 +496,8 @@ export class RampsController extends BaseController {
|
|
|
410
496
|
if (!regionCode) {
|
|
411
497
|
throw new Error('Region code is required. Cannot hydrate state without valid region information.');
|
|
412
498
|
}
|
|
413
|
-
this.
|
|
414
|
-
this.
|
|
499
|
+
__classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_fireAndForget).call(this, this.getTokens(regionCode, 'buy', options));
|
|
500
|
+
__classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_fireAndForget).call(this, this.getProviders(regionCode, options));
|
|
415
501
|
}
|
|
416
502
|
/**
|
|
417
503
|
* Fetches the list of supported countries.
|
|
@@ -425,9 +511,9 @@ export class RampsController extends BaseController {
|
|
|
425
511
|
const cacheKey = createCacheKey('getCountries', []);
|
|
426
512
|
const countries = await this.executeRequest(cacheKey, async () => {
|
|
427
513
|
return this.messenger.call('RampsService:getCountries');
|
|
428
|
-
}, options);
|
|
514
|
+
}, { ...options, resourceType: 'countries' });
|
|
429
515
|
this.update((state) => {
|
|
430
|
-
state.countries = countries;
|
|
516
|
+
state.countries.data = countries;
|
|
431
517
|
});
|
|
432
518
|
return countries;
|
|
433
519
|
}
|
|
@@ -456,11 +542,16 @@ export class RampsController extends BaseController {
|
|
|
456
542
|
return this.messenger.call('RampsService:getTokens', normalizedRegion, action, {
|
|
457
543
|
provider: options?.provider,
|
|
458
544
|
});
|
|
459
|
-
},
|
|
545
|
+
}, {
|
|
546
|
+
...options,
|
|
547
|
+
resourceType: 'tokens',
|
|
548
|
+
isResultCurrent: () => this.state.userRegion?.regionCode === undefined ||
|
|
549
|
+
this.state.userRegion?.regionCode === normalizedRegion,
|
|
550
|
+
});
|
|
460
551
|
this.update((state) => {
|
|
461
552
|
const userRegionCode = state.userRegion?.regionCode;
|
|
462
553
|
if (userRegionCode === undefined || userRegionCode === normalizedRegion) {
|
|
463
|
-
state.tokens = tokens;
|
|
554
|
+
state.tokens.data = tokens;
|
|
464
555
|
}
|
|
465
556
|
});
|
|
466
557
|
return tokens;
|
|
@@ -476,9 +567,9 @@ export class RampsController extends BaseController {
|
|
|
476
567
|
setSelectedToken(assetId) {
|
|
477
568
|
if (!assetId) {
|
|
478
569
|
this.update((state) => {
|
|
479
|
-
state.
|
|
480
|
-
state.paymentMethods = [];
|
|
481
|
-
state.
|
|
570
|
+
state.tokens.selected = null;
|
|
571
|
+
state.paymentMethods.data = [];
|
|
572
|
+
state.paymentMethods.selected = null;
|
|
482
573
|
});
|
|
483
574
|
return;
|
|
484
575
|
}
|
|
@@ -486,7 +577,7 @@ export class RampsController extends BaseController {
|
|
|
486
577
|
if (!regionCode) {
|
|
487
578
|
throw new Error('Region is required. Cannot set selected token without valid region information.');
|
|
488
579
|
}
|
|
489
|
-
const
|
|
580
|
+
const tokens = this.state.tokens.data;
|
|
490
581
|
if (!tokens) {
|
|
491
582
|
throw new Error('Tokens not loaded. Cannot set selected token before tokens are fetched.');
|
|
492
583
|
}
|
|
@@ -496,13 +587,11 @@ export class RampsController extends BaseController {
|
|
|
496
587
|
throw new Error(`Token with asset ID "${assetId}" not found in available tokens.`);
|
|
497
588
|
}
|
|
498
589
|
this.update((state) => {
|
|
499
|
-
state.
|
|
500
|
-
state.paymentMethods = [];
|
|
501
|
-
state.
|
|
502
|
-
});
|
|
503
|
-
this.triggerGetPaymentMethods(regionCode, {
|
|
504
|
-
assetId: token.assetId,
|
|
590
|
+
state.tokens.selected = token;
|
|
591
|
+
state.paymentMethods.data = [];
|
|
592
|
+
state.paymentMethods.selected = null;
|
|
505
593
|
});
|
|
594
|
+
__classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_fireAndForget).call(this, this.getPaymentMethods(regionCode, { assetId: token.assetId }));
|
|
506
595
|
}
|
|
507
596
|
/**
|
|
508
597
|
* Fetches the list of providers for a given region.
|
|
@@ -536,11 +625,16 @@ export class RampsController extends BaseController {
|
|
|
536
625
|
fiat: options?.fiat,
|
|
537
626
|
payments: options?.payments,
|
|
538
627
|
});
|
|
539
|
-
},
|
|
628
|
+
}, {
|
|
629
|
+
...options,
|
|
630
|
+
resourceType: 'providers',
|
|
631
|
+
isResultCurrent: () => this.state.userRegion?.regionCode === undefined ||
|
|
632
|
+
this.state.userRegion?.regionCode === normalizedRegion,
|
|
633
|
+
});
|
|
540
634
|
this.update((state) => {
|
|
541
635
|
const userRegionCode = state.userRegion?.regionCode;
|
|
542
636
|
if (userRegionCode === undefined || userRegionCode === normalizedRegion) {
|
|
543
|
-
state.providers = providers;
|
|
637
|
+
state.providers.data = providers;
|
|
544
638
|
}
|
|
545
639
|
});
|
|
546
640
|
return { providers };
|
|
@@ -559,8 +653,8 @@ export class RampsController extends BaseController {
|
|
|
559
653
|
async getPaymentMethods(region, options) {
|
|
560
654
|
const regionCode = region ?? this.state.userRegion?.regionCode ?? null;
|
|
561
655
|
const fiatToUse = options?.fiat ?? this.state.userRegion?.country?.currency ?? null;
|
|
562
|
-
const assetIdToUse = options?.assetId ?? this.state.
|
|
563
|
-
const providerToUse = options?.provider ?? this.state.
|
|
656
|
+
const assetIdToUse = options?.assetId ?? this.state.tokens.selected?.assetId ?? '';
|
|
657
|
+
const providerToUse = options?.provider ?? this.state.providers.selected?.id ?? '';
|
|
564
658
|
if (!regionCode) {
|
|
565
659
|
throw new Error('Region is required. Either provide a region parameter or ensure userRegion is set in controller state.');
|
|
566
660
|
}
|
|
@@ -582,21 +676,31 @@ export class RampsController extends BaseController {
|
|
|
582
676
|
assetId: assetIdToUse,
|
|
583
677
|
provider: providerToUse,
|
|
584
678
|
});
|
|
585
|
-
},
|
|
679
|
+
}, {
|
|
680
|
+
...options,
|
|
681
|
+
resourceType: 'paymentMethods',
|
|
682
|
+
isResultCurrent: () => {
|
|
683
|
+
const regionMatch = this.state.userRegion?.regionCode === undefined ||
|
|
684
|
+
this.state.userRegion?.regionCode === normalizedRegion;
|
|
685
|
+
const tokenMatch = (this.state.tokens.selected?.assetId ?? '') === assetIdToUse;
|
|
686
|
+
const providerMatch = (this.state.providers.selected?.id ?? '') === providerToUse;
|
|
687
|
+
return regionMatch && tokenMatch && providerMatch;
|
|
688
|
+
},
|
|
689
|
+
});
|
|
586
690
|
this.update((state) => {
|
|
587
|
-
const currentAssetId = state.
|
|
588
|
-
const currentProviderId = state.
|
|
691
|
+
const currentAssetId = state.tokens.selected?.assetId ?? '';
|
|
692
|
+
const currentProviderId = state.providers.selected?.id ?? '';
|
|
589
693
|
const tokenSelectionUnchanged = assetIdToUse === currentAssetId;
|
|
590
694
|
const providerSelectionUnchanged = providerToUse === currentProviderId;
|
|
591
695
|
// this is a race condition check to ensure that the selected token and provider in state are the same as the tokens we're requesting for
|
|
592
696
|
// ex: if the user rapidly changes the token or provider, the in-flight payment methods might not be valid
|
|
593
697
|
// so this check will ensure that the payment methods are still valid for the token and provider that were requested
|
|
594
698
|
if (tokenSelectionUnchanged && providerSelectionUnchanged) {
|
|
595
|
-
state.paymentMethods = response.payments;
|
|
699
|
+
state.paymentMethods.data = response.payments;
|
|
596
700
|
// this will auto-select the first payment method if the selected payment method is not in the new payment methods
|
|
597
|
-
const currentSelectionStillValid = response.payments.some((pm) => pm.id === state.
|
|
701
|
+
const currentSelectionStillValid = response.payments.some((pm) => pm.id === state.paymentMethods.selected?.id);
|
|
598
702
|
if (!currentSelectionStillValid) {
|
|
599
|
-
state.
|
|
703
|
+
state.paymentMethods.selected = response.payments[0] ?? null;
|
|
600
704
|
}
|
|
601
705
|
}
|
|
602
706
|
});
|
|
@@ -612,11 +716,11 @@ export class RampsController extends BaseController {
|
|
|
612
716
|
setSelectedPaymentMethod(paymentMethodId) {
|
|
613
717
|
if (!paymentMethodId) {
|
|
614
718
|
this.update((state) => {
|
|
615
|
-
state.
|
|
719
|
+
state.paymentMethods.selected = null;
|
|
616
720
|
});
|
|
617
721
|
return;
|
|
618
722
|
}
|
|
619
|
-
const
|
|
723
|
+
const paymentMethods = this.state.paymentMethods.data;
|
|
620
724
|
if (!paymentMethods || paymentMethods.length === 0) {
|
|
621
725
|
throw new Error('Payment methods not loaded. Cannot set selected payment method before payment methods are fetched.');
|
|
622
726
|
}
|
|
@@ -625,7 +729,7 @@ export class RampsController extends BaseController {
|
|
|
625
729
|
throw new Error(`Payment method with ID "${paymentMethodId}" not found in available payment methods.`);
|
|
626
730
|
}
|
|
627
731
|
this.update((state) => {
|
|
628
|
-
state.
|
|
732
|
+
state.paymentMethods.selected = paymentMethod;
|
|
629
733
|
});
|
|
630
734
|
}
|
|
631
735
|
/**
|
|
@@ -650,7 +754,7 @@ export class RampsController extends BaseController {
|
|
|
650
754
|
const regionToUse = options.region ?? this.state.userRegion?.regionCode;
|
|
651
755
|
const fiatToUse = options.fiat ?? this.state.userRegion?.country?.currency;
|
|
652
756
|
const paymentMethodsToUse = options.paymentMethods ??
|
|
653
|
-
this.state.paymentMethods.map((pm) => pm.id);
|
|
757
|
+
this.state.paymentMethods.data.map((pm) => pm.id);
|
|
654
758
|
const action = options.action ?? 'buy';
|
|
655
759
|
if (!regionToUse) {
|
|
656
760
|
throw new Error('Region is required. Either provide a region parameter or ensure userRegion is set in controller state.');
|
|
@@ -701,11 +805,14 @@ export class RampsController extends BaseController {
|
|
|
701
805
|
}, {
|
|
702
806
|
forceRefresh: options.forceRefresh,
|
|
703
807
|
ttl: options.ttl ?? DEFAULT_QUOTES_TTL,
|
|
808
|
+
resourceType: 'quotes',
|
|
809
|
+
isResultCurrent: () => this.state.userRegion?.regionCode === undefined ||
|
|
810
|
+
this.state.userRegion?.regionCode === normalizedRegion,
|
|
704
811
|
});
|
|
705
812
|
this.update((state) => {
|
|
706
813
|
const userRegionCode = state.userRegion?.regionCode;
|
|
707
814
|
if (userRegionCode === undefined || userRegionCode === normalizedRegion) {
|
|
708
|
-
state.quotes = response;
|
|
815
|
+
state.quotes.data = response;
|
|
709
816
|
}
|
|
710
817
|
});
|
|
711
818
|
return response;
|
|
@@ -720,107 +827,40 @@ export class RampsController extends BaseController {
|
|
|
720
827
|
getWidgetUrl(quote) {
|
|
721
828
|
return quote.quote?.widgetUrl ?? null;
|
|
722
829
|
}
|
|
723
|
-
// ============================================================
|
|
724
|
-
// Sync Trigger Methods
|
|
725
|
-
// These fire-and-forget methods are for use in React effects.
|
|
726
|
-
// Errors are stored in state and available via selectors.
|
|
727
|
-
// ============================================================
|
|
728
|
-
/**
|
|
729
|
-
* Triggers setting the user region without throwing.
|
|
730
|
-
*
|
|
731
|
-
* @param region - The region code to set (e.g., "US-CA").
|
|
732
|
-
* @param options - Options for cache behavior.
|
|
733
|
-
*/
|
|
734
|
-
triggerSetUserRegion(region, options) {
|
|
735
|
-
this.setUserRegion(region, options).catch(() => {
|
|
736
|
-
// Error stored in state
|
|
737
|
-
});
|
|
738
|
-
}
|
|
739
|
-
/**
|
|
740
|
-
* Triggers fetching countries without throwing.
|
|
741
|
-
*
|
|
742
|
-
* @param options - Options for cache behavior.
|
|
743
|
-
*/
|
|
744
|
-
triggerGetCountries(options) {
|
|
745
|
-
this.getCountries(options).catch(() => {
|
|
746
|
-
// Error stored in state
|
|
747
|
-
});
|
|
748
|
-
}
|
|
749
|
-
/**
|
|
750
|
-
* Triggers fetching tokens without throwing.
|
|
751
|
-
*
|
|
752
|
-
* @param region - The region code. If not provided, uses userRegion from state.
|
|
753
|
-
* @param action - The ramp action type ('buy' or 'sell').
|
|
754
|
-
* @param options - Options for cache behavior.
|
|
755
|
-
*/
|
|
756
|
-
triggerGetTokens(region, action = 'buy', options) {
|
|
757
|
-
this.getTokens(region, action, options).catch(() => {
|
|
758
|
-
// Error stored in state
|
|
759
|
-
});
|
|
760
|
-
}
|
|
761
|
-
/**
|
|
762
|
-
* Triggers fetching providers without throwing.
|
|
763
|
-
*
|
|
764
|
-
* @param region - The region code. If not provided, uses userRegion from state.
|
|
765
|
-
* @param options - Options for cache behavior and query filters.
|
|
766
|
-
*/
|
|
767
|
-
triggerGetProviders(region, options) {
|
|
768
|
-
this.getProviders(region, options).catch(() => {
|
|
769
|
-
// Error stored in state
|
|
770
|
-
});
|
|
771
|
-
}
|
|
772
|
-
/**
|
|
773
|
-
* Triggers fetching payment methods without throwing.
|
|
774
|
-
*
|
|
775
|
-
* @param region - User's region code (e.g., "us", "fr", "us-ny").
|
|
776
|
-
* @param options - Query parameters for filtering payment methods.
|
|
777
|
-
* @param options.fiat - Fiat currency code. If not provided, uses userRegion currency.
|
|
778
|
-
* @param options.assetId - CAIP-19 cryptocurrency identifier.
|
|
779
|
-
* @param options.provider - Provider ID path.
|
|
780
|
-
*/
|
|
781
|
-
triggerGetPaymentMethods(region, options) {
|
|
782
|
-
this.getPaymentMethods(region, options).catch(() => {
|
|
783
|
-
// Error stored in state
|
|
784
|
-
});
|
|
785
|
-
}
|
|
786
|
-
/**
|
|
787
|
-
* Triggers fetching quotes without throwing.
|
|
788
|
-
*
|
|
789
|
-
* @param options - The parameters for fetching quotes.
|
|
790
|
-
* @param options.region - User's region code. If not provided, uses userRegion from state.
|
|
791
|
-
* @param options.fiat - Fiat currency code. If not provided, uses userRegion currency.
|
|
792
|
-
* @param options.assetId - CAIP-19 cryptocurrency identifier.
|
|
793
|
-
* @param options.amount - The amount (in fiat for buy, crypto for sell).
|
|
794
|
-
* @param options.walletAddress - The destination wallet address.
|
|
795
|
-
* @param options.paymentMethods - Array of payment method IDs. If not provided, uses paymentMethods from state.
|
|
796
|
-
* @param options.provider - Optional provider ID to filter quotes.
|
|
797
|
-
* @param options.redirectUrl - Optional redirect URL after order completion.
|
|
798
|
-
* @param options.action - The ramp action type. Defaults to 'buy'.
|
|
799
|
-
* @param options.forceRefresh - Whether to bypass cache.
|
|
800
|
-
* @param options.ttl - Custom TTL for this request.
|
|
801
|
-
*/
|
|
802
|
-
triggerGetQuotes(options) {
|
|
803
|
-
this.getQuotes(options).catch(() => {
|
|
804
|
-
// Error stored in state
|
|
805
|
-
});
|
|
806
|
-
}
|
|
807
830
|
}
|
|
808
|
-
_RampsController_requestCacheTTL = new WeakMap(), _RampsController_requestCacheMaxSize = new WeakMap(), _RampsController_pendingRequests = new WeakMap(), _RampsController_instances = new WeakSet(),
|
|
831
|
+
_RampsController_requestCacheTTL = new WeakMap(), _RampsController_requestCacheMaxSize = new WeakMap(), _RampsController_pendingRequests = new WeakMap(), _RampsController_pendingResourceCount = new WeakMap(), _RampsController_instances = new WeakSet(), _RampsController_clearPendingResourceCountForDependentResources = function _RampsController_clearPendingResourceCountForDependentResources() {
|
|
832
|
+
const types = [
|
|
833
|
+
'providers',
|
|
834
|
+
'tokens',
|
|
835
|
+
'paymentMethods',
|
|
836
|
+
'quotes',
|
|
837
|
+
];
|
|
838
|
+
for (const resourceType of types) {
|
|
839
|
+
__classPrivateFieldGet(this, _RampsController_pendingResourceCount, "f").delete(resourceType);
|
|
840
|
+
}
|
|
841
|
+
}, _RampsController_removeRequestState = function _RampsController_removeRequestState(cacheKey) {
|
|
809
842
|
this.update((state) => {
|
|
810
843
|
const requests = state.requests;
|
|
811
844
|
delete requests[cacheKey];
|
|
812
845
|
});
|
|
813
846
|
}, _RampsController_cleanupState = function _RampsController_cleanupState() {
|
|
847
|
+
__classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_clearPendingResourceCountForDependentResources).call(this);
|
|
848
|
+
this.update((state) => resetDependentResources(state, {
|
|
849
|
+
clearUserRegionData: true,
|
|
850
|
+
}));
|
|
851
|
+
}, _RampsController_fireAndForget = function _RampsController_fireAndForget(promise) {
|
|
852
|
+
promise.catch((_error) => undefined);
|
|
853
|
+
}, _RampsController_updateResourceField = function _RampsController_updateResourceField(resourceType, field, value) {
|
|
814
854
|
this.update((state) => {
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
state.providers = [];
|
|
820
|
-
state.paymentMethods = [];
|
|
821
|
-
state.selectedPaymentMethod = null;
|
|
822
|
-
state.quotes = null;
|
|
855
|
+
const resource = state[resourceType];
|
|
856
|
+
if (resource) {
|
|
857
|
+
resource[field] = value;
|
|
858
|
+
}
|
|
823
859
|
});
|
|
860
|
+
}, _RampsController_setResourceLoading = function _RampsController_setResourceLoading(resourceType, loading) {
|
|
861
|
+
__classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_updateResourceField).call(this, resourceType, 'isLoading', loading);
|
|
862
|
+
}, _RampsController_setResourceError = function _RampsController_setResourceError(resourceType, error) {
|
|
863
|
+
__classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_updateResourceField).call(this, resourceType, 'error', error);
|
|
824
864
|
}, _RampsController_updateRequestState = function _RampsController_updateRequestState(cacheKey, requestState) {
|
|
825
865
|
const maxSize = __classPrivateFieldGet(this, _RampsController_requestCacheMaxSize, "f");
|
|
826
866
|
const ttl = __classPrivateFieldGet(this, _RampsController_requestCacheTTL, "f");
|