@metamask-previews/bridge-controller 71.1.1-preview-ef7075b96 → 72.0.1-preview-c4c6220ef

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.
Files changed (66) hide show
  1. package/CHANGELOG.md +21 -1
  2. package/dist/bridge-controller.cjs +150 -102
  3. package/dist/bridge-controller.cjs.map +1 -1
  4. package/dist/bridge-controller.d.cts +14 -4
  5. package/dist/bridge-controller.d.cts.map +1 -1
  6. package/dist/bridge-controller.d.mts +14 -4
  7. package/dist/bridge-controller.d.mts.map +1 -1
  8. package/dist/bridge-controller.mjs +151 -103
  9. package/dist/bridge-controller.mjs.map +1 -1
  10. package/dist/constants/bridge.cjs +5 -3
  11. package/dist/constants/bridge.cjs.map +1 -1
  12. package/dist/constants/bridge.d.cts.map +1 -1
  13. package/dist/constants/bridge.d.mts.map +1 -1
  14. package/dist/constants/bridge.mjs +5 -3
  15. package/dist/constants/bridge.mjs.map +1 -1
  16. package/dist/constants/traces.cjs +1 -0
  17. package/dist/constants/traces.cjs.map +1 -1
  18. package/dist/constants/traces.d.cts +1 -0
  19. package/dist/constants/traces.d.cts.map +1 -1
  20. package/dist/constants/traces.d.mts +1 -0
  21. package/dist/constants/traces.d.mts.map +1 -1
  22. package/dist/constants/traces.mjs +1 -0
  23. package/dist/constants/traces.mjs.map +1 -1
  24. package/dist/index.cjs +4 -2
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.d.cts +2 -2
  27. package/dist/index.d.cts.map +1 -1
  28. package/dist/index.d.mts +2 -2
  29. package/dist/index.d.mts.map +1 -1
  30. package/dist/index.mjs +2 -2
  31. package/dist/index.mjs.map +1 -1
  32. package/dist/selectors.cjs +89 -33
  33. package/dist/selectors.cjs.map +1 -1
  34. package/dist/selectors.d.cts +7163 -76
  35. package/dist/selectors.d.cts.map +1 -1
  36. package/dist/selectors.d.mts +7163 -76
  37. package/dist/selectors.d.mts.map +1 -1
  38. package/dist/selectors.mjs +88 -32
  39. package/dist/selectors.mjs.map +1 -1
  40. package/dist/types.cjs.map +1 -1
  41. package/dist/types.d.cts +6 -1
  42. package/dist/types.d.cts.map +1 -1
  43. package/dist/types.d.mts +6 -1
  44. package/dist/types.d.mts.map +1 -1
  45. package/dist/types.mjs.map +1 -1
  46. package/dist/utils/fetch.cjs +61 -17
  47. package/dist/utils/fetch.cjs.map +1 -1
  48. package/dist/utils/fetch.d.cts +3 -4
  49. package/dist/utils/fetch.d.cts.map +1 -1
  50. package/dist/utils/fetch.d.mts +3 -4
  51. package/dist/utils/fetch.d.mts.map +1 -1
  52. package/dist/utils/fetch.mjs +62 -18
  53. package/dist/utils/fetch.mjs.map +1 -1
  54. package/dist/utils/metrics/properties.cjs +1 -1
  55. package/dist/utils/metrics/properties.cjs.map +1 -1
  56. package/dist/utils/metrics/properties.mjs +1 -1
  57. package/dist/utils/metrics/properties.mjs.map +1 -1
  58. package/dist/utils/quote.cjs +3 -1
  59. package/dist/utils/quote.cjs.map +1 -1
  60. package/dist/utils/quote.d.cts +1 -0
  61. package/dist/utils/quote.d.cts.map +1 -1
  62. package/dist/utils/quote.d.mts +1 -0
  63. package/dist/utils/quote.d.mts.map +1 -1
  64. package/dist/utils/quote.mjs +1 -0
  65. package/dist/utils/quote.mjs.map +1 -1
  66. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [72.0.1]
11
+
12
+ ### Changed
13
+
14
+ - Bump `@metamask/assets-controller` from `^6.4.0` to `^7.0.0` ([#8738](https://github.com/MetaMask/core/pull/8738))
15
+
16
+ ## [72.0.0]
17
+
18
+ ### Added
19
+
20
+ - **BREAKING:** Add support for BatchSell quotes ([#8711](https://github.com/MetaMask/core/pull/8711))
21
+ - change `quoteRequest`'s type from `QuoteRequest` to `QuoteRequest[]`
22
+ - allow callers to update specific quote requests within a batch by adding 2 optional parameters to `updateBridgeQuoteRequest`: quoteRequestIndex and quoteRequestCount
23
+ - export `isValidBatchSellQuoteRequest` request validator
24
+ - fetch multiple swap quotes through a single SSE stream and append `quoteRequestIndex` to link each one to its originating quoteRequest
25
+ - implement `selectBatchSellQuotes` selector which returns the recommended quote for each batched quote, and their aggregated fees and received amounts
26
+ - trace BatchSell quote fetch operations in Sentry using label `Batch Sell Quotes Fetched`
27
+
10
28
  ### Changed
11
29
 
12
30
  - Bump `@metamask/gas-fee-controller` from `^26.1.1` to `^26.2.0` ([#8722](https://github.com/MetaMask/core/pull/8722))
@@ -1421,7 +1439,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1421
1439
 
1422
1440
  - Initial release ([#5317](https://github.com/MetaMask/core/pull/5317))
1423
1441
 
1424
- [Unreleased]: https://github.com/MetaMask/core/compare/@metamask/bridge-controller@71.1.1...HEAD
1442
+ [Unreleased]: https://github.com/MetaMask/core/compare/@metamask/bridge-controller@72.0.1...HEAD
1443
+ [72.0.1]: https://github.com/MetaMask/core/compare/@metamask/bridge-controller@72.0.0...@metamask/bridge-controller@72.0.1
1444
+ [72.0.0]: https://github.com/MetaMask/core/compare/@metamask/bridge-controller@71.1.1...@metamask/bridge-controller@72.0.0
1425
1445
  [71.1.1]: https://github.com/MetaMask/core/compare/@metamask/bridge-controller@71.1.0...@metamask/bridge-controller@71.1.1
1426
1446
  [71.1.0]: https://github.com/MetaMask/core/compare/@metamask/bridge-controller@71.0.0...@metamask/bridge-controller@71.1.0
1427
1447
  [71.0.0]: https://github.com/MetaMask/core/compare/@metamask/bridge-controller@70.2.0...@metamask/bridge-controller@71.0.0
@@ -10,7 +10,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
10
10
  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");
11
11
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
12
  };
13
- var _BridgeController_instances, _BridgeController_abortController, _BridgeController_quotesFirstFetched, _BridgeController_location, _BridgeController_clientId, _BridgeController_clientVersion, _BridgeController_getLayer1GasFee, _BridgeController_fetchFn, _BridgeController_trackMetaMetricsFn, _BridgeController_trace, _BridgeController_config, _BridgeController_getUseAssetsControllerForRates, _BridgeController_trackQuoteValidationFailures, _BridgeController_getExchangeRateSources, _BridgeController_fetchAssetExchangeRates, _BridgeController_hasInsufficientBalance, _BridgeController_shouldResetApproval, _BridgeController_fetchBridgeQuotes, _BridgeController_handleQuoteStreaming, _BridgeController_setMinimumBalanceForRentExemptionInLamports, _BridgeController_getMultichainSelectedAccount, _BridgeController_getNetworkClientByChainId, _BridgeController_getJwt, _BridgeController_getRequestMetadata, _BridgeController_getQuoteFetchData, _BridgeController_getEventProperties, _BridgeController_trackInputChangedEvents, _BridgeController_getUSDTMainnetAllowance;
13
+ var _BridgeController_instances, _BridgeController_abortController, _BridgeController_quotesFirstFetched, _BridgeController_location, _BridgeController_clientId, _BridgeController_clientVersion, _BridgeController_getLayer1GasFee, _BridgeController_fetchFn, _BridgeController_trackMetaMetricsFn, _BridgeController_trace, _BridgeController_config, _BridgeController_getUseAssetsControllerForRates, _BridgeController_trackQuoteValidationFailures, _BridgeController_getExchangeRateSources, _BridgeController_fetchAssetExchangeRates, _BridgeController_hasInsufficientBalance, _BridgeController_appendInsufficientBalAndResetApproval, _BridgeController_shouldResetApproval, _BridgeController_fetchBridgeQuotes, _BridgeController_handleQuoteStreaming, _BridgeController_setMinimumBalanceForRentExemptionInLamports, _BridgeController_getMultichainSelectedAccount, _BridgeController_getNetworkClientByChainId, _BridgeController_getJwt, _BridgeController_getRequestMetadata, _BridgeController_getQuoteFetchData, _BridgeController_getEventProperties, _BridgeController_trackInputChangedEvents, _BridgeController_getUSDTMainnetAllowance;
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.BridgeController = void 0;
16
16
  /* eslint-disable @typescript-eslint/explicit-function-return-type */
@@ -156,54 +156,48 @@ class BridgeController extends (0, polling_controller_1.StaticIntervalPollingCon
156
156
  this._executePoll = async (pollingInput) => {
157
157
  await __classPrivateFieldGet(this, _BridgeController_fetchBridgeQuotes, "f").call(this, pollingInput);
158
158
  };
159
- this.updateBridgeQuoteRequestParams = async (paramsToUpdate, context) => {
160
- __classPrivateFieldGet(this, _BridgeController_trackInputChangedEvents, "f").call(this, paramsToUpdate);
161
- this.resetState(constants_1.AbortReason.QuoteRequestUpdated);
162
- const updatedQuoteRequest = {
163
- ...bridge_1.DEFAULT_BRIDGE_CONTROLLER_STATE.quoteRequest,
164
- ...paramsToUpdate,
165
- };
159
+ /**
160
+ * Updates the quote request at the specified index with the given parameters, then starts
161
+ * polling for quotes.
162
+ *
163
+ * @param paramsToUpdate - The parameters to update in the quote request at the specified index
164
+ * @param context - metrics context
165
+ * @param quoteRequestIndex - The index of the quote request to update
166
+ * @param quoteRequestCount - The number of quote requests in the UI
167
+ */
168
+ this.updateBridgeQuoteRequestParams = async (paramsToUpdate, context, quoteRequestIndex = 0, quoteRequestCount = 1) => {
169
+ // Guard against updating a quote request that doesn't exist
170
+ if (quoteRequestIndex >= quoteRequestCount) {
171
+ return;
172
+ }
173
+ __classPrivateFieldGet(this, _BridgeController_trackInputChangedEvents, "f").call(this, paramsToUpdate, quoteRequestIndex);
174
+ this.resetState(constants_1.AbortReason.QuoteRequestUpdated, quoteRequestIndex);
166
175
  this.update((state) => {
167
- state.quoteRequest = updatedQuoteRequest;
176
+ // Update only the specified quote request and keep the rest of the quote requests unchanged
177
+ state.quoteRequest = state.quoteRequest
178
+ .slice(0, quoteRequestIndex)
179
+ .concat({
180
+ ...bridge_1.DEFAULT_BRIDGE_CONTROLLER_STATE.quoteRequest[0],
181
+ ...paramsToUpdate,
182
+ })
183
+ .concat(state.quoteRequest.slice(quoteRequestIndex + 1, quoteRequestCount));
168
184
  state.tokenSecurityTypeDestination =
169
185
  context.token_security_type_destination ?? null;
170
186
  });
171
- if ((0, quote_1.isValidQuoteRequest)(updatedQuoteRequest)) {
187
+ // BatchSell and Unified swaps both use the same polling logic so both validations should pass
188
+ if ((0, quote_1.isValidQuoteRequest)(paramsToUpdate) &&
189
+ (0, quote_1.isValidBatchSellQuoteRequest)(this.state.quoteRequest)) {
172
190
  __classPrivateFieldSet(this, _BridgeController_quotesFirstFetched, Date.now(), "f");
173
- const isSrcChainNonEVM = (0, bridge_2.isNonEvmChainId)(updatedQuoteRequest.srcChainId);
174
- const providerConfig = isSrcChainNonEVM
175
- ? undefined
176
- : __classPrivateFieldGet(this, _BridgeController_instances, "m", _BridgeController_getNetworkClientByChainId).call(this, (0, caip_formatters_1.formatChainIdToHex)(updatedQuoteRequest.srcChainId))?.configuration;
177
- let insufficientBal;
178
- let resetApproval = Boolean(paramsToUpdate.resetApproval);
179
- if (isSrcChainNonEVM) {
180
- // If the source chain is not an EVM network, use value from params
181
- insufficientBal = paramsToUpdate.insufficientBal;
182
- }
183
- else if (providerConfig?.rpcUrl?.includes('tenderly')) {
184
- // If the rpcUrl is a tenderly fork (e2e tests), set insufficientBal=true
185
- // The bridge-api filters out quotes if the balance on mainnet is insufficient so this override allows quotes to always be returned
186
- insufficientBal = true;
187
- }
188
- else {
189
- // Set loading status if RPC calls are made before the quotes are fetched
190
- this.update((state) => {
191
- state.quotesLoadingStatus = types_1.RequestStatus.LOADING;
192
- });
193
- resetApproval = await __classPrivateFieldGet(this, _BridgeController_shouldResetApproval, "f").call(this, updatedQuoteRequest);
194
- // Otherwise query the src token balance from the RPC provider
195
- insufficientBal =
196
- paramsToUpdate.insufficientBal ??
197
- (await __classPrivateFieldGet(this, _BridgeController_hasInsufficientBalance, "f").call(this, updatedQuoteRequest));
198
- }
191
+ // Update the insufficientBal and resetApproval params for the quote request
192
+ const quoteWithInsufficientBalAndResetApproval = await __classPrivateFieldGet(this, _BridgeController_appendInsufficientBalAndResetApproval, "f").call(this, paramsToUpdate);
193
+ this.update((state) => {
194
+ state.quoteRequest[quoteRequestIndex] =
195
+ quoteWithInsufficientBalAndResetApproval;
196
+ });
199
197
  // Set refresh rate based on the source chain before starting polling
200
198
  this.setChainIntervalLength();
201
199
  this.startPolling({
202
- updatedQuoteRequest: {
203
- ...updatedQuoteRequest,
204
- insufficientBal,
205
- resetApproval,
206
- },
200
+ quoteRequests: this.state.quoteRequest,
207
201
  context,
208
202
  });
209
203
  }
@@ -261,25 +255,21 @@ class BridgeController extends (0, polling_controller_1.StaticIntervalPollingCon
261
255
  * Fetches the exchange rates for the assets in the quote request if they are not already in the state
262
256
  * In addition to the selected tokens, this also fetches the native asset for the source and destination chains
263
257
  *
264
- * @param quoteRequest - The quote request
265
- * @param quoteRequest.srcChainId - The source chain ID
266
- * @param quoteRequest.srcTokenAddress - The source token address
267
- * @param quoteRequest.destChainId - The destination chain ID
268
- * @param quoteRequest.destTokenAddress - The destination token address
258
+ * @param quoteRequests - The quote requests to fetch the exchange rates for
269
259
  */
270
- _BridgeController_fetchAssetExchangeRates.set(this, async ({ srcChainId, srcTokenAddress, destChainId, destTokenAddress, }) => {
271
- const assetIds = new Set([]);
260
+ _BridgeController_fetchAssetExchangeRates.set(this, async (quoteRequests) => {
272
261
  const exchangeRateSources = __classPrivateFieldGet(this, _BridgeController_getExchangeRateSources, "f").call(this);
273
- if (srcTokenAddress &&
274
- srcChainId &&
275
- !(0, selectors_1.selectIsAssetExchangeRateInState)(exchangeRateSources, srcChainId, srcTokenAddress)) {
276
- (0, assets_1.getAssetIdsForToken)(srcTokenAddress, srcChainId).forEach((assetId) => assetIds.add(assetId));
277
- }
278
- if (destTokenAddress &&
279
- destChainId &&
280
- !(0, selectors_1.selectIsAssetExchangeRateInState)(exchangeRateSources, destChainId, destTokenAddress)) {
281
- (0, assets_1.getAssetIdsForToken)(destTokenAddress, destChainId).forEach((assetId) => assetIds.add(assetId));
282
- }
262
+ // Get unique assetIds for all quote requests
263
+ const assetIds = new Set(quoteRequests
264
+ .flatMap((quoteRequest) => [
265
+ quoteRequest.srcTokenAddress && quoteRequest.srcChainId
266
+ ? (0, assets_1.getAssetIdsForToken)(quoteRequest.srcTokenAddress, quoteRequest.srcChainId)
267
+ : undefined,
268
+ quoteRequest.destTokenAddress && quoteRequest.destChainId
269
+ ? (0, assets_1.getAssetIdsForToken)(quoteRequest.destTokenAddress, quoteRequest.destChainId)
270
+ : undefined,
271
+ ].flat())
272
+ .filter((assetId) => !(0, selectors_1.selectIsAssetExchangeRateInState)(exchangeRateSources, assetId)));
283
273
  const currency = __classPrivateFieldGet(this, _BridgeController_getUseAssetsControllerForRates, "f").call(this)
284
274
  ? this.messenger.call('AssetsController:getExchangeRatesForBridge')
285
275
  .currentCurrency
@@ -320,6 +310,39 @@ class BridgeController extends (0, polling_controller_1.StaticIntervalPollingCon
320
310
  return true;
321
311
  }
322
312
  });
313
+ _BridgeController_appendInsufficientBalAndResetApproval.set(this, async (quoteRequest) => {
314
+ const isSrcChainNonEVM = (0, bridge_2.isNonEvmChainId)(quoteRequest.srcChainId);
315
+ const providerConfig = isSrcChainNonEVM
316
+ ? undefined
317
+ : __classPrivateFieldGet(this, _BridgeController_instances, "m", _BridgeController_getNetworkClientByChainId).call(this, (0, caip_formatters_1.formatChainIdToHex)(quoteRequest.srcChainId))?.configuration;
318
+ let insufficientBal;
319
+ let resetApproval = Boolean(quoteRequest.resetApproval);
320
+ if (isSrcChainNonEVM) {
321
+ // If the source chain is not an EVM network, use value from params
322
+ insufficientBal = quoteRequest.insufficientBal;
323
+ }
324
+ else if (providerConfig?.rpcUrl?.includes('tenderly')) {
325
+ // If the rpcUrl is a tenderly fork (e2e tests), set insufficientBal=true
326
+ // The bridge-api filters out quotes if the balance on mainnet is insufficient so this override allows quotes to always be returned
327
+ insufficientBal = true;
328
+ }
329
+ else {
330
+ // Set loading status if RPC calls are made before the quotes are fetched
331
+ this.update((state) => {
332
+ state.quotesLoadingStatus = types_1.RequestStatus.LOADING;
333
+ });
334
+ resetApproval = await __classPrivateFieldGet(this, _BridgeController_shouldResetApproval, "f").call(this, quoteRequest);
335
+ // Otherwise query the src token balance from the RPC provider
336
+ insufficientBal =
337
+ quoteRequest.insufficientBal ??
338
+ (await __classPrivateFieldGet(this, _BridgeController_hasInsufficientBalance, "f").call(this, quoteRequest));
339
+ }
340
+ return {
341
+ ...quoteRequest,
342
+ insufficientBal,
343
+ resetApproval,
344
+ };
345
+ });
323
346
  _BridgeController_shouldResetApproval.set(this, async (quoteRequest) => {
324
347
  if ((0, bridge_2.isNonEvmChainId)(quoteRequest.srcChainId)) {
325
348
  return false;
@@ -357,11 +380,21 @@ class BridgeController extends (0, polling_controller_1.StaticIntervalPollingCon
357
380
  this.setLocation = (location) => {
358
381
  __classPrivateFieldSet(this, _BridgeController_location, location, "f");
359
382
  };
360
- this.resetState = (reason = constants_1.AbortReason.ResetState) => {
383
+ this.resetState = (reason = constants_1.AbortReason.ResetState, quoteRequestIndex = null) => {
361
384
  this.stopPollingForQuotes(reason);
362
385
  this.update((state) => {
363
386
  // Cannot do direct assignment to state, i.e. state = {... }, need to manually assign each field
364
- state.quoteRequest = bridge_1.DEFAULT_BRIDGE_CONTROLLER_STATE.quoteRequest;
387
+ if (quoteRequestIndex === null) {
388
+ // Clear all requests if index is null
389
+ state.quoteRequest = bridge_1.DEFAULT_BRIDGE_CONTROLLER_STATE.quoteRequest;
390
+ }
391
+ else {
392
+ // Otherwise only clear the specified request
393
+ state.quoteRequest = state.quoteRequest
394
+ .slice(0, quoteRequestIndex)
395
+ .concat(bridge_1.DEFAULT_BRIDGE_CONTROLLER_STATE.quoteRequest[0])
396
+ .concat(state.quoteRequest.slice(quoteRequestIndex + 1));
397
+ }
365
398
  state.quotesInitialLoadTime =
366
399
  bridge_1.DEFAULT_BRIDGE_CONTROLLER_STATE.quotesInitialLoadTime;
367
400
  state.quotes = bridge_1.DEFAULT_BRIDGE_CONTROLLER_STATE.quotes;
@@ -388,7 +421,9 @@ class BridgeController extends (0, polling_controller_1.StaticIntervalPollingCon
388
421
  */
389
422
  this.setChainIntervalLength = () => {
390
423
  const { state } = this;
391
- const { srcChainId } = state.quoteRequest;
424
+ // Assume that BatchSell quote requests all have the same source chain
425
+ // Use the first one to determine refresh rate
426
+ const { srcChainId } = state.quoteRequest[0];
392
427
  const bridgeFeatureFlags = (0, feature_flags_1.getBridgeFeatureFlags)(this.messenger);
393
428
  const refreshRateOverride = srcChainId
394
429
  ? bridgeFeatureFlags.chains[(0, caip_formatters_1.formatChainIdToCaip)(srcChainId)]?.refreshRate
@@ -396,16 +431,16 @@ class BridgeController extends (0, polling_controller_1.StaticIntervalPollingCon
396
431
  const defaultRefreshRate = bridgeFeatureFlags.refreshRate;
397
432
  this.setIntervalLength(refreshRateOverride ?? defaultRefreshRate);
398
433
  };
399
- _BridgeController_fetchBridgeQuotes.set(this, async ({ updatedQuoteRequest, context, }) => {
434
+ _BridgeController_fetchBridgeQuotes.set(this, async ({ quoteRequests, context, }) => {
400
435
  __classPrivateFieldGet(this, _BridgeController_abortController, "f")?.abort(constants_1.AbortReason.NewQuoteRequest);
401
436
  __classPrivateFieldSet(this, _BridgeController_abortController, new AbortController(), "f");
402
- __classPrivateFieldGet(this, _BridgeController_fetchAssetExchangeRates, "f").call(this, updatedQuoteRequest).catch((error) => console.warn('Failed to fetch asset exchange rates', error));
437
+ __classPrivateFieldGet(this, _BridgeController_fetchAssetExchangeRates, "f").call(this, quoteRequests).catch((error) => console.warn('Failed to fetch asset exchange rates', error));
403
438
  this.trackUnifiedSwapBridgeEvent(constants_1.UnifiedSwapBridgeEventName.QuotesRequested, context);
404
439
  const { sse, maxRefreshCount } = (0, feature_flags_1.getBridgeFeatureFlags)(this.messenger);
405
440
  const shouldStream = sse?.enabled &&
406
441
  (0, feature_flags_1.hasMinimumRequiredVersion)(__classPrivateFieldGet(this, _BridgeController_clientVersion, "f"), sse.minimumVersion);
442
+ const isBatchSellRequest = quoteRequests.length > 1;
407
443
  this.update((state) => {
408
- state.quoteRequest = updatedQuoteRequest;
409
444
  state.quoteFetchError = bridge_1.DEFAULT_BRIDGE_CONTROLLER_STATE.quoteFetchError;
410
445
  state.tokenWarnings = bridge_1.DEFAULT_BRIDGE_CONTROLLER_STATE.tokenWarnings;
411
446
  state.quoteStreamComplete =
@@ -415,26 +450,30 @@ class BridgeController extends (0, polling_controller_1.StaticIntervalPollingCon
415
450
  });
416
451
  const jwt = await __classPrivateFieldGet(this, _BridgeController_getJwt, "f").call(this);
417
452
  try {
453
+ const [firstQuoteRequest] = quoteRequests;
454
+ const unifiedSwapTraceName = (0, bridge_2.isCrossChain)(firstQuoteRequest.srcChainId, firstQuoteRequest.destChainId)
455
+ ? traces_1.TraceName.BridgeQuotesFetched
456
+ : traces_1.TraceName.SwapQuotesFetched;
418
457
  await __classPrivateFieldGet(this, _BridgeController_trace, "f").call(this, {
419
- name: (0, bridge_2.isCrossChain)(updatedQuoteRequest.srcChainId, updatedQuoteRequest.destChainId)
420
- ? traces_1.TraceName.BridgeQuotesFetched
421
- : traces_1.TraceName.SwapQuotesFetched,
458
+ name: isBatchSellRequest
459
+ ? traces_1.TraceName.BatchSellQuotesFetched
460
+ : unifiedSwapTraceName,
422
461
  data: {
423
- srcChainId: (0, caip_formatters_1.formatChainIdToCaip)(updatedQuoteRequest.srcChainId),
424
- destChainId: (0, caip_formatters_1.formatChainIdToCaip)(updatedQuoteRequest.destChainId),
462
+ srcChainId: (0, caip_formatters_1.formatChainIdToCaip)(firstQuoteRequest.srcChainId),
463
+ destChainId: (0, caip_formatters_1.formatChainIdToCaip)(firstQuoteRequest.destChainId),
425
464
  },
426
465
  }, async () => {
427
- const selectedAccount = __classPrivateFieldGet(this, _BridgeController_instances, "m", _BridgeController_getMultichainSelectedAccount).call(this, updatedQuoteRequest.walletAddress);
466
+ const selectedAccount = __classPrivateFieldGet(this, _BridgeController_instances, "m", _BridgeController_getMultichainSelectedAccount).call(this, firstQuoteRequest.walletAddress);
428
467
  // This call is not awaited to prevent blocking quote fetching if the snap takes too long to respond
429
468
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
430
- __classPrivateFieldGet(this, _BridgeController_setMinimumBalanceForRentExemptionInLamports, "f").call(this, updatedQuoteRequest.srcChainId, selectedAccount?.metadata?.snap?.id);
469
+ __classPrivateFieldGet(this, _BridgeController_setMinimumBalanceForRentExemptionInLamports, "f").call(this, firstQuoteRequest.srcChainId, selectedAccount?.metadata?.snap?.id);
431
470
  // Use SSE if enabled and return early
432
- if (shouldStream) {
433
- await __classPrivateFieldGet(this, _BridgeController_handleQuoteStreaming, "f").call(this, updatedQuoteRequest, jwt, selectedAccount);
471
+ if (shouldStream || isBatchSellRequest) {
472
+ await __classPrivateFieldGet(this, _BridgeController_handleQuoteStreaming, "f").call(this, quoteRequests, jwt, selectedAccount);
434
473
  return;
435
474
  }
436
475
  // Otherwise use regular fetch
437
- const quotes = await this.fetchQuotes(updatedQuoteRequest, __classPrivateFieldGet(this, _BridgeController_abortController, "f")?.signal);
476
+ const quotes = await this.fetchQuotes(firstQuoteRequest, __classPrivateFieldGet(this, _BridgeController_abortController, "f")?.signal);
438
477
  this.update((state) => {
439
478
  // Set the initial load time if this is the first fetch
440
479
  if (state.quotesRefreshCount ===
@@ -490,14 +529,17 @@ class BridgeController extends (0, polling_controller_1.StaticIntervalPollingCon
490
529
  this.update((state) => {
491
530
  state.quotesRefreshCount += 1;
492
531
  });
493
- // Stop polling if the maximum number of refreshes has been reached
494
- if (updatedQuoteRequest.insufficientBal ||
495
- (!updatedQuoteRequest.insufficientBal &&
496
- this.state.quotesRefreshCount >= maxRefreshCount)) {
532
+ const hasNoFundedQuoteRequests = quoteRequests.every(({ insufficientBal }) => Boolean(insufficientBal));
533
+ if (hasNoFundedQuoteRequests
534
+ ? // If all quote requests are insufficiently funded, stop polling
535
+ // So if a BatchSell has at least 1 sufficiently funded quote request, polling continues
536
+ true
537
+ : // Otherwise continue polling until the maximum number of refreshes has been reached
538
+ this.state.quotesRefreshCount >= maxRefreshCount) {
497
539
  this.stopAllPolling();
498
540
  }
499
541
  });
500
- _BridgeController_handleQuoteStreaming.set(this, async (updatedQuoteRequest, jwt, selectedAccount) => {
542
+ _BridgeController_handleQuoteStreaming.set(this, async (quoteRequests, jwt, selectedAccount) => {
501
543
  /**
502
544
  * Tracks the number of valid quotes received from the current stream, which is used
503
545
  * to determine when to clear the quotes list and set the initial load time
@@ -508,7 +550,7 @@ class BridgeController extends (0, polling_controller_1.StaticIntervalPollingCon
508
550
  * before setting quotesLoadingStatus to FETCHED
509
551
  */
510
552
  const pendingFeeAppendPromises = new Set();
511
- await (0, fetch_1.fetchBridgeQuoteStream)(__classPrivateFieldGet(this, _BridgeController_fetchFn, "f"), updatedQuoteRequest, __classPrivateFieldGet(this, _BridgeController_abortController, "f")?.signal, __classPrivateFieldGet(this, _BridgeController_clientId, "f"), jwt, __classPrivateFieldGet(this, _BridgeController_config, "f").customBridgeApiBaseUrl ?? bridge_1.BRIDGE_PROD_API_BASE_URL, {
553
+ await (0, fetch_1.fetchBridgeQuoteStream)(__classPrivateFieldGet(this, _BridgeController_fetchFn, "f"), quoteRequests, __classPrivateFieldGet(this, _BridgeController_abortController, "f")?.signal, __classPrivateFieldGet(this, _BridgeController_clientId, "f"), jwt, __classPrivateFieldGet(this, _BridgeController_config, "f").customBridgeApiBaseUrl ?? bridge_1.BRIDGE_PROD_API_BASE_URL, {
512
554
  onQuoteValidationFailure: __classPrivateFieldGet(this, _BridgeController_trackQuoteValidationFailures, "f"),
513
555
  onValidQuoteReceived: async (quote) => {
514
556
  const feeAppendPromise = (async () => {
@@ -595,15 +637,16 @@ class BridgeController extends (0, polling_controller_1.StaticIntervalPollingCon
595
637
  return undefined;
596
638
  }
597
639
  });
598
- _BridgeController_getRequestMetadata.set(this, () => {
599
- const { walletAddress } = this.state.quoteRequest;
640
+ _BridgeController_getRequestMetadata.set(this, (quoteRequestIndex = 0) => {
641
+ const quoteRequest = this.state.quoteRequest[quoteRequestIndex];
642
+ const { walletAddress } = quoteRequest;
600
643
  const accountHardwareType = (0, properties_1.getAccountHardwareType)(walletAddress
601
644
  ? __classPrivateFieldGet(this, _BridgeController_instances, "m", _BridgeController_getMultichainSelectedAccount).call(this, walletAddress)
602
645
  : undefined);
603
646
  return {
604
- slippage_limit: this.state.quoteRequest.slippage,
605
- swap_type: (0, properties_1.getSwapTypeFromQuote)(this.state.quoteRequest),
606
- custom_slippage: (0, properties_1.isCustomSlippage)(this.state.quoteRequest.slippage),
647
+ slippage_limit: quoteRequest.slippage,
648
+ swap_type: (0, properties_1.getSwapTypeFromQuote)(quoteRequest),
649
+ custom_slippage: (0, properties_1.isCustomSlippage)(quoteRequest.slippage),
607
650
  account_hardware_type: accountHardwareType,
608
651
  is_hardware_wallet: accountHardwareType !== null,
609
652
  };
@@ -616,34 +659,35 @@ class BridgeController extends (0, polling_controller_1.StaticIntervalPollingCon
616
659
  has_gas_included_quote: this.state.quotes.some(({ quote }) => quote.gasIncluded),
617
660
  };
618
661
  });
619
- _BridgeController_getEventProperties.set(this, (eventName, propertiesFromClient) => {
662
+ _BridgeController_getEventProperties.set(this, (eventName, propertiesFromClient, quoteRequestIndex = 0) => {
620
663
  const clientProps = propertiesFromClient;
621
664
  const baseProperties = {
622
665
  ...propertiesFromClient,
623
666
  location: clientProps?.location ?? __classPrivateFieldGet(this, _BridgeController_location, "f"),
624
667
  action_type: constants_1.MetricsActionType.SWAPBRIDGE_V1,
625
668
  };
669
+ const quoteRequest = this.state.quoteRequest[quoteRequestIndex];
626
670
  switch (eventName) {
627
671
  case constants_1.UnifiedSwapBridgeEventName.ButtonClicked:
628
672
  return {
629
- ...(0, properties_1.getRequestParams)(this.state.quoteRequest, this.state.tokenSecurityTypeDestination),
673
+ ...(0, properties_1.getRequestParams)(quoteRequest, this.state.tokenSecurityTypeDestination),
630
674
  ...baseProperties,
631
675
  };
632
676
  case constants_1.UnifiedSwapBridgeEventName.PageViewed:
633
677
  return {
634
- ...(0, properties_1.getRequestParams)(this.state.quoteRequest, this.state.tokenSecurityTypeDestination),
678
+ ...(0, properties_1.getRequestParams)(quoteRequest, this.state.tokenSecurityTypeDestination),
635
679
  ...__classPrivateFieldGet(this, _BridgeController_getRequestMetadata, "f").call(this),
636
680
  ...baseProperties,
637
681
  };
638
682
  case constants_1.UnifiedSwapBridgeEventName.QuotesValidationFailed:
639
683
  return {
640
- ...(0, properties_1.getRequestParams)(this.state.quoteRequest, this.state.tokenSecurityTypeDestination),
684
+ ...(0, properties_1.getRequestParams)(quoteRequest, this.state.tokenSecurityTypeDestination),
641
685
  refresh_count: this.state.quotesRefreshCount,
642
686
  ...baseProperties,
643
687
  };
644
688
  case constants_1.UnifiedSwapBridgeEventName.QuotesReceived:
645
689
  return {
646
- ...(0, properties_1.getRequestParams)(this.state.quoteRequest, this.state.tokenSecurityTypeDestination),
690
+ ...(0, properties_1.getRequestParams)(quoteRequest, this.state.tokenSecurityTypeDestination),
647
691
  ...__classPrivateFieldGet(this, _BridgeController_getRequestMetadata, "f").call(this),
648
692
  ...__classPrivateFieldGet(this, _BridgeController_getQuoteFetchData, "f").call(this),
649
693
  refresh_count: this.state.quotesRefreshCount,
@@ -651,24 +695,24 @@ class BridgeController extends (0, polling_controller_1.StaticIntervalPollingCon
651
695
  };
652
696
  case constants_1.UnifiedSwapBridgeEventName.QuotesRequested:
653
697
  return {
654
- ...(0, properties_1.getRequestParams)(this.state.quoteRequest, this.state.tokenSecurityTypeDestination),
698
+ ...(0, properties_1.getRequestParams)(quoteRequest, this.state.tokenSecurityTypeDestination),
655
699
  ...__classPrivateFieldGet(this, _BridgeController_getRequestMetadata, "f").call(this),
656
- has_sufficient_funds: !this.state.quoteRequest.insufficientBal,
700
+ has_sufficient_funds: !quoteRequest.insufficientBal,
657
701
  ...baseProperties,
658
702
  };
659
703
  case constants_1.UnifiedSwapBridgeEventName.QuotesError:
660
704
  return {
661
- ...(0, properties_1.getRequestParams)(this.state.quoteRequest, this.state.tokenSecurityTypeDestination),
705
+ ...(0, properties_1.getRequestParams)(quoteRequest, this.state.tokenSecurityTypeDestination),
662
706
  ...__classPrivateFieldGet(this, _BridgeController_getRequestMetadata, "f").call(this),
663
707
  error_message: this.state.quoteFetchError,
664
- has_sufficient_funds: !this.state.quoteRequest.insufficientBal,
708
+ has_sufficient_funds: !quoteRequest.insufficientBal,
665
709
  ...baseProperties,
666
710
  };
667
711
  case constants_1.UnifiedSwapBridgeEventName.AllQuotesOpened:
668
712
  case constants_1.UnifiedSwapBridgeEventName.AllQuotesSorted:
669
713
  case constants_1.UnifiedSwapBridgeEventName.QuoteSelected:
670
714
  return {
671
- ...(0, properties_1.getRequestParams)(this.state.quoteRequest, this.state.tokenSecurityTypeDestination),
715
+ ...(0, properties_1.getRequestParams)(quoteRequest, this.state.tokenSecurityTypeDestination),
672
716
  ...__classPrivateFieldGet(this, _BridgeController_getRequestMetadata, "f").call(this),
673
717
  ...__classPrivateFieldGet(this, _BridgeController_getQuoteFetchData, "f").call(this),
674
718
  ...baseProperties,
@@ -677,7 +721,7 @@ class BridgeController extends (0, polling_controller_1.StaticIntervalPollingCon
677
721
  // Populate the properties that the error occurred before the tx was submitted
678
722
  return {
679
723
  ...baseProperties,
680
- ...(0, properties_1.getRequestParams)(this.state.quoteRequest, this.state.tokenSecurityTypeDestination),
724
+ ...(0, properties_1.getRequestParams)(quoteRequest, this.state.tokenSecurityTypeDestination),
681
725
  ...__classPrivateFieldGet(this, _BridgeController_getRequestMetadata, "f").call(this),
682
726
  ...__classPrivateFieldGet(this, _BridgeController_getQuoteFetchData, "f").call(this),
683
727
  ...propertiesFromClient,
@@ -704,13 +748,15 @@ class BridgeController extends (0, polling_controller_1.StaticIntervalPollingCon
704
748
  return baseProperties;
705
749
  }
706
750
  });
707
- _BridgeController_trackInputChangedEvents.set(this, (paramsToUpdate) => {
751
+ _BridgeController_trackInputChangedEvents.set(this, (paramsToUpdate, quoteRequestIndex = 0) => {
708
752
  Object.entries(paramsToUpdate).forEach(([key, value]) => {
709
753
  const inputKey = properties_1.toInputChangedPropertyKey[key];
710
754
  const inputValue = properties_1.toInputChangedPropertyValue[key]?.(paramsToUpdate);
711
755
  if (inputKey &&
712
756
  inputValue !== undefined &&
713
- value !== this.state.quoteRequest[key]) {
757
+ this.state.quoteRequest[quoteRequestIndex] &&
758
+ value !==
759
+ this.state.quoteRequest[quoteRequestIndex][key]) {
714
760
  this.trackUnifiedSwapBridgeEvent(constants_1.UnifiedSwapBridgeEventName.InputChanged, {
715
761
  input: inputKey,
716
762
  input_value: inputValue,
@@ -724,14 +770,15 @@ class BridgeController extends (0, polling_controller_1.StaticIntervalPollingCon
724
770
  *
725
771
  * @param eventName - The name of the event to track
726
772
  * @param propertiesFromClient - Properties that can't be calculated from the event name and need to be provided by the client
773
+ * @param quoteRequestIndex - The index of the quote request to track the event for
727
774
  * @example
728
775
  * this.trackUnifiedSwapBridgeEvent(UnifiedSwapBridgeEventName.ActionOpened, {
729
776
  * location: MetaMetricsSwapsEventSource.MainView,
730
777
  * });
731
778
  */
732
- this.trackUnifiedSwapBridgeEvent = (eventName, propertiesFromClient) => {
779
+ this.trackUnifiedSwapBridgeEvent = (eventName, propertiesFromClient, quoteRequestIndex = 0) => {
733
780
  try {
734
- const combinedPropertiesForEvent = __classPrivateFieldGet(this, _BridgeController_getEventProperties, "f").call(this, eventName, propertiesFromClient);
781
+ const combinedPropertiesForEvent = __classPrivateFieldGet(this, _BridgeController_getEventProperties, "f").call(this, eventName, propertiesFromClient, quoteRequestIndex);
735
782
  __classPrivateFieldGet(this, _BridgeController_trackMetaMetricsFn, "f").call(this, eventName, combinedPropertiesForEvent);
736
783
  }
737
784
  catch (error) {
@@ -773,8 +820,9 @@ class BridgeController extends (0, polling_controller_1.StaticIntervalPollingCon
773
820
  }
774
821
  }
775
822
  exports.BridgeController = BridgeController;
776
- _BridgeController_abortController = new WeakMap(), _BridgeController_quotesFirstFetched = new WeakMap(), _BridgeController_location = new WeakMap(), _BridgeController_clientId = new WeakMap(), _BridgeController_clientVersion = new WeakMap(), _BridgeController_getLayer1GasFee = new WeakMap(), _BridgeController_fetchFn = new WeakMap(), _BridgeController_trackMetaMetricsFn = new WeakMap(), _BridgeController_trace = new WeakMap(), _BridgeController_config = new WeakMap(), _BridgeController_getUseAssetsControllerForRates = new WeakMap(), _BridgeController_trackQuoteValidationFailures = new WeakMap(), _BridgeController_getExchangeRateSources = new WeakMap(), _BridgeController_fetchAssetExchangeRates = new WeakMap(), _BridgeController_hasInsufficientBalance = new WeakMap(), _BridgeController_shouldResetApproval = new WeakMap(), _BridgeController_fetchBridgeQuotes = new WeakMap(), _BridgeController_handleQuoteStreaming = new WeakMap(), _BridgeController_setMinimumBalanceForRentExemptionInLamports = new WeakMap(), _BridgeController_getJwt = new WeakMap(), _BridgeController_getRequestMetadata = new WeakMap(), _BridgeController_getQuoteFetchData = new WeakMap(), _BridgeController_getEventProperties = new WeakMap(), _BridgeController_trackInputChangedEvents = new WeakMap(), _BridgeController_getUSDTMainnetAllowance = new WeakMap(), _BridgeController_instances = new WeakSet(), _BridgeController_getMultichainSelectedAccount = function _BridgeController_getMultichainSelectedAccount(walletAddress) {
777
- const addressToUse = walletAddress ?? this.state.quoteRequest.walletAddress;
823
+ _BridgeController_abortController = new WeakMap(), _BridgeController_quotesFirstFetched = new WeakMap(), _BridgeController_location = new WeakMap(), _BridgeController_clientId = new WeakMap(), _BridgeController_clientVersion = new WeakMap(), _BridgeController_getLayer1GasFee = new WeakMap(), _BridgeController_fetchFn = new WeakMap(), _BridgeController_trackMetaMetricsFn = new WeakMap(), _BridgeController_trace = new WeakMap(), _BridgeController_config = new WeakMap(), _BridgeController_getUseAssetsControllerForRates = new WeakMap(), _BridgeController_trackQuoteValidationFailures = new WeakMap(), _BridgeController_getExchangeRateSources = new WeakMap(), _BridgeController_fetchAssetExchangeRates = new WeakMap(), _BridgeController_hasInsufficientBalance = new WeakMap(), _BridgeController_appendInsufficientBalAndResetApproval = new WeakMap(), _BridgeController_shouldResetApproval = new WeakMap(), _BridgeController_fetchBridgeQuotes = new WeakMap(), _BridgeController_handleQuoteStreaming = new WeakMap(), _BridgeController_setMinimumBalanceForRentExemptionInLamports = new WeakMap(), _BridgeController_getJwt = new WeakMap(), _BridgeController_getRequestMetadata = new WeakMap(), _BridgeController_getQuoteFetchData = new WeakMap(), _BridgeController_getEventProperties = new WeakMap(), _BridgeController_trackInputChangedEvents = new WeakMap(), _BridgeController_getUSDTMainnetAllowance = new WeakMap(), _BridgeController_instances = new WeakSet(), _BridgeController_getMultichainSelectedAccount = function _BridgeController_getMultichainSelectedAccount(walletAddress) {
824
+ // Assume that all quotes in a batch are for the same account
825
+ const addressToUse = walletAddress ?? this.state.quoteRequest[0].walletAddress;
778
826
  if (!addressToUse) {
779
827
  throw new Error('Account address is required');
780
828
  }