@metamask/bridge-controller 71.1.1 → 72.0.1

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 +26 -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 +4 -4
@@ -9,7 +9,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
9
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
10
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
11
  };
12
- 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;
12
+ 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;
13
13
  /* eslint-disable @typescript-eslint/explicit-function-return-type */
14
14
  import { BigNumber } from "@ethersproject/bignumber";
15
15
  import { Contract } from "@ethersproject/contracts";
@@ -30,7 +30,7 @@ import { getBridgeFeatureFlags, hasMinimumRequiredVersion } from "./utils/featur
30
30
  import { fetchAssetPrices, fetchBridgeQuotes, fetchBridgeQuoteStream } from "./utils/fetch.mjs";
31
31
  import { AbortReason, MetaMetricsSwapsEventSource, MetricsActionType, UnifiedSwapBridgeEventName } from "./utils/metrics/constants.mjs";
32
32
  import { formatProviderLabel, getAccountHardwareType, getRequestParams, getSwapTypeFromQuote, isCustomSlippage, toInputChangedPropertyKey, toInputChangedPropertyValue } from "./utils/metrics/properties.mjs";
33
- import { isValidQuoteRequest, sortQuotes } from "./utils/quote.mjs";
33
+ import { isValidQuoteRequest, isValidBatchSellQuoteRequest, sortQuotes } from "./utils/quote.mjs";
34
34
  import { appendFeesToQuotes } from "./utils/quote-fees.mjs";
35
35
  import { getMinimumBalanceForRentExemptionInLamports } from "./utils/snaps.mjs";
36
36
  const metadata = {
@@ -153,54 +153,48 @@ export class BridgeController extends StaticIntervalPollingController() {
153
153
  this._executePoll = async (pollingInput) => {
154
154
  await __classPrivateFieldGet(this, _BridgeController_fetchBridgeQuotes, "f").call(this, pollingInput);
155
155
  };
156
- this.updateBridgeQuoteRequestParams = async (paramsToUpdate, context) => {
157
- __classPrivateFieldGet(this, _BridgeController_trackInputChangedEvents, "f").call(this, paramsToUpdate);
158
- this.resetState(AbortReason.QuoteRequestUpdated);
159
- const updatedQuoteRequest = {
160
- ...DEFAULT_BRIDGE_CONTROLLER_STATE.quoteRequest,
161
- ...paramsToUpdate,
162
- };
156
+ /**
157
+ * Updates the quote request at the specified index with the given parameters, then starts
158
+ * polling for quotes.
159
+ *
160
+ * @param paramsToUpdate - The parameters to update in the quote request at the specified index
161
+ * @param context - metrics context
162
+ * @param quoteRequestIndex - The index of the quote request to update
163
+ * @param quoteRequestCount - The number of quote requests in the UI
164
+ */
165
+ this.updateBridgeQuoteRequestParams = async (paramsToUpdate, context, quoteRequestIndex = 0, quoteRequestCount = 1) => {
166
+ // Guard against updating a quote request that doesn't exist
167
+ if (quoteRequestIndex >= quoteRequestCount) {
168
+ return;
169
+ }
170
+ __classPrivateFieldGet(this, _BridgeController_trackInputChangedEvents, "f").call(this, paramsToUpdate, quoteRequestIndex);
171
+ this.resetState(AbortReason.QuoteRequestUpdated, quoteRequestIndex);
163
172
  this.update((state) => {
164
- state.quoteRequest = updatedQuoteRequest;
173
+ // Update only the specified quote request and keep the rest of the quote requests unchanged
174
+ state.quoteRequest = state.quoteRequest
175
+ .slice(0, quoteRequestIndex)
176
+ .concat({
177
+ ...DEFAULT_BRIDGE_CONTROLLER_STATE.quoteRequest[0],
178
+ ...paramsToUpdate,
179
+ })
180
+ .concat(state.quoteRequest.slice(quoteRequestIndex + 1, quoteRequestCount));
165
181
  state.tokenSecurityTypeDestination =
166
182
  context.token_security_type_destination ?? null;
167
183
  });
168
- if (isValidQuoteRequest(updatedQuoteRequest)) {
184
+ // BatchSell and Unified swaps both use the same polling logic so both validations should pass
185
+ if (isValidQuoteRequest(paramsToUpdate) &&
186
+ isValidBatchSellQuoteRequest(this.state.quoteRequest)) {
169
187
  __classPrivateFieldSet(this, _BridgeController_quotesFirstFetched, Date.now(), "f");
170
- const isSrcChainNonEVM = isNonEvmChainId(updatedQuoteRequest.srcChainId);
171
- const providerConfig = isSrcChainNonEVM
172
- ? undefined
173
- : __classPrivateFieldGet(this, _BridgeController_instances, "m", _BridgeController_getNetworkClientByChainId).call(this, formatChainIdToHex(updatedQuoteRequest.srcChainId))?.configuration;
174
- let insufficientBal;
175
- let resetApproval = Boolean(paramsToUpdate.resetApproval);
176
- if (isSrcChainNonEVM) {
177
- // If the source chain is not an EVM network, use value from params
178
- insufficientBal = paramsToUpdate.insufficientBal;
179
- }
180
- else if (providerConfig?.rpcUrl?.includes('tenderly')) {
181
- // If the rpcUrl is a tenderly fork (e2e tests), set insufficientBal=true
182
- // The bridge-api filters out quotes if the balance on mainnet is insufficient so this override allows quotes to always be returned
183
- insufficientBal = true;
184
- }
185
- else {
186
- // Set loading status if RPC calls are made before the quotes are fetched
187
- this.update((state) => {
188
- state.quotesLoadingStatus = RequestStatus.LOADING;
189
- });
190
- resetApproval = await __classPrivateFieldGet(this, _BridgeController_shouldResetApproval, "f").call(this, updatedQuoteRequest);
191
- // Otherwise query the src token balance from the RPC provider
192
- insufficientBal =
193
- paramsToUpdate.insufficientBal ??
194
- (await __classPrivateFieldGet(this, _BridgeController_hasInsufficientBalance, "f").call(this, updatedQuoteRequest));
195
- }
188
+ // Update the insufficientBal and resetApproval params for the quote request
189
+ const quoteWithInsufficientBalAndResetApproval = await __classPrivateFieldGet(this, _BridgeController_appendInsufficientBalAndResetApproval, "f").call(this, paramsToUpdate);
190
+ this.update((state) => {
191
+ state.quoteRequest[quoteRequestIndex] =
192
+ quoteWithInsufficientBalAndResetApproval;
193
+ });
196
194
  // Set refresh rate based on the source chain before starting polling
197
195
  this.setChainIntervalLength();
198
196
  this.startPolling({
199
- updatedQuoteRequest: {
200
- ...updatedQuoteRequest,
201
- insufficientBal,
202
- resetApproval,
203
- },
197
+ quoteRequests: this.state.quoteRequest,
204
198
  context,
205
199
  });
206
200
  }
@@ -258,25 +252,21 @@ export class BridgeController extends StaticIntervalPollingController() {
258
252
  * Fetches the exchange rates for the assets in the quote request if they are not already in the state
259
253
  * In addition to the selected tokens, this also fetches the native asset for the source and destination chains
260
254
  *
261
- * @param quoteRequest - The quote request
262
- * @param quoteRequest.srcChainId - The source chain ID
263
- * @param quoteRequest.srcTokenAddress - The source token address
264
- * @param quoteRequest.destChainId - The destination chain ID
265
- * @param quoteRequest.destTokenAddress - The destination token address
255
+ * @param quoteRequests - The quote requests to fetch the exchange rates for
266
256
  */
267
- _BridgeController_fetchAssetExchangeRates.set(this, async ({ srcChainId, srcTokenAddress, destChainId, destTokenAddress, }) => {
268
- const assetIds = new Set([]);
257
+ _BridgeController_fetchAssetExchangeRates.set(this, async (quoteRequests) => {
269
258
  const exchangeRateSources = __classPrivateFieldGet(this, _BridgeController_getExchangeRateSources, "f").call(this);
270
- if (srcTokenAddress &&
271
- srcChainId &&
272
- !selectIsAssetExchangeRateInState(exchangeRateSources, srcChainId, srcTokenAddress)) {
273
- getAssetIdsForToken(srcTokenAddress, srcChainId).forEach((assetId) => assetIds.add(assetId));
274
- }
275
- if (destTokenAddress &&
276
- destChainId &&
277
- !selectIsAssetExchangeRateInState(exchangeRateSources, destChainId, destTokenAddress)) {
278
- getAssetIdsForToken(destTokenAddress, destChainId).forEach((assetId) => assetIds.add(assetId));
279
- }
259
+ // Get unique assetIds for all quote requests
260
+ const assetIds = new Set(quoteRequests
261
+ .flatMap((quoteRequest) => [
262
+ quoteRequest.srcTokenAddress && quoteRequest.srcChainId
263
+ ? getAssetIdsForToken(quoteRequest.srcTokenAddress, quoteRequest.srcChainId)
264
+ : undefined,
265
+ quoteRequest.destTokenAddress && quoteRequest.destChainId
266
+ ? getAssetIdsForToken(quoteRequest.destTokenAddress, quoteRequest.destChainId)
267
+ : undefined,
268
+ ].flat())
269
+ .filter((assetId) => !selectIsAssetExchangeRateInState(exchangeRateSources, assetId)));
280
270
  const currency = __classPrivateFieldGet(this, _BridgeController_getUseAssetsControllerForRates, "f").call(this)
281
271
  ? this.messenger.call('AssetsController:getExchangeRatesForBridge')
282
272
  .currentCurrency
@@ -317,6 +307,39 @@ export class BridgeController extends StaticIntervalPollingController() {
317
307
  return true;
318
308
  }
319
309
  });
310
+ _BridgeController_appendInsufficientBalAndResetApproval.set(this, async (quoteRequest) => {
311
+ const isSrcChainNonEVM = isNonEvmChainId(quoteRequest.srcChainId);
312
+ const providerConfig = isSrcChainNonEVM
313
+ ? undefined
314
+ : __classPrivateFieldGet(this, _BridgeController_instances, "m", _BridgeController_getNetworkClientByChainId).call(this, formatChainIdToHex(quoteRequest.srcChainId))?.configuration;
315
+ let insufficientBal;
316
+ let resetApproval = Boolean(quoteRequest.resetApproval);
317
+ if (isSrcChainNonEVM) {
318
+ // If the source chain is not an EVM network, use value from params
319
+ insufficientBal = quoteRequest.insufficientBal;
320
+ }
321
+ else if (providerConfig?.rpcUrl?.includes('tenderly')) {
322
+ // If the rpcUrl is a tenderly fork (e2e tests), set insufficientBal=true
323
+ // The bridge-api filters out quotes if the balance on mainnet is insufficient so this override allows quotes to always be returned
324
+ insufficientBal = true;
325
+ }
326
+ else {
327
+ // Set loading status if RPC calls are made before the quotes are fetched
328
+ this.update((state) => {
329
+ state.quotesLoadingStatus = RequestStatus.LOADING;
330
+ });
331
+ resetApproval = await __classPrivateFieldGet(this, _BridgeController_shouldResetApproval, "f").call(this, quoteRequest);
332
+ // Otherwise query the src token balance from the RPC provider
333
+ insufficientBal =
334
+ quoteRequest.insufficientBal ??
335
+ (await __classPrivateFieldGet(this, _BridgeController_hasInsufficientBalance, "f").call(this, quoteRequest));
336
+ }
337
+ return {
338
+ ...quoteRequest,
339
+ insufficientBal,
340
+ resetApproval,
341
+ };
342
+ });
320
343
  _BridgeController_shouldResetApproval.set(this, async (quoteRequest) => {
321
344
  if (isNonEvmChainId(quoteRequest.srcChainId)) {
322
345
  return false;
@@ -354,11 +377,21 @@ export class BridgeController extends StaticIntervalPollingController() {
354
377
  this.setLocation = (location) => {
355
378
  __classPrivateFieldSet(this, _BridgeController_location, location, "f");
356
379
  };
357
- this.resetState = (reason = AbortReason.ResetState) => {
380
+ this.resetState = (reason = AbortReason.ResetState, quoteRequestIndex = null) => {
358
381
  this.stopPollingForQuotes(reason);
359
382
  this.update((state) => {
360
383
  // Cannot do direct assignment to state, i.e. state = {... }, need to manually assign each field
361
- state.quoteRequest = DEFAULT_BRIDGE_CONTROLLER_STATE.quoteRequest;
384
+ if (quoteRequestIndex === null) {
385
+ // Clear all requests if index is null
386
+ state.quoteRequest = DEFAULT_BRIDGE_CONTROLLER_STATE.quoteRequest;
387
+ }
388
+ else {
389
+ // Otherwise only clear the specified request
390
+ state.quoteRequest = state.quoteRequest
391
+ .slice(0, quoteRequestIndex)
392
+ .concat(DEFAULT_BRIDGE_CONTROLLER_STATE.quoteRequest[0])
393
+ .concat(state.quoteRequest.slice(quoteRequestIndex + 1));
394
+ }
362
395
  state.quotesInitialLoadTime =
363
396
  DEFAULT_BRIDGE_CONTROLLER_STATE.quotesInitialLoadTime;
364
397
  state.quotes = DEFAULT_BRIDGE_CONTROLLER_STATE.quotes;
@@ -385,7 +418,9 @@ export class BridgeController extends StaticIntervalPollingController() {
385
418
  */
386
419
  this.setChainIntervalLength = () => {
387
420
  const { state } = this;
388
- const { srcChainId } = state.quoteRequest;
421
+ // Assume that BatchSell quote requests all have the same source chain
422
+ // Use the first one to determine refresh rate
423
+ const { srcChainId } = state.quoteRequest[0];
389
424
  const bridgeFeatureFlags = getBridgeFeatureFlags(this.messenger);
390
425
  const refreshRateOverride = srcChainId
391
426
  ? bridgeFeatureFlags.chains[formatChainIdToCaip(srcChainId)]?.refreshRate
@@ -393,16 +428,16 @@ export class BridgeController extends StaticIntervalPollingController() {
393
428
  const defaultRefreshRate = bridgeFeatureFlags.refreshRate;
394
429
  this.setIntervalLength(refreshRateOverride ?? defaultRefreshRate);
395
430
  };
396
- _BridgeController_fetchBridgeQuotes.set(this, async ({ updatedQuoteRequest, context, }) => {
431
+ _BridgeController_fetchBridgeQuotes.set(this, async ({ quoteRequests, context, }) => {
397
432
  __classPrivateFieldGet(this, _BridgeController_abortController, "f")?.abort(AbortReason.NewQuoteRequest);
398
433
  __classPrivateFieldSet(this, _BridgeController_abortController, new AbortController(), "f");
399
- __classPrivateFieldGet(this, _BridgeController_fetchAssetExchangeRates, "f").call(this, updatedQuoteRequest).catch((error) => console.warn('Failed to fetch asset exchange rates', error));
434
+ __classPrivateFieldGet(this, _BridgeController_fetchAssetExchangeRates, "f").call(this, quoteRequests).catch((error) => console.warn('Failed to fetch asset exchange rates', error));
400
435
  this.trackUnifiedSwapBridgeEvent(UnifiedSwapBridgeEventName.QuotesRequested, context);
401
436
  const { sse, maxRefreshCount } = getBridgeFeatureFlags(this.messenger);
402
437
  const shouldStream = sse?.enabled &&
403
438
  hasMinimumRequiredVersion(__classPrivateFieldGet(this, _BridgeController_clientVersion, "f"), sse.minimumVersion);
439
+ const isBatchSellRequest = quoteRequests.length > 1;
404
440
  this.update((state) => {
405
- state.quoteRequest = updatedQuoteRequest;
406
441
  state.quoteFetchError = DEFAULT_BRIDGE_CONTROLLER_STATE.quoteFetchError;
407
442
  state.tokenWarnings = DEFAULT_BRIDGE_CONTROLLER_STATE.tokenWarnings;
408
443
  state.quoteStreamComplete =
@@ -412,26 +447,30 @@ export class BridgeController extends StaticIntervalPollingController() {
412
447
  });
413
448
  const jwt = await __classPrivateFieldGet(this, _BridgeController_getJwt, "f").call(this);
414
449
  try {
450
+ const [firstQuoteRequest] = quoteRequests;
451
+ const unifiedSwapTraceName = isCrossChain(firstQuoteRequest.srcChainId, firstQuoteRequest.destChainId)
452
+ ? TraceName.BridgeQuotesFetched
453
+ : TraceName.SwapQuotesFetched;
415
454
  await __classPrivateFieldGet(this, _BridgeController_trace, "f").call(this, {
416
- name: isCrossChain(updatedQuoteRequest.srcChainId, updatedQuoteRequest.destChainId)
417
- ? TraceName.BridgeQuotesFetched
418
- : TraceName.SwapQuotesFetched,
455
+ name: isBatchSellRequest
456
+ ? TraceName.BatchSellQuotesFetched
457
+ : unifiedSwapTraceName,
419
458
  data: {
420
- srcChainId: formatChainIdToCaip(updatedQuoteRequest.srcChainId),
421
- destChainId: formatChainIdToCaip(updatedQuoteRequest.destChainId),
459
+ srcChainId: formatChainIdToCaip(firstQuoteRequest.srcChainId),
460
+ destChainId: formatChainIdToCaip(firstQuoteRequest.destChainId),
422
461
  },
423
462
  }, async () => {
424
- const selectedAccount = __classPrivateFieldGet(this, _BridgeController_instances, "m", _BridgeController_getMultichainSelectedAccount).call(this, updatedQuoteRequest.walletAddress);
463
+ const selectedAccount = __classPrivateFieldGet(this, _BridgeController_instances, "m", _BridgeController_getMultichainSelectedAccount).call(this, firstQuoteRequest.walletAddress);
425
464
  // This call is not awaited to prevent blocking quote fetching if the snap takes too long to respond
426
465
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
427
- __classPrivateFieldGet(this, _BridgeController_setMinimumBalanceForRentExemptionInLamports, "f").call(this, updatedQuoteRequest.srcChainId, selectedAccount?.metadata?.snap?.id);
466
+ __classPrivateFieldGet(this, _BridgeController_setMinimumBalanceForRentExemptionInLamports, "f").call(this, firstQuoteRequest.srcChainId, selectedAccount?.metadata?.snap?.id);
428
467
  // Use SSE if enabled and return early
429
- if (shouldStream) {
430
- await __classPrivateFieldGet(this, _BridgeController_handleQuoteStreaming, "f").call(this, updatedQuoteRequest, jwt, selectedAccount);
468
+ if (shouldStream || isBatchSellRequest) {
469
+ await __classPrivateFieldGet(this, _BridgeController_handleQuoteStreaming, "f").call(this, quoteRequests, jwt, selectedAccount);
431
470
  return;
432
471
  }
433
472
  // Otherwise use regular fetch
434
- const quotes = await this.fetchQuotes(updatedQuoteRequest, __classPrivateFieldGet(this, _BridgeController_abortController, "f")?.signal);
473
+ const quotes = await this.fetchQuotes(firstQuoteRequest, __classPrivateFieldGet(this, _BridgeController_abortController, "f")?.signal);
435
474
  this.update((state) => {
436
475
  // Set the initial load time if this is the first fetch
437
476
  if (state.quotesRefreshCount ===
@@ -487,14 +526,17 @@ export class BridgeController extends StaticIntervalPollingController() {
487
526
  this.update((state) => {
488
527
  state.quotesRefreshCount += 1;
489
528
  });
490
- // Stop polling if the maximum number of refreshes has been reached
491
- if (updatedQuoteRequest.insufficientBal ||
492
- (!updatedQuoteRequest.insufficientBal &&
493
- this.state.quotesRefreshCount >= maxRefreshCount)) {
529
+ const hasNoFundedQuoteRequests = quoteRequests.every(({ insufficientBal }) => Boolean(insufficientBal));
530
+ if (hasNoFundedQuoteRequests
531
+ ? // If all quote requests are insufficiently funded, stop polling
532
+ // So if a BatchSell has at least 1 sufficiently funded quote request, polling continues
533
+ true
534
+ : // Otherwise continue polling until the maximum number of refreshes has been reached
535
+ this.state.quotesRefreshCount >= maxRefreshCount) {
494
536
  this.stopAllPolling();
495
537
  }
496
538
  });
497
- _BridgeController_handleQuoteStreaming.set(this, async (updatedQuoteRequest, jwt, selectedAccount) => {
539
+ _BridgeController_handleQuoteStreaming.set(this, async (quoteRequests, jwt, selectedAccount) => {
498
540
  /**
499
541
  * Tracks the number of valid quotes received from the current stream, which is used
500
542
  * to determine when to clear the quotes list and set the initial load time
@@ -505,7 +547,7 @@ export class BridgeController extends StaticIntervalPollingController() {
505
547
  * before setting quotesLoadingStatus to FETCHED
506
548
  */
507
549
  const pendingFeeAppendPromises = new Set();
508
- await 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_PROD_API_BASE_URL, {
550
+ await 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_PROD_API_BASE_URL, {
509
551
  onQuoteValidationFailure: __classPrivateFieldGet(this, _BridgeController_trackQuoteValidationFailures, "f"),
510
552
  onValidQuoteReceived: async (quote) => {
511
553
  const feeAppendPromise = (async () => {
@@ -592,15 +634,16 @@ export class BridgeController extends StaticIntervalPollingController() {
592
634
  return undefined;
593
635
  }
594
636
  });
595
- _BridgeController_getRequestMetadata.set(this, () => {
596
- const { walletAddress } = this.state.quoteRequest;
637
+ _BridgeController_getRequestMetadata.set(this, (quoteRequestIndex = 0) => {
638
+ const quoteRequest = this.state.quoteRequest[quoteRequestIndex];
639
+ const { walletAddress } = quoteRequest;
597
640
  const accountHardwareType = getAccountHardwareType(walletAddress
598
641
  ? __classPrivateFieldGet(this, _BridgeController_instances, "m", _BridgeController_getMultichainSelectedAccount).call(this, walletAddress)
599
642
  : undefined);
600
643
  return {
601
- slippage_limit: this.state.quoteRequest.slippage,
602
- swap_type: getSwapTypeFromQuote(this.state.quoteRequest),
603
- custom_slippage: isCustomSlippage(this.state.quoteRequest.slippage),
644
+ slippage_limit: quoteRequest.slippage,
645
+ swap_type: getSwapTypeFromQuote(quoteRequest),
646
+ custom_slippage: isCustomSlippage(quoteRequest.slippage),
604
647
  account_hardware_type: accountHardwareType,
605
648
  is_hardware_wallet: accountHardwareType !== null,
606
649
  };
@@ -613,34 +656,35 @@ export class BridgeController extends StaticIntervalPollingController() {
613
656
  has_gas_included_quote: this.state.quotes.some(({ quote }) => quote.gasIncluded),
614
657
  };
615
658
  });
616
- _BridgeController_getEventProperties.set(this, (eventName, propertiesFromClient) => {
659
+ _BridgeController_getEventProperties.set(this, (eventName, propertiesFromClient, quoteRequestIndex = 0) => {
617
660
  const clientProps = propertiesFromClient;
618
661
  const baseProperties = {
619
662
  ...propertiesFromClient,
620
663
  location: clientProps?.location ?? __classPrivateFieldGet(this, _BridgeController_location, "f"),
621
664
  action_type: MetricsActionType.SWAPBRIDGE_V1,
622
665
  };
666
+ const quoteRequest = this.state.quoteRequest[quoteRequestIndex];
623
667
  switch (eventName) {
624
668
  case UnifiedSwapBridgeEventName.ButtonClicked:
625
669
  return {
626
- ...getRequestParams(this.state.quoteRequest, this.state.tokenSecurityTypeDestination),
670
+ ...getRequestParams(quoteRequest, this.state.tokenSecurityTypeDestination),
627
671
  ...baseProperties,
628
672
  };
629
673
  case UnifiedSwapBridgeEventName.PageViewed:
630
674
  return {
631
- ...getRequestParams(this.state.quoteRequest, this.state.tokenSecurityTypeDestination),
675
+ ...getRequestParams(quoteRequest, this.state.tokenSecurityTypeDestination),
632
676
  ...__classPrivateFieldGet(this, _BridgeController_getRequestMetadata, "f").call(this),
633
677
  ...baseProperties,
634
678
  };
635
679
  case UnifiedSwapBridgeEventName.QuotesValidationFailed:
636
680
  return {
637
- ...getRequestParams(this.state.quoteRequest, this.state.tokenSecurityTypeDestination),
681
+ ...getRequestParams(quoteRequest, this.state.tokenSecurityTypeDestination),
638
682
  refresh_count: this.state.quotesRefreshCount,
639
683
  ...baseProperties,
640
684
  };
641
685
  case UnifiedSwapBridgeEventName.QuotesReceived:
642
686
  return {
643
- ...getRequestParams(this.state.quoteRequest, this.state.tokenSecurityTypeDestination),
687
+ ...getRequestParams(quoteRequest, this.state.tokenSecurityTypeDestination),
644
688
  ...__classPrivateFieldGet(this, _BridgeController_getRequestMetadata, "f").call(this),
645
689
  ...__classPrivateFieldGet(this, _BridgeController_getQuoteFetchData, "f").call(this),
646
690
  refresh_count: this.state.quotesRefreshCount,
@@ -648,24 +692,24 @@ export class BridgeController extends StaticIntervalPollingController() {
648
692
  };
649
693
  case UnifiedSwapBridgeEventName.QuotesRequested:
650
694
  return {
651
- ...getRequestParams(this.state.quoteRequest, this.state.tokenSecurityTypeDestination),
695
+ ...getRequestParams(quoteRequest, this.state.tokenSecurityTypeDestination),
652
696
  ...__classPrivateFieldGet(this, _BridgeController_getRequestMetadata, "f").call(this),
653
- has_sufficient_funds: !this.state.quoteRequest.insufficientBal,
697
+ has_sufficient_funds: !quoteRequest.insufficientBal,
654
698
  ...baseProperties,
655
699
  };
656
700
  case UnifiedSwapBridgeEventName.QuotesError:
657
701
  return {
658
- ...getRequestParams(this.state.quoteRequest, this.state.tokenSecurityTypeDestination),
702
+ ...getRequestParams(quoteRequest, this.state.tokenSecurityTypeDestination),
659
703
  ...__classPrivateFieldGet(this, _BridgeController_getRequestMetadata, "f").call(this),
660
704
  error_message: this.state.quoteFetchError,
661
- has_sufficient_funds: !this.state.quoteRequest.insufficientBal,
705
+ has_sufficient_funds: !quoteRequest.insufficientBal,
662
706
  ...baseProperties,
663
707
  };
664
708
  case UnifiedSwapBridgeEventName.AllQuotesOpened:
665
709
  case UnifiedSwapBridgeEventName.AllQuotesSorted:
666
710
  case UnifiedSwapBridgeEventName.QuoteSelected:
667
711
  return {
668
- ...getRequestParams(this.state.quoteRequest, this.state.tokenSecurityTypeDestination),
712
+ ...getRequestParams(quoteRequest, this.state.tokenSecurityTypeDestination),
669
713
  ...__classPrivateFieldGet(this, _BridgeController_getRequestMetadata, "f").call(this),
670
714
  ...__classPrivateFieldGet(this, _BridgeController_getQuoteFetchData, "f").call(this),
671
715
  ...baseProperties,
@@ -674,7 +718,7 @@ export class BridgeController extends StaticIntervalPollingController() {
674
718
  // Populate the properties that the error occurred before the tx was submitted
675
719
  return {
676
720
  ...baseProperties,
677
- ...getRequestParams(this.state.quoteRequest, this.state.tokenSecurityTypeDestination),
721
+ ...getRequestParams(quoteRequest, this.state.tokenSecurityTypeDestination),
678
722
  ...__classPrivateFieldGet(this, _BridgeController_getRequestMetadata, "f").call(this),
679
723
  ...__classPrivateFieldGet(this, _BridgeController_getQuoteFetchData, "f").call(this),
680
724
  ...propertiesFromClient,
@@ -701,13 +745,15 @@ export class BridgeController extends StaticIntervalPollingController() {
701
745
  return baseProperties;
702
746
  }
703
747
  });
704
- _BridgeController_trackInputChangedEvents.set(this, (paramsToUpdate) => {
748
+ _BridgeController_trackInputChangedEvents.set(this, (paramsToUpdate, quoteRequestIndex = 0) => {
705
749
  Object.entries(paramsToUpdate).forEach(([key, value]) => {
706
750
  const inputKey = toInputChangedPropertyKey[key];
707
751
  const inputValue = toInputChangedPropertyValue[key]?.(paramsToUpdate);
708
752
  if (inputKey &&
709
753
  inputValue !== undefined &&
710
- value !== this.state.quoteRequest[key]) {
754
+ this.state.quoteRequest[quoteRequestIndex] &&
755
+ value !==
756
+ this.state.quoteRequest[quoteRequestIndex][key]) {
711
757
  this.trackUnifiedSwapBridgeEvent(UnifiedSwapBridgeEventName.InputChanged, {
712
758
  input: inputKey,
713
759
  input_value: inputValue,
@@ -721,14 +767,15 @@ export class BridgeController extends StaticIntervalPollingController() {
721
767
  *
722
768
  * @param eventName - The name of the event to track
723
769
  * @param propertiesFromClient - Properties that can't be calculated from the event name and need to be provided by the client
770
+ * @param quoteRequestIndex - The index of the quote request to track the event for
724
771
  * @example
725
772
  * this.trackUnifiedSwapBridgeEvent(UnifiedSwapBridgeEventName.ActionOpened, {
726
773
  * location: MetaMetricsSwapsEventSource.MainView,
727
774
  * });
728
775
  */
729
- this.trackUnifiedSwapBridgeEvent = (eventName, propertiesFromClient) => {
776
+ this.trackUnifiedSwapBridgeEvent = (eventName, propertiesFromClient, quoteRequestIndex = 0) => {
730
777
  try {
731
- const combinedPropertiesForEvent = __classPrivateFieldGet(this, _BridgeController_getEventProperties, "f").call(this, eventName, propertiesFromClient);
778
+ const combinedPropertiesForEvent = __classPrivateFieldGet(this, _BridgeController_getEventProperties, "f").call(this, eventName, propertiesFromClient, quoteRequestIndex);
732
779
  __classPrivateFieldGet(this, _BridgeController_trackMetaMetricsFn, "f").call(this, eventName, combinedPropertiesForEvent);
733
780
  }
734
781
  catch (error) {
@@ -769,8 +816,9 @@ export class BridgeController extends StaticIntervalPollingController() {
769
816
  this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
770
817
  }
771
818
  }
772
- _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) {
773
- const addressToUse = walletAddress ?? this.state.quoteRequest.walletAddress;
819
+ _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) {
820
+ // Assume that all quotes in a batch are for the same account
821
+ const addressToUse = walletAddress ?? this.state.quoteRequest[0].walletAddress;
774
822
  if (!addressToUse) {
775
823
  throw new Error('Account address is required');
776
824
  }