@metamask-previews/bridge-controller 79.2.0-preview-e81fe8853 → 79.2.0-preview-f7d67b054
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 +4 -0
- package/dist/bridge-controller.cjs +97 -29
- package/dist/bridge-controller.cjs.map +1 -1
- package/dist/bridge-controller.d.cts.map +1 -1
- package/dist/bridge-controller.d.mts.map +1 -1
- package/dist/bridge-controller.mjs +98 -30
- package/dist/bridge-controller.mjs.map +1 -1
- package/dist/constants/traces.cjs +1 -0
- package/dist/constants/traces.cjs.map +1 -1
- package/dist/constants/traces.d.cts +1 -0
- package/dist/constants/traces.d.cts.map +1 -1
- package/dist/constants/traces.d.mts +1 -0
- package/dist/constants/traces.d.mts.map +1 -1
- package/dist/constants/traces.mjs +1 -0
- package/dist/constants/traces.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Add Sentry quote-fetch and provider first-result performance traces ([#9899](https://github.com/MetaMask/core/pull/9899))
|
|
13
|
+
|
|
10
14
|
### Changed
|
|
11
15
|
|
|
12
16
|
- Bump `@metamask/assets-controller` from `^13.1.2` to `^13.1.4` ([#9873](https://github.com/MetaMask/core/pull/9873), [#9886](https://github.com/MetaMask/core/pull/9886))
|
|
@@ -19,6 +19,7 @@ const contracts_1 = require("@ethersproject/contracts");
|
|
|
19
19
|
const providers_1 = require("@ethersproject/providers");
|
|
20
20
|
const metamask_eth_abis_1 = require("@metamask/metamask-eth-abis");
|
|
21
21
|
const polling_controller_1 = require("@metamask/polling-controller");
|
|
22
|
+
const uuid_1 = require("uuid");
|
|
22
23
|
const quote_response_v1_to_v2_js_1 = require("./coercers/quote-response-v1-to-v2.cjs");
|
|
23
24
|
const bridge_js_1 = require("./constants/bridge.cjs");
|
|
24
25
|
const chains_js_1 = require("./constants/chains.cjs");
|
|
@@ -129,6 +130,18 @@ const metadata = {
|
|
|
129
130
|
usedInUi: true,
|
|
130
131
|
},
|
|
131
132
|
};
|
|
133
|
+
const QUOTE_ABORT_REASONS = new Set(Object.values(constants_js_1.AbortReason));
|
|
134
|
+
const isExpectedQuoteAbort = (error, signal) => {
|
|
135
|
+
if (signal?.aborted) {
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
if (QUOTE_ABORT_REASONS.has(String(error))) {
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
const errorText = error instanceof Error ? `${error.name} ${error.message}` : String(error);
|
|
142
|
+
return (errorText.includes('AbortError') ||
|
|
143
|
+
errorText.includes('FetchRequestCanceledException'));
|
|
144
|
+
};
|
|
132
145
|
const MESSENGER_EXPOSED_METHODS = [
|
|
133
146
|
'updateBridgeQuoteRequestParams',
|
|
134
147
|
'fetchQuotes',
|
|
@@ -504,6 +517,9 @@ class BridgeController extends (0, polling_controller_1.StaticIntervalPollingCon
|
|
|
504
517
|
__classPrivateFieldGet(this, _BridgeController_abortController, "f")?.abort(constants_js_1.AbortReason.NewQuoteRequest);
|
|
505
518
|
__classPrivateFieldGet(this, _BridgeController_batchSellTradesAbortController, "f")?.abort(constants_js_1.AbortReason.NewQuoteRequest);
|
|
506
519
|
__classPrivateFieldSet(this, _BridgeController_abortController, new AbortController(), "f");
|
|
520
|
+
const quoteTraceStartTime = Date.now();
|
|
521
|
+
const quoteTraceRequestId = (0, uuid_1.v4)();
|
|
522
|
+
const quoteAbortSignal = __classPrivateFieldGet(this, _BridgeController_abortController, "f").signal;
|
|
507
523
|
__classPrivateFieldGet(this, _BridgeController_fetchAssetExchangeRates, "f").call(this, quoteRequests).catch((error) => console.warn('Failed to fetch asset exchange rates', error));
|
|
508
524
|
this.trackUnifiedSwapBridgeEvent(constants_js_1.UnifiedSwapBridgeEventName.QuotesRequested, context);
|
|
509
525
|
const { sse, maxRefreshCount } = (0, feature_flags_js_1.getBridgeFeatureFlags)(this.messenger);
|
|
@@ -524,43 +540,84 @@ class BridgeController extends (0, polling_controller_1.StaticIntervalPollingCon
|
|
|
524
540
|
}
|
|
525
541
|
});
|
|
526
542
|
const jwt = await __classPrivateFieldGet(this, _BridgeController_getJwt, "f").call(this);
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
543
|
+
const [firstQuoteRequest] = quoteRequests;
|
|
544
|
+
let traceResult = 'error';
|
|
545
|
+
let traceName = traces_js_1.TraceName.BatchSellQuotesFetched;
|
|
546
|
+
if (!isBatchSellRequest) {
|
|
547
|
+
traceName = (0, bridge_js_2.isCrossChain)(firstQuoteRequest.srcChainId, firstQuoteRequest.destChainId)
|
|
530
548
|
? traces_js_1.TraceName.BridgeQuotesFetched
|
|
531
549
|
: traces_js_1.TraceName.SwapQuotesFetched;
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
550
|
+
}
|
|
551
|
+
const tracedProviders = new Set();
|
|
552
|
+
const traceWithoutImpact = async (request) => {
|
|
553
|
+
try {
|
|
554
|
+
await __classPrivateFieldGet(this, _BridgeController_trace, "f").call(this, request, () => undefined);
|
|
555
|
+
}
|
|
556
|
+
catch {
|
|
557
|
+
// Telemetry failures must not affect quote fetching or state updates.
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
const traceProviderFirstResult = (providerData) => {
|
|
561
|
+
const provider = (0, properties_js_1.formatProviderLabel)(providerData);
|
|
562
|
+
if (isBatchSellRequest || tracedProviders.has(provider)) {
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
tracedProviders.add(provider);
|
|
566
|
+
// Provider telemetry must not delay quote processing.
|
|
567
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
568
|
+
traceWithoutImpact({
|
|
569
|
+
name: traces_js_1.TraceName.QuoteProviderFirstResult,
|
|
570
|
+
startTime: quoteTraceStartTime,
|
|
536
571
|
data: {
|
|
572
|
+
provider,
|
|
573
|
+
feature_id: context.feature_id,
|
|
574
|
+
request_id: quoteTraceRequestId,
|
|
575
|
+
swap_type: (0, properties_js_1.getSwapType)(firstQuoteRequest.srcChainId, firstQuoteRequest.destChainId),
|
|
537
576
|
srcChainId: (0, caip_formatters_js_1.formatChainIdToCaip)(firstQuoteRequest.srcChainId),
|
|
538
577
|
destChainId: (0, caip_formatters_js_1.formatChainIdToCaip)(firstQuoteRequest.destChainId),
|
|
578
|
+
result: 'success',
|
|
539
579
|
},
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
580
|
+
});
|
|
581
|
+
};
|
|
582
|
+
try {
|
|
583
|
+
const selectedAccount = __classPrivateFieldGet(this, _BridgeController_instances, "m", _BridgeController_getMultichainSelectedAccount).call(this, firstQuoteRequest.walletAddress);
|
|
584
|
+
// This call is not awaited to prevent blocking quote fetching if the snap takes too long to respond
|
|
585
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
586
|
+
__classPrivateFieldGet(this, _BridgeController_setMinimumBalanceForRentExemptionInLamports, "f").call(this, firstQuoteRequest.srcChainId, selectedAccount?.metadata?.snap?.id);
|
|
587
|
+
// Use SSE if enabled and return early
|
|
588
|
+
if (shouldStream || isBatchSellRequest) {
|
|
589
|
+
const quoteCount = await __classPrivateFieldGet(this, _BridgeController_handleQuoteStreaming, "f").call(this, {
|
|
590
|
+
quoteRequests,
|
|
591
|
+
featureId: context.feature_id,
|
|
592
|
+
jwt,
|
|
593
|
+
selectedAccount,
|
|
594
|
+
signal: quoteAbortSignal,
|
|
595
|
+
traceProviderFirstResult,
|
|
596
|
+
});
|
|
597
|
+
if (quoteAbortSignal.aborted) {
|
|
598
|
+
traceResult = 'cancelled';
|
|
548
599
|
return;
|
|
549
600
|
}
|
|
601
|
+
traceResult = quoteCount > 0 ? 'success' : 'no_quotes';
|
|
602
|
+
}
|
|
603
|
+
else {
|
|
550
604
|
// Otherwise use regular fetch
|
|
551
|
-
const quotes = await this.fetchQuotes(firstQuoteRequest, context.feature_id,
|
|
605
|
+
const quotes = await this.fetchQuotes(firstQuoteRequest, context.feature_id, quoteAbortSignal);
|
|
606
|
+
for (const quote of quotes) {
|
|
607
|
+
traceProviderFirstResult(quote.quote);
|
|
608
|
+
}
|
|
552
609
|
this.update((state) => {
|
|
553
610
|
// Set the initial load time if this is the first fetch
|
|
554
611
|
if (state.quotesRefreshCount ===
|
|
555
612
|
bridge_js_1.DEFAULT_BRIDGE_CONTROLLER_STATE.quotesRefreshCount &&
|
|
556
613
|
__classPrivateFieldGet(this, _BridgeController_quotesFirstFetched, "f")) {
|
|
557
|
-
state.quotesInitialLoadTime =
|
|
558
|
-
Date.now() - __classPrivateFieldGet(this, _BridgeController_quotesFirstFetched, "f");
|
|
614
|
+
state.quotesInitialLoadTime = Date.now() - __classPrivateFieldGet(this, _BridgeController_quotesFirstFetched, "f");
|
|
559
615
|
}
|
|
560
616
|
state.quotes = quotes.map(quote_response_v1_to_v2_js_1.toQuoteResponseV2);
|
|
561
617
|
state.quotesLoadingStatus = types_js_1.RequestStatus.FETCHED;
|
|
562
618
|
});
|
|
563
|
-
|
|
619
|
+
traceResult = quotes.length > 0 ? 'success' : 'no_quotes';
|
|
620
|
+
}
|
|
564
621
|
}
|
|
565
622
|
catch (error) {
|
|
566
623
|
// Reset the quotes list if the fetch fails to avoid showing stale quotes
|
|
@@ -568,14 +625,8 @@ class BridgeController extends (0, polling_controller_1.StaticIntervalPollingCon
|
|
|
568
625
|
state.quotes = bridge_js_1.DEFAULT_BRIDGE_CONTROLLER_STATE.quotes;
|
|
569
626
|
});
|
|
570
627
|
// Ignore abort errors
|
|
571
|
-
if (error
|
|
572
|
-
|
|
573
|
-
[
|
|
574
|
-
constants_js_1.AbortReason.ResetState,
|
|
575
|
-
constants_js_1.AbortReason.NewQuoteRequest,
|
|
576
|
-
constants_js_1.AbortReason.QuoteRequestUpdated,
|
|
577
|
-
constants_js_1.AbortReason.TransactionSubmitted,
|
|
578
|
-
].includes(error)) {
|
|
628
|
+
if (isExpectedQuoteAbort(error, quoteAbortSignal)) {
|
|
629
|
+
traceResult = 'cancelled';
|
|
579
630
|
// Exit the function early to prevent other state updates
|
|
580
631
|
return;
|
|
581
632
|
}
|
|
@@ -600,6 +651,21 @@ class BridgeController extends (0, polling_controller_1.StaticIntervalPollingCon
|
|
|
600
651
|
this.trackUnifiedSwapBridgeEvent(constants_js_1.UnifiedSwapBridgeEventName.QuotesError, context);
|
|
601
652
|
console.log(`Failed to ${shouldStream ? 'stream' : 'fetch'} bridge quotes`, error);
|
|
602
653
|
}
|
|
654
|
+
finally {
|
|
655
|
+
await traceWithoutImpact({
|
|
656
|
+
name: traceName,
|
|
657
|
+
startTime: quoteTraceStartTime,
|
|
658
|
+
data: {
|
|
659
|
+
srcChainId: (0, caip_formatters_js_1.formatChainIdToCaip)(firstQuoteRequest.srcChainId),
|
|
660
|
+
destChainId: (0, caip_formatters_js_1.formatChainIdToCaip)(firstQuoteRequest.destChainId),
|
|
661
|
+
...(!isBatchSellRequest && {
|
|
662
|
+
request_id: quoteTraceRequestId,
|
|
663
|
+
feature_id: context.feature_id,
|
|
664
|
+
result: traceResult,
|
|
665
|
+
}),
|
|
666
|
+
},
|
|
667
|
+
});
|
|
668
|
+
}
|
|
603
669
|
// Update refresh count after fetching, validation and fee calculation have completed
|
|
604
670
|
this.update((state) => {
|
|
605
671
|
state.quotesRefreshCount += 1;
|
|
@@ -614,7 +680,7 @@ class BridgeController extends (0, polling_controller_1.StaticIntervalPollingCon
|
|
|
614
680
|
this.stopAllPolling();
|
|
615
681
|
}
|
|
616
682
|
});
|
|
617
|
-
_BridgeController_handleQuoteStreaming.set(this, async (quoteRequests, featureId, jwt, selectedAccount) => {
|
|
683
|
+
_BridgeController_handleQuoteStreaming.set(this, async ({ quoteRequests, featureId, jwt, selectedAccount, signal, traceProviderFirstResult, }) => {
|
|
618
684
|
/**
|
|
619
685
|
* Tracks the number of valid quotes received from the current stream, which is used
|
|
620
686
|
* to determine when to clear the quotes list and set the initial load time
|
|
@@ -625,13 +691,14 @@ class BridgeController extends (0, polling_controller_1.StaticIntervalPollingCon
|
|
|
625
691
|
* before setting quotesLoadingStatus to FETCHED
|
|
626
692
|
*/
|
|
627
693
|
const pendingFeeAppendPromises = new Set();
|
|
628
|
-
await (0, fetch_js_1.fetchBridgeQuoteStream)(__classPrivateFieldGet(this, _BridgeController_fetchFn, "f"), quoteRequests,
|
|
694
|
+
await (0, fetch_js_1.fetchBridgeQuoteStream)(__classPrivateFieldGet(this, _BridgeController_fetchFn, "f"), quoteRequests, signal, featureId, __classPrivateFieldGet(this, _BridgeController_clientId, "f"), jwt, __classPrivateFieldGet(this, _BridgeController_config, "f").customBridgeApiBaseUrl ?? bridge_js_1.BRIDGE_PROD_API_BASE_URL, {
|
|
629
695
|
onQuoteValidationFailure: (validationFailures) => __classPrivateFieldGet(this, _BridgeController_trackQuoteValidationFailures, "f").call(this, validationFailures, featureId),
|
|
630
696
|
onValidQuoteReceived: async (quote) => {
|
|
631
697
|
const feeAppendPromise = (async () => {
|
|
632
698
|
const quotesWithFees = await (0, quote_fees_js_1.appendFeesToQuotes)(quote.chainId, [quote], this.messenger, __classPrivateFieldGet(this, _BridgeController_getLayer1GasFee, "f"), selectedAccount);
|
|
633
699
|
if (quotesWithFees.length > 0) {
|
|
634
700
|
validQuotesCounter += 1;
|
|
701
|
+
traceProviderFirstResult(quote.quote);
|
|
635
702
|
}
|
|
636
703
|
this.update((state) => {
|
|
637
704
|
// Clear previous quotes and quotes load time when first quote in the current
|
|
@@ -691,6 +758,7 @@ class BridgeController extends (0, polling_controller_1.StaticIntervalPollingCon
|
|
|
691
758
|
});
|
|
692
759
|
},
|
|
693
760
|
}, __classPrivateFieldGet(this, _BridgeController_clientVersion, "f"));
|
|
761
|
+
return validQuotesCounter;
|
|
694
762
|
});
|
|
695
763
|
_BridgeController_setMinimumBalanceForRentExemptionInLamports.set(this, async (srcChainId, snapId) => {
|
|
696
764
|
if (!(0, bridge_js_2.isSolanaChainId)(srcChainId) || !snapId) {
|