@metamask-previews/ramps-controller 9.0.0-preview-d1f62d044 → 9.0.0-preview-06d68e653

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 CHANGED
@@ -9,9 +9,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ### Changed
11
11
 
12
- - **BREAKING:** Remove `state.quotes` and `state.widgetUrl` from RampsController state. Quote and widget URL data are now managed by consuming components ([#8013](https://github.com/MetaMask/core/pull/8013))
13
- - **BREAKING:** Remove `fetchQuotesForSelection()` and `setSelectedQuote()`. Components call `getQuotes()` directly and manage selection locally ([#8013](https://github.com/MetaMask/core/pull/8013))
14
- - Simplify `getWidgetUrl()` to a pure fetch-and-return API; it no longer reads or writes controller state ([#8013](https://github.com/MetaMask/core/pull/8013))
15
12
  - Improve `TransakService` error handling ([#8010](https://github.com/MetaMask/core/pull/8010))
16
13
  - **BREAKING:** Replace `startQuotePolling()`/`stopQuotePolling()` with `fetchQuotesForSelection()` — quotes are now fetched once per call instead of polling on a 15-second interval ([#7999](https://github.com/MetaMask/core/pull/7999))
17
14
 
@@ -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_clearPendingResourceCountForDependentResources, _RampsController_abortDependentRequests, _RampsController_mutateRequests, _RampsController_removeRequestState, _RampsController_cleanupState, _RampsController_fireAndForget, _RampsController_requireRegion, _RampsController_isRegionCurrent, _RampsController_isTokenCurrent, _RampsController_isProviderCurrent, _RampsController_updateResourceField, _RampsController_setResourceLoading, _RampsController_setResourceError, _RampsController_updateRequestState, _RampsController_syncTransakAuthOnError;
13
+ var _RampsController_instances, _RampsController_requestCacheTTL, _RampsController_requestCacheMaxSize, _RampsController_pendingRequests, _RampsController_pendingResourceCount, _RampsController_clearPendingResourceCountForDependentResources, _RampsController_abortDependentRequests, _RampsController_mutateRequests, _RampsController_removeRequestState, _RampsController_cleanupState, _RampsController_fireAndForget, _RampsController_requireRegion, _RampsController_isRegionCurrent, _RampsController_isTokenCurrent, _RampsController_isProviderCurrent, _RampsController_updateResourceField, _RampsController_setResourceLoading, _RampsController_setResourceError, _RampsController_updateRequestState, _RampsController_syncWidgetUrl, _RampsController_syncTransakAuthOnError;
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.RampsController = exports.getDefaultRampsControllerState = exports.RAMPS_CONTROLLER_REQUIRED_SERVICE_ACTIONS = exports.controllerName = void 0;
16
16
  const base_controller_1 = require("@metamask/base-controller");
@@ -101,6 +101,18 @@ const rampsControllerMetadata = {
101
101
  includeInStateLogs: true,
102
102
  usedInUi: true,
103
103
  },
104
+ quotes: {
105
+ persist: false,
106
+ includeInDebugSnapshot: true,
107
+ includeInStateLogs: false,
108
+ usedInUi: true,
109
+ },
110
+ widgetUrl: {
111
+ persist: false,
112
+ includeInDebugSnapshot: true,
113
+ includeInStateLogs: false,
114
+ usedInUi: true,
115
+ },
104
116
  requests: {
105
117
  persist: false,
106
118
  includeInDebugSnapshot: true,
@@ -146,6 +158,8 @@ function getDefaultRampsControllerState() {
146
158
  providers: createDefaultResourceState([], null),
147
159
  tokens: createDefaultResourceState(null, null),
148
160
  paymentMethods: createDefaultResourceState([], null),
161
+ quotes: createDefaultResourceState(null, null),
162
+ widgetUrl: createDefaultResourceState(null),
149
163
  requests: {},
150
164
  nativeProviders: {
151
165
  transak: {
@@ -162,6 +176,7 @@ const DEPENDENT_RESOURCE_KEYS = [
162
176
  'providers',
163
177
  'tokens',
164
178
  'paymentMethods',
179
+ 'quotes',
165
180
  ];
166
181
  const DEPENDENT_RESOURCE_KEYS_SET = new Set(DEPENDENT_RESOURCE_KEYS);
167
182
  function resetResource(state, resourceType, defaultResource) {
@@ -173,7 +188,20 @@ function resetResource(state, resourceType, defaultResource) {
173
188
  resource.error = def.error;
174
189
  }
175
190
  /**
176
- * Resets region-dependent resources (userRegion, providers, tokens, paymentMethods).
191
+ * Resets the widgetUrl resource to its default state.
192
+ * Mutates state in place; use from within controller update() for atomic updates.
193
+ *
194
+ * @param state - The state object to mutate.
195
+ */
196
+ function resetWidgetUrl(state) {
197
+ const def = getDefaultRampsControllerState().widgetUrl;
198
+ state.widgetUrl.data = def.data;
199
+ state.widgetUrl.selected = def.selected;
200
+ state.widgetUrl.isLoading = def.isLoading;
201
+ state.widgetUrl.error = def.error;
202
+ }
203
+ /**
204
+ * Resets region-dependent resources (userRegion, providers, tokens, paymentMethods, quotes).
177
205
  * Mutates state in place; use from within controller update() for atomic updates.
178
206
  *
179
207
  * @param state - The state object to mutate.
@@ -188,6 +216,7 @@ function resetDependentResources(state, options) {
188
216
  for (const key of DEPENDENT_RESOURCE_KEYS) {
189
217
  resetResource(state, key, defaultState[key]);
190
218
  }
219
+ resetWidgetUrl(state);
191
220
  }
192
221
  // === HELPER FUNCTIONS ===
193
222
  /**
@@ -521,6 +550,8 @@ class RampsController extends base_controller_1.BaseController {
521
550
  this.update((state) => {
522
551
  state.providers.selected = provider;
523
552
  resetResource(state, 'paymentMethods');
553
+ state.quotes.selected = null;
554
+ resetWidgetUrl(state);
524
555
  });
525
556
  __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_fireAndForget).call(this, this.getPaymentMethods(regionCode, { provider: provider.id }));
526
557
  }
@@ -630,6 +661,8 @@ class RampsController extends base_controller_1.BaseController {
630
661
  this.update((state) => {
631
662
  state.tokens.selected = token;
632
663
  resetResource(state, 'paymentMethods');
664
+ state.quotes.selected = null;
665
+ resetWidgetUrl(state);
633
666
  });
634
667
  __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_fireAndForget).call(this, this.getPaymentMethods(regionCode, { assetId: token.assetId }));
635
668
  }
@@ -766,7 +799,7 @@ class RampsController extends base_controller_1.BaseController {
766
799
  }
767
800
  /**
768
801
  * Fetches quotes from all providers for a given set of parameters.
769
- * Uses the controller's request cache; callers manage the response in local state.
802
+ * The quotes are saved in the controller state once fetched.
770
803
  *
771
804
  * @param options - The parameters for fetching quotes.
772
805
  * @param options.region - User's region code. If not provided, uses userRegion from state.
@@ -835,12 +868,91 @@ class RampsController extends base_controller_1.BaseController {
835
868
  redirectUrl: options.redirectUrl,
836
869
  action,
837
870
  };
838
- return this.executeRequest(cacheKey, async () => {
871
+ const response = await this.executeRequest(cacheKey, async () => {
839
872
  return this.messenger.call('RampsService:getQuotes', params);
840
873
  }, {
841
874
  forceRefresh: options.forceRefresh,
842
875
  ttl: options.ttl ?? DEFAULT_QUOTES_TTL,
876
+ resourceType: 'quotes',
877
+ isResultCurrent: () => __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_isRegionCurrent).call(this, normalizedRegion),
878
+ });
879
+ this.update((state) => {
880
+ const userRegionCode = state.userRegion?.regionCode;
881
+ if (userRegionCode === undefined || userRegionCode === normalizedRegion) {
882
+ state.quotes.data = response;
883
+ }
884
+ });
885
+ return response;
886
+ }
887
+ /**
888
+ * Fetches quotes for the currently selected token, provider, and payment method.
889
+ * If the response contains exactly one quote, it is auto-selected.
890
+ * If multiple quotes are returned, the existing selection is preserved if still valid.
891
+ *
892
+ * Returns early (no-op) if the selected payment method is not yet set,
893
+ * allowing callers to invoke this before payment-method selection is finalized.
894
+ *
895
+ * @param options - Parameters for fetching quotes.
896
+ * @param options.walletAddress - The destination wallet address.
897
+ * @param options.amount - The amount (in fiat for buy, crypto for sell).
898
+ * @param options.redirectUrl - Optional redirect URL after order completion.
899
+ * @throws If required dependencies (region, token, provider) are not set.
900
+ */
901
+ fetchQuotesForSelection(options) {
902
+ __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_requireRegion).call(this);
903
+ const token = this.state.tokens.selected;
904
+ const provider = this.state.providers.selected;
905
+ const paymentMethod = this.state.paymentMethods.selected;
906
+ if (!token) {
907
+ throw new Error('Token is required. Cannot fetch quotes without a selected token.');
908
+ }
909
+ if (!provider) {
910
+ throw new Error('Provider is required. Cannot fetch quotes without a selected provider.');
911
+ }
912
+ if (!paymentMethod) {
913
+ return;
914
+ }
915
+ __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_fireAndForget).call(this, this.getQuotes({
916
+ assetId: token.assetId,
917
+ amount: options.amount,
918
+ walletAddress: options.walletAddress,
919
+ redirectUrl: options.redirectUrl,
920
+ paymentMethods: [paymentMethod.id],
921
+ providers: [provider.id],
922
+ forceRefresh: true,
923
+ }).then((response) => {
924
+ let newSelectedQuote = null;
925
+ this.update((state) => {
926
+ if (response.success.length === 1) {
927
+ newSelectedQuote = response.success[0];
928
+ state.quotes.selected = newSelectedQuote;
929
+ }
930
+ else {
931
+ const currentSelection = state.quotes.selected;
932
+ if (currentSelection) {
933
+ const freshQuote = response.success.find((quote) => quote.provider === currentSelection.provider &&
934
+ quote.quote.paymentMethod ===
935
+ currentSelection.quote.paymentMethod);
936
+ newSelectedQuote = freshQuote ?? null;
937
+ state.quotes.selected = newSelectedQuote;
938
+ }
939
+ }
940
+ });
941
+ __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_syncWidgetUrl).call(this, newSelectedQuote);
942
+ return undefined;
943
+ }));
944
+ }
945
+ /**
946
+ * Manually sets the selected quote.
947
+ * Automatically triggers a widget URL fetch for the new quote.
948
+ *
949
+ * @param quote - The quote to select, or null to clear the selection.
950
+ */
951
+ setSelectedQuote(quote) {
952
+ this.update((state) => {
953
+ state.quotes.selected = quote;
843
954
  });
955
+ __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_syncWidgetUrl).call(this, quote);
844
956
  }
845
957
  /**
846
958
  * Cleans up controller resources.
@@ -852,10 +964,12 @@ class RampsController extends base_controller_1.BaseController {
852
964
  /**
853
965
  * Fetches the widget URL from a quote for redirect providers.
854
966
  * Makes a request to the buyURL endpoint via the RampsService to get the
855
- * actual provider widget URL.
967
+ * actual provider widget URL, using the injected fetch and retry policy.
856
968
  *
857
969
  * @param quote - The quote to fetch the widget URL from.
858
970
  * @returns Promise resolving to the widget URL string, or null if not available.
971
+ * @deprecated Read `state.widgetUrl` instead. The widget URL is now automatically
972
+ * fetched and stored in state whenever the selected quote changes.
859
973
  */
860
974
  async getWidgetUrl(quote) {
861
975
  const buyUrl = quote.quote?.buyURL;
@@ -1375,6 +1489,39 @@ _RampsController_requestCacheTTL = new WeakMap(), _RampsController_requestCacheM
1375
1489
  }
1376
1490
  }
1377
1491
  });
1492
+ }, _RampsController_syncWidgetUrl = function _RampsController_syncWidgetUrl(quote) {
1493
+ const buyUrl = quote?.quote?.buyURL;
1494
+ if (!buyUrl) {
1495
+ this.update((state) => {
1496
+ resetWidgetUrl(state);
1497
+ });
1498
+ return;
1499
+ }
1500
+ if (this.state.widgetUrl.data === null) {
1501
+ this.update((state) => {
1502
+ state.widgetUrl.isLoading = true;
1503
+ state.widgetUrl.error = null;
1504
+ });
1505
+ }
1506
+ __classPrivateFieldGet(this, _RampsController_instances, "m", _RampsController_fireAndForget).call(this, this.messenger
1507
+ .call('RampsService:getBuyWidgetUrl', buyUrl)
1508
+ .then((buyWidget) => {
1509
+ this.update((state) => {
1510
+ state.widgetUrl.data = buyWidget;
1511
+ state.widgetUrl.isLoading = false;
1512
+ state.widgetUrl.error = null;
1513
+ });
1514
+ return undefined;
1515
+ })
1516
+ .catch((error) => {
1517
+ this.update((state) => {
1518
+ state.widgetUrl.isLoading = false;
1519
+ state.widgetUrl.error =
1520
+ error instanceof Error
1521
+ ? error.message
1522
+ : 'Failed to fetch widget URL';
1523
+ });
1524
+ }));
1378
1525
  }, _RampsController_syncTransakAuthOnError = function _RampsController_syncTransakAuthOnError(error) {
1379
1526
  if (error instanceof Error &&
1380
1527
  'httpStatus' in error &&