@metamask-previews/bridge-controller 79.2.0-preview-a1017a8ee → 79.2.0-preview-c2ef75d18
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 +95 -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 +96 -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,81 @@ 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 = async (providerData) => {
|
|
561
|
+
const provider = (0, properties_js_1.formatProviderLabel)(providerData);
|
|
562
|
+
if (isBatchSellRequest || tracedProviders.has(provider)) {
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
tracedProviders.add(provider);
|
|
566
|
+
await traceWithoutImpact({
|
|
567
|
+
name: traces_js_1.TraceName.QuoteProviderFirstResult,
|
|
568
|
+
startTime: quoteTraceStartTime,
|
|
536
569
|
data: {
|
|
570
|
+
provider,
|
|
571
|
+
request_id: quoteTraceRequestId,
|
|
572
|
+
swap_type: (0, properties_js_1.getSwapType)(firstQuoteRequest.srcChainId, firstQuoteRequest.destChainId),
|
|
537
573
|
srcChainId: (0, caip_formatters_js_1.formatChainIdToCaip)(firstQuoteRequest.srcChainId),
|
|
538
574
|
destChainId: (0, caip_formatters_js_1.formatChainIdToCaip)(firstQuoteRequest.destChainId),
|
|
575
|
+
result: 'success',
|
|
539
576
|
},
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
577
|
+
});
|
|
578
|
+
};
|
|
579
|
+
try {
|
|
580
|
+
const selectedAccount = __classPrivateFieldGet(this, _BridgeController_instances, "m", _BridgeController_getMultichainSelectedAccount).call(this, firstQuoteRequest.walletAddress);
|
|
581
|
+
// This call is not awaited to prevent blocking quote fetching if the snap takes too long to respond
|
|
582
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
583
|
+
__classPrivateFieldGet(this, _BridgeController_setMinimumBalanceForRentExemptionInLamports, "f").call(this, firstQuoteRequest.srcChainId, selectedAccount?.metadata?.snap?.id);
|
|
584
|
+
// Use SSE if enabled and return early
|
|
585
|
+
if (shouldStream || isBatchSellRequest) {
|
|
586
|
+
const quoteCount = await __classPrivateFieldGet(this, _BridgeController_handleQuoteStreaming, "f").call(this, {
|
|
587
|
+
quoteRequests,
|
|
588
|
+
featureId: context.feature_id,
|
|
589
|
+
jwt,
|
|
590
|
+
selectedAccount,
|
|
591
|
+
signal: quoteAbortSignal,
|
|
592
|
+
traceProviderFirstResult,
|
|
593
|
+
});
|
|
594
|
+
if (quoteAbortSignal.aborted) {
|
|
595
|
+
traceResult = 'cancelled';
|
|
548
596
|
return;
|
|
549
597
|
}
|
|
598
|
+
traceResult = quoteCount > 0 ? 'success' : 'no_quotes';
|
|
599
|
+
}
|
|
600
|
+
else {
|
|
550
601
|
// Otherwise use regular fetch
|
|
551
|
-
const quotes = await this.fetchQuotes(firstQuoteRequest, context.feature_id,
|
|
602
|
+
const quotes = await this.fetchQuotes(firstQuoteRequest, context.feature_id, quoteAbortSignal);
|
|
603
|
+
for (const quote of quotes) {
|
|
604
|
+
await traceProviderFirstResult(quote.quote);
|
|
605
|
+
}
|
|
552
606
|
this.update((state) => {
|
|
553
607
|
// Set the initial load time if this is the first fetch
|
|
554
608
|
if (state.quotesRefreshCount ===
|
|
555
609
|
bridge_js_1.DEFAULT_BRIDGE_CONTROLLER_STATE.quotesRefreshCount &&
|
|
556
610
|
__classPrivateFieldGet(this, _BridgeController_quotesFirstFetched, "f")) {
|
|
557
|
-
state.quotesInitialLoadTime =
|
|
558
|
-
Date.now() - __classPrivateFieldGet(this, _BridgeController_quotesFirstFetched, "f");
|
|
611
|
+
state.quotesInitialLoadTime = Date.now() - __classPrivateFieldGet(this, _BridgeController_quotesFirstFetched, "f");
|
|
559
612
|
}
|
|
560
613
|
state.quotes = quotes.map(quote_response_v1_to_v2_js_1.toQuoteResponseV2);
|
|
561
614
|
state.quotesLoadingStatus = types_js_1.RequestStatus.FETCHED;
|
|
562
615
|
});
|
|
563
|
-
|
|
616
|
+
traceResult = quotes.length > 0 ? 'success' : 'no_quotes';
|
|
617
|
+
}
|
|
564
618
|
}
|
|
565
619
|
catch (error) {
|
|
566
620
|
// Reset the quotes list if the fetch fails to avoid showing stale quotes
|
|
@@ -568,14 +622,8 @@ class BridgeController extends (0, polling_controller_1.StaticIntervalPollingCon
|
|
|
568
622
|
state.quotes = bridge_js_1.DEFAULT_BRIDGE_CONTROLLER_STATE.quotes;
|
|
569
623
|
});
|
|
570
624
|
// 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)) {
|
|
625
|
+
if (isExpectedQuoteAbort(error, quoteAbortSignal)) {
|
|
626
|
+
traceResult = 'cancelled';
|
|
579
627
|
// Exit the function early to prevent other state updates
|
|
580
628
|
return;
|
|
581
629
|
}
|
|
@@ -600,6 +648,22 @@ class BridgeController extends (0, polling_controller_1.StaticIntervalPollingCon
|
|
|
600
648
|
this.trackUnifiedSwapBridgeEvent(constants_js_1.UnifiedSwapBridgeEventName.QuotesError, context);
|
|
601
649
|
console.log(`Failed to ${shouldStream ? 'stream' : 'fetch'} bridge quotes`, error);
|
|
602
650
|
}
|
|
651
|
+
finally {
|
|
652
|
+
await traceWithoutImpact({
|
|
653
|
+
name: traceName,
|
|
654
|
+
startTime: quoteTraceStartTime,
|
|
655
|
+
data: {
|
|
656
|
+
srcChainId: (0, caip_formatters_js_1.formatChainIdToCaip)(firstQuoteRequest.srcChainId),
|
|
657
|
+
destChainId: (0, caip_formatters_js_1.formatChainIdToCaip)(firstQuoteRequest.destChainId),
|
|
658
|
+
...(!isBatchSellRequest && {
|
|
659
|
+
request_id: quoteTraceRequestId,
|
|
660
|
+
swap_type: (0, properties_js_1.getSwapType)(firstQuoteRequest.srcChainId, firstQuoteRequest.destChainId),
|
|
661
|
+
feature_id: context.feature_id,
|
|
662
|
+
result: traceResult,
|
|
663
|
+
}),
|
|
664
|
+
},
|
|
665
|
+
});
|
|
666
|
+
}
|
|
603
667
|
// Update refresh count after fetching, validation and fee calculation have completed
|
|
604
668
|
this.update((state) => {
|
|
605
669
|
state.quotesRefreshCount += 1;
|
|
@@ -614,7 +678,7 @@ class BridgeController extends (0, polling_controller_1.StaticIntervalPollingCon
|
|
|
614
678
|
this.stopAllPolling();
|
|
615
679
|
}
|
|
616
680
|
});
|
|
617
|
-
_BridgeController_handleQuoteStreaming.set(this, async (quoteRequests, featureId, jwt, selectedAccount) => {
|
|
681
|
+
_BridgeController_handleQuoteStreaming.set(this, async ({ quoteRequests, featureId, jwt, selectedAccount, signal, traceProviderFirstResult, }) => {
|
|
618
682
|
/**
|
|
619
683
|
* Tracks the number of valid quotes received from the current stream, which is used
|
|
620
684
|
* to determine when to clear the quotes list and set the initial load time
|
|
@@ -625,9 +689,10 @@ class BridgeController extends (0, polling_controller_1.StaticIntervalPollingCon
|
|
|
625
689
|
* before setting quotesLoadingStatus to FETCHED
|
|
626
690
|
*/
|
|
627
691
|
const pendingFeeAppendPromises = new Set();
|
|
628
|
-
await (0, fetch_js_1.fetchBridgeQuoteStream)(__classPrivateFieldGet(this, _BridgeController_fetchFn, "f"), quoteRequests,
|
|
692
|
+
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
693
|
onQuoteValidationFailure: (validationFailures) => __classPrivateFieldGet(this, _BridgeController_trackQuoteValidationFailures, "f").call(this, validationFailures, featureId),
|
|
630
694
|
onValidQuoteReceived: async (quote) => {
|
|
695
|
+
await traceProviderFirstResult(quote.quote);
|
|
631
696
|
const feeAppendPromise = (async () => {
|
|
632
697
|
const quotesWithFees = await (0, quote_fees_js_1.appendFeesToQuotes)(quote.chainId, [quote], this.messenger, __classPrivateFieldGet(this, _BridgeController_getLayer1GasFee, "f"), selectedAccount);
|
|
633
698
|
if (quotesWithFees.length > 0) {
|
|
@@ -691,6 +756,7 @@ class BridgeController extends (0, polling_controller_1.StaticIntervalPollingCon
|
|
|
691
756
|
});
|
|
692
757
|
},
|
|
693
758
|
}, __classPrivateFieldGet(this, _BridgeController_clientVersion, "f"));
|
|
759
|
+
return validQuotesCounter;
|
|
694
760
|
});
|
|
695
761
|
_BridgeController_setMinimumBalanceForRentExemptionInLamports.set(this, async (srcChainId, snapId) => {
|
|
696
762
|
if (!(0, bridge_js_2.isSolanaChainId)(srcChainId) || !snapId) {
|