@metamask-previews/ramps-controller 15.0.0-preview-022b2ce8d → 15.0.0-preview-8980fa5
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 +6 -0
- package/dist/RampsController.cjs +146 -8
- package/dist/RampsController.cjs.map +1 -1
- package/dist/RampsController.d.cts +33 -1
- package/dist/RampsController.d.cts.map +1 -1
- package/dist/RampsController.d.mts +33 -1
- package/dist/RampsController.d.mts.map +1 -1
- package/dist/RampsController.mjs +146 -8
- package/dist/RampsController.mjs.map +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,11 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Add an optional `getProviderScope` callback to `RampsControllerOptions` and export the `ProviderScope` type (`'off' | 'in-app' | 'all'`); when it returns a non-`off` scope, `RampsController.getQuotes` widens its native-only auto-selection (the `autoSelectProvider` / `restrictToKnownOrNativeProviders` path) to every supporting provider and returns the best in-app quote at `success[0]`, excluding external-browser and custom-action quotes and enforcing per-provider fiat limits, while the default stays native-only and explicit-`providers` callers are unaffected ([#9353](https://github.com/MetaMask/core/pull/9353))
|
|
13
|
+
- Add an optional `getDefaultRedirectUrl` callback to `RampsControllerOptions`; on the widened in-app auto-selection path, when the caller omits `redirectUrl`, `RampsController.getQuotes` now supplies this default so aggregator quotes carry the `buyURL`/`buyWidget` widget URL the app needs, while an explicit caller `redirectUrl` always wins and scope `off` never injects a default ([#9353](https://github.com/MetaMask/core/pull/9353))
|
|
14
|
+
|
|
10
15
|
### Changed
|
|
11
16
|
|
|
12
17
|
- Refetch the countries catalog on every app startup via `init()` so region preset amounts (e.g. default amounts) stay current ([#9261](https://github.com/MetaMask/core/pull/9261))
|
|
13
18
|
- Re-sync `userRegion` preset amounts from the countries catalog after each `getCountries()` call ([#9261](https://github.com/MetaMask/core/pull/9261))
|
|
14
19
|
- On startup, `init()` now preserves an already-persisted `userRegion` when the refreshed countries catalog is momentarily empty or no longer lists that region, instead of clearing it ([#9261](https://github.com/MetaMask/core/pull/9261))
|
|
20
|
+
- Bump `@metamask/messenger` from `^1.2.0` to `^2.0.0` ([#9392](https://github.com/MetaMask/core/pull/9392))
|
|
15
21
|
|
|
16
22
|
## [15.0.0]
|
|
17
23
|
|
package/dist/RampsController.cjs
CHANGED
|
@@ -10,7 +10,7 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
|
|
|
10
10
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
11
11
|
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
12
12
|
};
|
|
13
|
-
var _RampsController_instances, _RampsController_requestCacheTTL, _RampsController_requestCacheMaxSize, _RampsController_pendingRequests, _RampsController_pendingResourceCount, _RampsController_pendingResourceGeneration, _RampsController_orderPollingMeta, _RampsController_orderPollingTimer, _RampsController_isPolling, _RampsController_initPromise, _RampsController_clearPendingResourceCountForDependentResources, _RampsController_abortDependentRequests, _RampsController_mutateRequests, _RampsController_removeRequestState, _RampsController_cleanupState, _RampsController_requireRegion, _RampsController_isRegionCurrent, _RampsController_isTokenCurrent, _RampsController_isProviderCurrent, _RampsController_updateResourceField, _RampsController_setResourceLoading, _RampsController_setResourceError, _RampsController_updateRequestState, _RampsController_runInit, _RampsController_syncUserRegionFromCountriesCatalog, _RampsController_getSupportingProvidersForRegion, _RampsController_filterProviderIdsBySupport, _RampsController_resolveProviderIdsForQuote, _RampsController_getPreferredProviderIdsFromOrders, _RampsController_refreshOrder, _RampsController_pollPendingOrders, _RampsController_syncTransakAuthOnError, _RampsController_getNormalizedTransakError;
|
|
13
|
+
var _RampsController_instances, _RampsController_requestCacheTTL, _RampsController_requestCacheMaxSize, _RampsController_getProviderScope, _RampsController_getDefaultRedirectUrl, _RampsController_pendingRequests, _RampsController_pendingResourceCount, _RampsController_pendingResourceGeneration, _RampsController_orderPollingMeta, _RampsController_orderPollingTimer, _RampsController_isPolling, _RampsController_initPromise, _RampsController_clearPendingResourceCountForDependentResources, _RampsController_abortDependentRequests, _RampsController_mutateRequests, _RampsController_removeRequestState, _RampsController_cleanupState, _RampsController_requireRegion, _RampsController_isRegionCurrent, _RampsController_isTokenCurrent, _RampsController_isProviderCurrent, _RampsController_updateResourceField, _RampsController_setResourceLoading, _RampsController_setResourceError, _RampsController_updateRequestState, _RampsController_runInit, _RampsController_syncUserRegionFromCountriesCatalog, _RampsController_pickInAppQuote, _RampsController_getSupportingProvidersForRegion, _RampsController_filterProviderIdsBySupport, _RampsController_resolveProviderIdsForQuote, _RampsController_getPreferredProviderIdsFromOrders, _RampsController_refreshOrder, _RampsController_pollPendingOrders, _RampsController_syncTransakAuthOnError, _RampsController_getNormalizedTransakError;
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
exports.RampsController = exports.getInternalOrderCode = exports.normalizeProviderCode = exports.getDefaultRampsControllerState = exports.RAMPS_CONTROLLER_REQUIRED_SERVICE_ACTIONS = exports.controllerName = void 0;
|
|
16
16
|
const base_controller_1 = require("@metamask/base-controller");
|
|
@@ -441,8 +441,13 @@ class RampsController extends base_controller_1.BaseController {
|
|
|
441
441
|
* controller. Missing properties will be filled in with defaults.
|
|
442
442
|
* @param args.requestCacheTTL - Time to live for cached requests in milliseconds.
|
|
443
443
|
* @param args.requestCacheMaxSize - Maximum number of entries in the request cache.
|
|
444
|
+
* @param args.getProviderScope - Optional callback returning the current
|
|
445
|
+
* provider-class scope for fiat quote widening. Defaults to `off`.
|
|
446
|
+
* @param args.getDefaultRedirectUrl - Optional callback returning the default
|
|
447
|
+
* redirect URL used for the widened in-app quote fetch when the caller omits
|
|
448
|
+
* `redirectUrl`. Defaults to a callback returning `undefined`.
|
|
444
449
|
*/
|
|
445
|
-
constructor({ messenger, state = {}, requestCacheTTL = RequestCache_1.DEFAULT_REQUEST_CACHE_TTL, requestCacheMaxSize = RequestCache_1.DEFAULT_REQUEST_CACHE_MAX_SIZE, }) {
|
|
450
|
+
constructor({ messenger, state = {}, requestCacheTTL = RequestCache_1.DEFAULT_REQUEST_CACHE_TTL, requestCacheMaxSize = RequestCache_1.DEFAULT_REQUEST_CACHE_MAX_SIZE, getProviderScope, getDefaultRedirectUrl, }) {
|
|
446
451
|
super({
|
|
447
452
|
messenger,
|
|
448
453
|
metadata: rampsControllerMetadata,
|
|
@@ -463,6 +468,17 @@ class RampsController extends base_controller_1.BaseController {
|
|
|
463
468
|
* Maximum number of entries in the request cache.
|
|
464
469
|
*/
|
|
465
470
|
_RampsController_requestCacheMaxSize.set(this, void 0);
|
|
471
|
+
/**
|
|
472
|
+
* Resolves the current provider-class scope for fiat quote widening. Defaults
|
|
473
|
+
* to `() => 'off'` (native-only) when no callback is injected.
|
|
474
|
+
*/
|
|
475
|
+
_RampsController_getProviderScope.set(this, void 0);
|
|
476
|
+
/**
|
|
477
|
+
* Resolves the default redirect URL for the widened in-app quote fetch when
|
|
478
|
+
* the caller omits `redirectUrl`. Defaults to `() => undefined` when no
|
|
479
|
+
* callback is injected.
|
|
480
|
+
*/
|
|
481
|
+
_RampsController_getDefaultRedirectUrl.set(this, void 0);
|
|
466
482
|
/**
|
|
467
483
|
* Map of pending requests for deduplication.
|
|
468
484
|
* Key is the cache key, value is the pending request with abort controller.
|
|
@@ -484,6 +500,8 @@ class RampsController extends base_controller_1.BaseController {
|
|
|
484
500
|
_RampsController_initPromise.set(this, null);
|
|
485
501
|
__classPrivateFieldSet(this, _RampsController_requestCacheTTL, requestCacheTTL, "f");
|
|
486
502
|
__classPrivateFieldSet(this, _RampsController_requestCacheMaxSize, requestCacheMaxSize, "f");
|
|
503
|
+
__classPrivateFieldSet(this, _RampsController_getProviderScope, getProviderScope ?? (() => 'off'), "f");
|
|
504
|
+
__classPrivateFieldSet(this, _RampsController_getDefaultRedirectUrl, getDefaultRedirectUrl ?? (() => undefined), "f");
|
|
487
505
|
this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
|
|
488
506
|
}
|
|
489
507
|
/**
|
|
@@ -998,7 +1016,19 @@ class RampsController extends base_controller_1.BaseController {
|
|
|
998
1016
|
if (normalizedAssetIdForValidation === '') {
|
|
999
1017
|
throw new Error('assetId is required.');
|
|
1000
1018
|
}
|
|
1019
|
+
// When a non-`off` provider scope is active, widen the native-only
|
|
1020
|
+
// auto-selection path to every supporting provider and pick the best in-app
|
|
1021
|
+
// quote from the results (in-app vs external is only knowable per-quote via
|
|
1022
|
+
// `buyWidget.browser`). Only the auto-select/restrict path that MM Pay's
|
|
1023
|
+
// `getRampsQuote` uses is affected; explicit-`providers` callers and the
|
|
1024
|
+
// plain all-provider path are untouched.
|
|
1025
|
+
const providerScope = __classPrivateFieldGet(this, _RampsController_getProviderScope, "f").call(this);
|
|
1026
|
+
const widenToInAppProviders = providerScope !== 'off' &&
|
|
1027
|
+
!options.providers &&
|
|
1028
|
+
(options.autoSelectProvider === true ||
|
|
1029
|
+
options.restrictToKnownOrNativeProviders === true);
|
|
1001
1030
|
let providersToUse;
|
|
1031
|
+
let inAppProviderCatalog = this.state.providers.data;
|
|
1002
1032
|
if (options.providers) {
|
|
1003
1033
|
providersToUse = options.restrictToKnownOrNativeProviders
|
|
1004
1034
|
? await __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_filterProviderIdsBySupport).call(this, {
|
|
@@ -1008,6 +1038,17 @@ class RampsController extends base_controller_1.BaseController {
|
|
|
1008
1038
|
})
|
|
1009
1039
|
: options.providers;
|
|
1010
1040
|
}
|
|
1041
|
+
else if (widenToInAppProviders) {
|
|
1042
|
+
// `#getSupportingProvidersForRegion` also hydrates the provider catalog
|
|
1043
|
+
// when controller state is empty, so all-provider quoting cannot silently
|
|
1044
|
+
// return zero providers here.
|
|
1045
|
+
const { supporting } = await __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_getSupportingProvidersForRegion).call(this, {
|
|
1046
|
+
assetId: normalizedAssetIdForValidation,
|
|
1047
|
+
region: regionToUse,
|
|
1048
|
+
});
|
|
1049
|
+
inAppProviderCatalog = supporting;
|
|
1050
|
+
providersToUse = supporting.map((provider) => provider.id);
|
|
1051
|
+
}
|
|
1011
1052
|
else if (options.autoSelectProvider ||
|
|
1012
1053
|
options.restrictToKnownOrNativeProviders) {
|
|
1013
1054
|
// The restriction flag implies resolution: it must narrow the provider
|
|
@@ -1037,8 +1078,12 @@ class RampsController extends base_controller_1.BaseController {
|
|
|
1037
1078
|
// Under headless-buy gating, an empty resolved provider list means no
|
|
1038
1079
|
// eligible (native/supporting) provider exists. Return an empty response
|
|
1039
1080
|
// rather than passing `[]` to the service, which omits the provider filter
|
|
1040
|
-
// and would quote every provider.
|
|
1041
|
-
|
|
1081
|
+
// and would quote every provider. This also guards the widened in-app path:
|
|
1082
|
+
// a caller may trigger widening with `autoSelectProvider` alone (no
|
|
1083
|
+
// `restrictToKnownOrNativeProviders`), and an empty supporting set must not
|
|
1084
|
+
// fall through to unfiltered quotes from providers that do not support the
|
|
1085
|
+
// asset.
|
|
1086
|
+
if ((options.restrictToKnownOrNativeProviders || widenToInAppProviders) &&
|
|
1042
1087
|
providersToUse.length === 0) {
|
|
1043
1088
|
return { success: [], sorted: [], error: [], customActions: [] };
|
|
1044
1089
|
}
|
|
@@ -1046,6 +1091,12 @@ class RampsController extends base_controller_1.BaseController {
|
|
|
1046
1091
|
const normalizedFiat = fiatToUse.toLowerCase().trim();
|
|
1047
1092
|
const normalizedAssetId = normalizedAssetIdForValidation;
|
|
1048
1093
|
const normalizedWalletAddress = options.walletAddress.trim();
|
|
1094
|
+
// The quotes API only embeds a `buyURL`/`buyWidget` when a `redirectUrl` is
|
|
1095
|
+
// present, so on the widened in-app path (where MM Pay omits one) supply the
|
|
1096
|
+
// injected default so aggregator quotes carry a usable widget URL. An
|
|
1097
|
+
// explicit caller `redirectUrl` always wins, and scope `off` never injects.
|
|
1098
|
+
const effectiveRedirectUrl = options.redirectUrl ??
|
|
1099
|
+
(widenToInAppProviders ? __classPrivateFieldGet(this, _RampsController_getDefaultRedirectUrl, "f").call(this) : undefined);
|
|
1049
1100
|
const cacheKey = (0, RequestCache_1.createCacheKey)('getQuotes', [
|
|
1050
1101
|
normalizedRegion,
|
|
1051
1102
|
normalizedFiat,
|
|
@@ -1054,7 +1105,7 @@ class RampsController extends base_controller_1.BaseController {
|
|
|
1054
1105
|
normalizedWalletAddress,
|
|
1055
1106
|
[...paymentMethodsToUse].sort().join(','),
|
|
1056
1107
|
[...providersToUse].sort().join(','),
|
|
1057
|
-
|
|
1108
|
+
effectiveRedirectUrl,
|
|
1058
1109
|
action,
|
|
1059
1110
|
]);
|
|
1060
1111
|
const params = {
|
|
@@ -1065,15 +1116,45 @@ class RampsController extends base_controller_1.BaseController {
|
|
|
1065
1116
|
walletAddress: normalizedWalletAddress,
|
|
1066
1117
|
paymentMethods: paymentMethodsToUse,
|
|
1067
1118
|
providers: providersToUse,
|
|
1068
|
-
redirectUrl:
|
|
1119
|
+
redirectUrl: effectiveRedirectUrl,
|
|
1069
1120
|
action,
|
|
1070
1121
|
};
|
|
1071
|
-
|
|
1122
|
+
const response = await this.executeRequest(cacheKey, async () => {
|
|
1072
1123
|
return this.messenger.call('RampsService:getQuotes', params);
|
|
1073
1124
|
}, {
|
|
1074
1125
|
forceRefresh: options.forceRefresh,
|
|
1075
1126
|
ttl: options.ttl ?? DEFAULT_QUOTES_TTL,
|
|
1076
1127
|
});
|
|
1128
|
+
if (!widenToInAppProviders) {
|
|
1129
|
+
return response;
|
|
1130
|
+
}
|
|
1131
|
+
// Reduce the widened multi-provider result to the single best in-app quote
|
|
1132
|
+
// and place it at `success[0]`, since single-pick consumers
|
|
1133
|
+
// (`getRampsQuote` -> `success?.[0]`) rely on index 0 while `success[]`
|
|
1134
|
+
// order is server-defined rather than ranked.
|
|
1135
|
+
const selectedQuote = __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_pickInAppQuote).call(this, response, {
|
|
1136
|
+
scope: providerScope,
|
|
1137
|
+
amount: options.amount,
|
|
1138
|
+
fiat: normalizedFiat,
|
|
1139
|
+
providers: inAppProviderCatalog,
|
|
1140
|
+
});
|
|
1141
|
+
if (!selectedQuote) {
|
|
1142
|
+
// No usable in-app quote: surface "no quote" rather than leaking an
|
|
1143
|
+
// external/custom quote to the single-pick consumer.
|
|
1144
|
+
return {
|
|
1145
|
+
success: [],
|
|
1146
|
+
sorted: response.sorted,
|
|
1147
|
+
error: response.error,
|
|
1148
|
+
customActions: response.customActions,
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
return {
|
|
1152
|
+
...response,
|
|
1153
|
+
success: [
|
|
1154
|
+
selectedQuote,
|
|
1155
|
+
...response.success.filter((quote) => quote !== selectedQuote),
|
|
1156
|
+
],
|
|
1157
|
+
};
|
|
1077
1158
|
}
|
|
1078
1159
|
// === ORDER MANAGEMENT ===
|
|
1079
1160
|
/**
|
|
@@ -1690,7 +1771,7 @@ class RampsController extends base_controller_1.BaseController {
|
|
|
1690
1771
|
}
|
|
1691
1772
|
}
|
|
1692
1773
|
exports.RampsController = RampsController;
|
|
1693
|
-
_RampsController_requestCacheTTL = new WeakMap(), _RampsController_requestCacheMaxSize = new WeakMap(), _RampsController_pendingRequests = new WeakMap(), _RampsController_pendingResourceCount = new WeakMap(), _RampsController_pendingResourceGeneration = new WeakMap(), _RampsController_orderPollingMeta = new WeakMap(), _RampsController_orderPollingTimer = new WeakMap(), _RampsController_isPolling = new WeakMap(), _RampsController_initPromise = new WeakMap(), _RampsController_instances = new WeakSet(), _RampsController_clearPendingResourceCountForDependentResources = function _RampsController_clearPendingResourceCountForDependentResources() {
|
|
1774
|
+
_RampsController_requestCacheTTL = new WeakMap(), _RampsController_requestCacheMaxSize = new WeakMap(), _RampsController_getProviderScope = new WeakMap(), _RampsController_getDefaultRedirectUrl = new WeakMap(), _RampsController_pendingRequests = new WeakMap(), _RampsController_pendingResourceCount = new WeakMap(), _RampsController_pendingResourceGeneration = new WeakMap(), _RampsController_orderPollingMeta = new WeakMap(), _RampsController_orderPollingTimer = new WeakMap(), _RampsController_isPolling = new WeakMap(), _RampsController_initPromise = new WeakMap(), _RampsController_instances = new WeakSet(), _RampsController_clearPendingResourceCountForDependentResources = function _RampsController_clearPendingResourceCountForDependentResources() {
|
|
1694
1775
|
for (const resourceType of DEPENDENT_RESOURCE_KEYS) {
|
|
1695
1776
|
__classPrivateFieldGet(this, _RampsController_pendingResourceCount, "f").delete(resourceType);
|
|
1696
1777
|
const generation = __classPrivateFieldGet(this, _RampsController_pendingResourceGeneration, "f").get(resourceType) ?? 0;
|
|
@@ -1816,6 +1897,63 @@ _RampsController_requestCacheTTL = new WeakMap(), _RampsController_requestCacheM
|
|
|
1816
1897
|
this.update((state) => {
|
|
1817
1898
|
state.userRegion = userRegion;
|
|
1818
1899
|
});
|
|
1900
|
+
}, _RampsController_pickInAppQuote = function _RampsController_pickInAppQuote(response, { scope, amount, fiat, providers, }) {
|
|
1901
|
+
const providerByCode = new Map(providers.map((provider) => [
|
|
1902
|
+
normalizeProviderCode(provider.id),
|
|
1903
|
+
provider,
|
|
1904
|
+
]));
|
|
1905
|
+
const customActionProviderCodes = new Set(response.customActions.map((action) => normalizeProviderCode(action.buy.providerId)));
|
|
1906
|
+
const fitsProviderLimits = (quote) => {
|
|
1907
|
+
const provider = providerByCode.get(normalizeProviderCode(quote.provider));
|
|
1908
|
+
const limit = provider?.limits?.fiat?.[fiat]?.[quote.quote.paymentMethod];
|
|
1909
|
+
if (!limit) {
|
|
1910
|
+
// No published limits for this provider/payment method: treat as
|
|
1911
|
+
// eligible and let the provider enforce limits at checkout.
|
|
1912
|
+
return true;
|
|
1913
|
+
}
|
|
1914
|
+
return amount >= limit.minAmount && amount <= limit.maxAmount;
|
|
1915
|
+
};
|
|
1916
|
+
const isEligible = (quote) => {
|
|
1917
|
+
// `all` (Phase 2) skips the in-app-only exclusions; both scopes still
|
|
1918
|
+
// enforce provider limits up front.
|
|
1919
|
+
if (scope !== 'all') {
|
|
1920
|
+
const providerCode = normalizeProviderCode(quote.provider);
|
|
1921
|
+
if (customActionProviderCodes.has(providerCode)) {
|
|
1922
|
+
return false;
|
|
1923
|
+
}
|
|
1924
|
+
// Defensive: the wire may carry an inline `isCustomAction` flag that is
|
|
1925
|
+
// absent from the published `Quote` type.
|
|
1926
|
+
if (quote.quote.isCustomAction === true) {
|
|
1927
|
+
return false;
|
|
1928
|
+
}
|
|
1929
|
+
if (quote.quote.buyWidget?.browser === 'IN_APP_OS_BROWSER') {
|
|
1930
|
+
return false;
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
return fitsProviderLimits(quote);
|
|
1934
|
+
};
|
|
1935
|
+
const candidates = response.success.filter(isEligible);
|
|
1936
|
+
if (candidates.length === 0) {
|
|
1937
|
+
return undefined;
|
|
1938
|
+
}
|
|
1939
|
+
const candidateByCode = new Map(candidates.map((quote) => [normalizeProviderCode(quote.provider), quote]));
|
|
1940
|
+
const pickBySortOrder = (sortBy) => {
|
|
1941
|
+
const order = response.sorted.find((entry) => entry.sortBy === sortBy)?.ids;
|
|
1942
|
+
if (!order) {
|
|
1943
|
+
return undefined;
|
|
1944
|
+
}
|
|
1945
|
+
for (const providerId of order) {
|
|
1946
|
+
const match = candidateByCode.get(normalizeProviderCode(providerId));
|
|
1947
|
+
if (match) {
|
|
1948
|
+
return match;
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
return undefined;
|
|
1952
|
+
};
|
|
1953
|
+
// Reliability first, then price, then the first surviving candidate.
|
|
1954
|
+
return (pickBySortOrder('reliability') ??
|
|
1955
|
+
pickBySortOrder('price') ??
|
|
1956
|
+
candidates[0]);
|
|
1819
1957
|
}, _RampsController_getSupportingProvidersForRegion =
|
|
1820
1958
|
/**
|
|
1821
1959
|
* Returns the region's providers that support the given asset, plus the full
|