@metamask-previews/bridge-controller 79.2.0-preview-c2ef75d18 → 79.2.0-preview-e81fe8853

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.
@@ -16,7 +16,6 @@ import { Contract } from "@ethersproject/contracts";
16
16
  import { Web3Provider } from "@ethersproject/providers";
17
17
  import { abiERC20 } from "@metamask/metamask-eth-abis";
18
18
  import { StaticIntervalPollingController } from "@metamask/polling-controller";
19
- import { v4 as uuid } from "uuid";
20
19
  import { toQuoteResponseV2 } from "./coercers/quote-response-v1-to-v2.mjs";
21
20
  import { BRIDGE_CONTROLLER_NAME, BRIDGE_PROD_API_BASE_URL, DEFAULT_BRIDGE_CONTROLLER_STATE, METABRIDGE_ETHEREUM_ADDRESS, REFRESH_INTERVAL_MS } from "./constants/bridge.mjs";
22
21
  import { CHAIN_IDS } from "./constants/chains.mjs";
@@ -30,7 +29,7 @@ import { formatAddressToCaipReference, formatChainIdToCaip, formatChainIdToHex }
30
29
  import { getBridgeFeatureFlags, hasMinimumRequiredVersion } from "./utils/feature-flags.mjs";
31
30
  import { fetchAssetPrices, fetchBridgeQuotes, fetchBridgeQuoteStream, fetchBatchSellTrades } from "./utils/fetch.mjs";
32
31
  import { AbortReason, BatchSellMetricsEventName, MetaMetricsSwapsEventSource, MetricsActionType, UnifiedSwapBridgeEventName } from "./utils/metrics/constants.mjs";
33
- import { formatProviderLabel, getAccountHardwareType, getRequestParams, getSwapType, getSwapTypeFromQuote, isCustomSlippage, toInputChangedPropertyKey, toInputChangedPropertyValue } from "./utils/metrics/properties.mjs";
32
+ import { formatProviderLabel, getAccountHardwareType, getRequestParams, getSwapTypeFromQuote, isCustomSlippage, toInputChangedPropertyKey, toInputChangedPropertyValue } from "./utils/metrics/properties.mjs";
34
33
  import { appendFeesToQuotes } from "./utils/quote-fees.mjs";
35
34
  import { getMinimumBalanceForRentExemptionInLamports } from "./utils/snaps.mjs";
36
35
  import { sortQuotes } from "./utils/sort-quotes.mjs";
@@ -127,18 +126,6 @@ const metadata = {
127
126
  usedInUi: true,
128
127
  },
129
128
  };
130
- const QUOTE_ABORT_REASONS = new Set(Object.values(AbortReason));
131
- const isExpectedQuoteAbort = (error, signal) => {
132
- if (signal?.aborted) {
133
- return true;
134
- }
135
- if (QUOTE_ABORT_REASONS.has(String(error))) {
136
- return true;
137
- }
138
- const errorText = error instanceof Error ? `${error.name} ${error.message}` : String(error);
139
- return (errorText.includes('AbortError') ||
140
- errorText.includes('FetchRequestCanceledException'));
141
- };
142
129
  const MESSENGER_EXPOSED_METHODS = [
143
130
  'updateBridgeQuoteRequestParams',
144
131
  'fetchQuotes',
@@ -514,9 +501,6 @@ export class BridgeController extends StaticIntervalPollingController() {
514
501
  __classPrivateFieldGet(this, _BridgeController_abortController, "f")?.abort(AbortReason.NewQuoteRequest);
515
502
  __classPrivateFieldGet(this, _BridgeController_batchSellTradesAbortController, "f")?.abort(AbortReason.NewQuoteRequest);
516
503
  __classPrivateFieldSet(this, _BridgeController_abortController, new AbortController(), "f");
517
- const quoteTraceStartTime = Date.now();
518
- const quoteTraceRequestId = uuid();
519
- const quoteAbortSignal = __classPrivateFieldGet(this, _BridgeController_abortController, "f").signal;
520
504
  __classPrivateFieldGet(this, _BridgeController_fetchAssetExchangeRates, "f").call(this, quoteRequests).catch((error) => console.warn('Failed to fetch asset exchange rates', error));
521
505
  this.trackUnifiedSwapBridgeEvent(UnifiedSwapBridgeEventName.QuotesRequested, context);
522
506
  const { sse, maxRefreshCount } = getBridgeFeatureFlags(this.messenger);
@@ -537,81 +521,43 @@ export class BridgeController extends StaticIntervalPollingController() {
537
521
  }
538
522
  });
539
523
  const jwt = await __classPrivateFieldGet(this, _BridgeController_getJwt, "f").call(this);
540
- const [firstQuoteRequest] = quoteRequests;
541
- let traceResult = 'error';
542
- let traceName = TraceName.BatchSellQuotesFetched;
543
- if (!isBatchSellRequest) {
544
- traceName = isCrossChain(firstQuoteRequest.srcChainId, firstQuoteRequest.destChainId)
524
+ try {
525
+ const [firstQuoteRequest] = quoteRequests;
526
+ const unifiedSwapTraceName = isCrossChain(firstQuoteRequest.srcChainId, firstQuoteRequest.destChainId)
545
527
  ? TraceName.BridgeQuotesFetched
546
528
  : TraceName.SwapQuotesFetched;
547
- }
548
- const tracedProviders = new Set();
549
- const traceWithoutImpact = async (request) => {
550
- try {
551
- await __classPrivateFieldGet(this, _BridgeController_trace, "f").call(this, request, () => undefined);
552
- }
553
- catch {
554
- // Telemetry failures must not affect quote fetching or state updates.
555
- }
556
- };
557
- const traceProviderFirstResult = async (providerData) => {
558
- const provider = formatProviderLabel(providerData);
559
- if (isBatchSellRequest || tracedProviders.has(provider)) {
560
- return;
561
- }
562
- tracedProviders.add(provider);
563
- await traceWithoutImpact({
564
- name: TraceName.QuoteProviderFirstResult,
565
- startTime: quoteTraceStartTime,
529
+ await __classPrivateFieldGet(this, _BridgeController_trace, "f").call(this, {
530
+ name: isBatchSellRequest
531
+ ? TraceName.BatchSellQuotesFetched
532
+ : unifiedSwapTraceName,
566
533
  data: {
567
- provider,
568
- request_id: quoteTraceRequestId,
569
- swap_type: getSwapType(firstQuoteRequest.srcChainId, firstQuoteRequest.destChainId),
570
534
  srcChainId: formatChainIdToCaip(firstQuoteRequest.srcChainId),
571
535
  destChainId: formatChainIdToCaip(firstQuoteRequest.destChainId),
572
- result: 'success',
573
536
  },
574
- });
575
- };
576
- try {
577
- const selectedAccount = __classPrivateFieldGet(this, _BridgeController_instances, "m", _BridgeController_getMultichainSelectedAccount).call(this, firstQuoteRequest.walletAddress);
578
- // This call is not awaited to prevent blocking quote fetching if the snap takes too long to respond
579
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
580
- __classPrivateFieldGet(this, _BridgeController_setMinimumBalanceForRentExemptionInLamports, "f").call(this, firstQuoteRequest.srcChainId, selectedAccount?.metadata?.snap?.id);
581
- // Use SSE if enabled and return early
582
- if (shouldStream || isBatchSellRequest) {
583
- const quoteCount = await __classPrivateFieldGet(this, _BridgeController_handleQuoteStreaming, "f").call(this, {
584
- quoteRequests,
585
- featureId: context.feature_id,
586
- jwt,
587
- selectedAccount,
588
- signal: quoteAbortSignal,
589
- traceProviderFirstResult,
590
- });
591
- if (quoteAbortSignal.aborted) {
592
- traceResult = 'cancelled';
537
+ }, async () => {
538
+ const selectedAccount = __classPrivateFieldGet(this, _BridgeController_instances, "m", _BridgeController_getMultichainSelectedAccount).call(this, firstQuoteRequest.walletAddress);
539
+ // This call is not awaited to prevent blocking quote fetching if the snap takes too long to respond
540
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
541
+ __classPrivateFieldGet(this, _BridgeController_setMinimumBalanceForRentExemptionInLamports, "f").call(this, firstQuoteRequest.srcChainId, selectedAccount?.metadata?.snap?.id);
542
+ // Use SSE if enabled and return early
543
+ if (shouldStream || isBatchSellRequest) {
544
+ await __classPrivateFieldGet(this, _BridgeController_handleQuoteStreaming, "f").call(this, quoteRequests, context.feature_id, jwt, selectedAccount);
593
545
  return;
594
546
  }
595
- traceResult = quoteCount > 0 ? 'success' : 'no_quotes';
596
- }
597
- else {
598
547
  // Otherwise use regular fetch
599
- const quotes = await this.fetchQuotes(firstQuoteRequest, context.feature_id, quoteAbortSignal);
600
- for (const quote of quotes) {
601
- await traceProviderFirstResult(quote.quote);
602
- }
548
+ const quotes = await this.fetchQuotes(firstQuoteRequest, context.feature_id, __classPrivateFieldGet(this, _BridgeController_abortController, "f")?.signal);
603
549
  this.update((state) => {
604
550
  // Set the initial load time if this is the first fetch
605
551
  if (state.quotesRefreshCount ===
606
552
  DEFAULT_BRIDGE_CONTROLLER_STATE.quotesRefreshCount &&
607
553
  __classPrivateFieldGet(this, _BridgeController_quotesFirstFetched, "f")) {
608
- state.quotesInitialLoadTime = Date.now() - __classPrivateFieldGet(this, _BridgeController_quotesFirstFetched, "f");
554
+ state.quotesInitialLoadTime =
555
+ Date.now() - __classPrivateFieldGet(this, _BridgeController_quotesFirstFetched, "f");
609
556
  }
610
557
  state.quotes = quotes.map(toQuoteResponseV2);
611
558
  state.quotesLoadingStatus = RequestStatus.FETCHED;
612
559
  });
613
- traceResult = quotes.length > 0 ? 'success' : 'no_quotes';
614
- }
560
+ });
615
561
  }
616
562
  catch (error) {
617
563
  // Reset the quotes list if the fetch fails to avoid showing stale quotes
@@ -619,8 +565,14 @@ export class BridgeController extends StaticIntervalPollingController() {
619
565
  state.quotes = DEFAULT_BRIDGE_CONTROLLER_STATE.quotes;
620
566
  });
621
567
  // Ignore abort errors
622
- if (isExpectedQuoteAbort(error, quoteAbortSignal)) {
623
- traceResult = 'cancelled';
568
+ if (error.toString().includes('AbortError') ||
569
+ error.toString().includes('FetchRequestCanceledException') ||
570
+ [
571
+ AbortReason.ResetState,
572
+ AbortReason.NewQuoteRequest,
573
+ AbortReason.QuoteRequestUpdated,
574
+ AbortReason.TransactionSubmitted,
575
+ ].includes(error)) {
624
576
  // Exit the function early to prevent other state updates
625
577
  return;
626
578
  }
@@ -645,22 +597,6 @@ export class BridgeController extends StaticIntervalPollingController() {
645
597
  this.trackUnifiedSwapBridgeEvent(UnifiedSwapBridgeEventName.QuotesError, context);
646
598
  console.log(`Failed to ${shouldStream ? 'stream' : 'fetch'} bridge quotes`, error);
647
599
  }
648
- finally {
649
- await traceWithoutImpact({
650
- name: traceName,
651
- startTime: quoteTraceStartTime,
652
- data: {
653
- srcChainId: formatChainIdToCaip(firstQuoteRequest.srcChainId),
654
- destChainId: formatChainIdToCaip(firstQuoteRequest.destChainId),
655
- ...(!isBatchSellRequest && {
656
- request_id: quoteTraceRequestId,
657
- swap_type: getSwapType(firstQuoteRequest.srcChainId, firstQuoteRequest.destChainId),
658
- feature_id: context.feature_id,
659
- result: traceResult,
660
- }),
661
- },
662
- });
663
- }
664
600
  // Update refresh count after fetching, validation and fee calculation have completed
665
601
  this.update((state) => {
666
602
  state.quotesRefreshCount += 1;
@@ -675,7 +611,7 @@ export class BridgeController extends StaticIntervalPollingController() {
675
611
  this.stopAllPolling();
676
612
  }
677
613
  });
678
- _BridgeController_handleQuoteStreaming.set(this, async ({ quoteRequests, featureId, jwt, selectedAccount, signal, traceProviderFirstResult, }) => {
614
+ _BridgeController_handleQuoteStreaming.set(this, async (quoteRequests, featureId, jwt, selectedAccount) => {
679
615
  /**
680
616
  * Tracks the number of valid quotes received from the current stream, which is used
681
617
  * to determine when to clear the quotes list and set the initial load time
@@ -686,10 +622,9 @@ export class BridgeController extends StaticIntervalPollingController() {
686
622
  * before setting quotesLoadingStatus to FETCHED
687
623
  */
688
624
  const pendingFeeAppendPromises = new Set();
689
- await fetchBridgeQuoteStream(__classPrivateFieldGet(this, _BridgeController_fetchFn, "f"), quoteRequests, signal, featureId, __classPrivateFieldGet(this, _BridgeController_clientId, "f"), jwt, __classPrivateFieldGet(this, _BridgeController_config, "f").customBridgeApiBaseUrl ?? BRIDGE_PROD_API_BASE_URL, {
625
+ await fetchBridgeQuoteStream(__classPrivateFieldGet(this, _BridgeController_fetchFn, "f"), quoteRequests, __classPrivateFieldGet(this, _BridgeController_abortController, "f")?.signal, featureId, __classPrivateFieldGet(this, _BridgeController_clientId, "f"), jwt, __classPrivateFieldGet(this, _BridgeController_config, "f").customBridgeApiBaseUrl ?? BRIDGE_PROD_API_BASE_URL, {
690
626
  onQuoteValidationFailure: (validationFailures) => __classPrivateFieldGet(this, _BridgeController_trackQuoteValidationFailures, "f").call(this, validationFailures, featureId),
691
627
  onValidQuoteReceived: async (quote) => {
692
- await traceProviderFirstResult(quote.quote);
693
628
  const feeAppendPromise = (async () => {
694
629
  const quotesWithFees = await appendFeesToQuotes(quote.chainId, [quote], this.messenger, __classPrivateFieldGet(this, _BridgeController_getLayer1GasFee, "f"), selectedAccount);
695
630
  if (quotesWithFees.length > 0) {
@@ -753,7 +688,6 @@ export class BridgeController extends StaticIntervalPollingController() {
753
688
  });
754
689
  },
755
690
  }, __classPrivateFieldGet(this, _BridgeController_clientVersion, "f"));
756
- return validQuotesCounter;
757
691
  });
758
692
  _BridgeController_setMinimumBalanceForRentExemptionInLamports.set(this, async (srcChainId, snapId) => {
759
693
  if (!isSolanaChainId(srcChainId) || !snapId) {