Skip to content

Commit 45b6fbd

Browse files
committed
refactor: combine streaming + non-streaming fetch
1 parent 118d3d0 commit 45b6fbd

File tree

2 files changed

+79
-156
lines changed

2 files changed

+79
-156
lines changed

packages/bridge-controller/src/bridge-controller.ts

Lines changed: 78 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -267,14 +267,7 @@ export class BridgeController extends StaticIntervalPollingController<BridgePoll
267267
}
268268

269269
_executePoll = async (pollingInput: BridgePollingInput) => {
270-
const shouldStream = Boolean(
271-
getBridgeFeatureFlags(this.messagingSystem).sseEnabled,
272-
);
273-
if (shouldStream) {
274-
await this.#fetchBridgeQuoteStream(pollingInput);
275-
} else {
276-
await this.#fetchBridgeQuotes(pollingInput);
277-
}
270+
await this.#fetchBridgeQuotes(pollingInput);
278271
};
279272

280273
updateBridgeQuoteRequestParams = async (
@@ -391,13 +384,8 @@ export class BridgeController extends StaticIntervalPollingController<BridgePoll
391384

392385
this.#trackResponseValidationFailures(validationFailures);
393386

394-
const quotesWithL1GasFees = await this.#appendL1GasFees(baseQuotes);
395-
const quotesWithNonEvmFees = await this.#appendNonEvmFees(
396-
baseQuotes,
397-
quoteRequest.walletAddress,
398-
);
399-
const quotesWithFees =
400-
quotesWithL1GasFees ?? quotesWithNonEvmFees ?? baseQuotes;
387+
const quotesWithFees = await this.#appendFees(quoteRequest, baseQuotes);
388+
401389
// Sort perps quotes by increasing estimated processing time (fastest first)
402390
if (featureId === FeatureId.PERPS) {
403391
return quotesWithFees.sort((a, b) => {
@@ -410,6 +398,20 @@ export class BridgeController extends StaticIntervalPollingController<BridgePoll
410398
return quotesWithFees;
411399
};
412400

401+
readonly #appendFees = async (
402+
quoteRequest: GenericQuoteRequest,
403+
baseQuotes: QuoteResponse[],
404+
) => {
405+
const quotesWithL1GasFees = await this.#appendL1GasFees(baseQuotes);
406+
const quotesWithNonEvmFees = await this.#appendNonEvmFees(
407+
baseQuotes,
408+
quoteRequest.walletAddress,
409+
);
410+
const quotesWithFees =
411+
quotesWithL1GasFees ?? quotesWithNonEvmFees ?? baseQuotes;
412+
return quotesWithFees;
413+
};
414+
413415
readonly #trackResponseValidationFailures = (
414416
validationFailures: string[],
415417
) => {
@@ -587,10 +589,14 @@ export class BridgeController extends StaticIntervalPollingController<BridgePoll
587589
);
588590
this.update((state) => {
589591
state.quotesLoadingStatus = RequestStatus.LOADING;
590-
state.quoteRequest = updatedQuoteRequest;
591592
state.quoteFetchError = DEFAULT_BRIDGE_CONTROLLER_STATE.quoteFetchError;
592593
});
593594

595+
const { sseEnabled, maxRefreshCount } = getBridgeFeatureFlags(
596+
this.messagingSystem,
597+
);
598+
const shouldStream = Boolean(sseEnabled);
599+
594600
try {
595601
await this.#trace(
596602
{
@@ -611,17 +617,49 @@ export class BridgeController extends StaticIntervalPollingController<BridgePoll
611617
this.#setMinimumBalanceForRentExemptionInLamports(
612618
updatedQuoteRequest.srcChainId,
613619
);
614-
const quotes = await this.fetchQuotes(
615-
updatedQuoteRequest,
616-
// AbortController is always defined by this line, because we assign it a few lines above,
617-
// not sure why Jest thinks it's not
618-
// Linters accurately say that it's defined
619-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
620-
this.#abortController!.signal as AbortSignal,
621-
);
620+
if (shouldStream) {
621+
await fetchBridgeQuoteStream(
622+
this.#fetchFn,
623+
updatedQuoteRequest,
624+
this.#abortController?.signal,
625+
this.#clientId,
626+
this.#config.customBridgeApiBaseUrl ?? BRIDGE_PROD_API_BASE_URL,
627+
{
628+
onValidationFailures: (validationFailures: string[]) => {
629+
this.#trackResponseValidationFailures(validationFailures);
630+
},
631+
onValidQuotesReceived: async (quotes: QuoteResponse[]) => {
632+
const quotesWithFees = await this.#appendFees(
633+
updatedQuoteRequest,
634+
quotes,
635+
);
636+
this.update((state) => {
637+
// If there are no other quotes yet, set initial load time
638+
if (
639+
state.quotes.length === 0 &&
640+
quotesWithFees.length > 0
641+
) {
642+
state.quotesInitialLoadTime = Date.now();
643+
}
644+
state.quotes.push(...quotesWithFees);
645+
});
646+
},
647+
},
648+
);
649+
} else {
650+
const quotes = await this.fetchQuotes(
651+
updatedQuoteRequest,
652+
// AbortController is always defined by this line, because we assign it a few lines above,
653+
// not sure why Jest thinks it's not
654+
// Linters accurately say that it's defined
655+
this.#abortController?.signal as AbortSignal,
656+
);
657+
this.update((state) => {
658+
state.quotes = quotes;
659+
});
660+
}
622661

623662
this.update((state) => {
624-
state.quotes = quotes;
625663
state.quotesLoadingStatus = RequestStatus.FETCHED;
626664
});
627665
},
@@ -641,134 +679,18 @@ export class BridgeController extends StaticIntervalPollingController<BridgePoll
641679
}
642680

643681
this.update((state) => {
644-
state.quoteFetchError =
645-
error instanceof Error ? error.message : (error?.toString() ?? null);
646-
state.quotesLoadingStatus = RequestStatus.ERROR;
647-
state.quotes = DEFAULT_BRIDGE_CONTROLLER_STATE.quotes;
648-
});
649-
this.trackUnifiedSwapBridgeEvent(
650-
UnifiedSwapBridgeEventName.QuotesError,
651-
context,
652-
);
653-
console.log('Failed to fetch bridge quotes', error);
654-
}
655-
const bridgeFeatureFlags = getBridgeFeatureFlags(this.messagingSystem);
656-
const { maxRefreshCount } = bridgeFeatureFlags;
657-
658-
// Stop polling if the maximum number of refreshes has been reached
659-
if (
660-
updatedQuoteRequest.insufficientBal ||
661-
(!updatedQuoteRequest.insufficientBal &&
662-
this.state.quotesRefreshCount >= maxRefreshCount)
663-
) {
664-
this.stopAllPolling();
665-
}
666-
667-
// Update quote fetching stats
668-
const quotesLastFetched = Date.now();
669-
this.update((state) => {
670-
state.quotesInitialLoadTime =
671-
state.quotesRefreshCount === 0 && this.#quotesFirstFetched
672-
? quotesLastFetched - this.#quotesFirstFetched
673-
: this.state.quotesInitialLoadTime;
674-
state.quotesLastFetched = quotesLastFetched;
675-
state.quotesRefreshCount += 1;
676-
});
677-
};
678-
679-
readonly #fetchBridgeQuoteStream = async ({
680-
networkClientId: _networkClientId,
681-
updatedQuoteRequest,
682-
context,
683-
}: BridgePollingInput) => {
684-
this.#abortController?.abort('New quote request');
685-
this.#abortController = new AbortController();
686-
687-
this.trackUnifiedSwapBridgeEvent(
688-
UnifiedSwapBridgeEventName.QuotesRequested,
689-
context,
690-
);
691-
this.update((state) => {
692-
state.quotesLoadingStatus = RequestStatus.LOADING;
693-
state.quoteFetchError = DEFAULT_BRIDGE_CONTROLLER_STATE.quoteFetchError;
694-
});
695-
696-
try {
697-
await this.#trace(
698-
{
699-
name: isCrossChain(
700-
updatedQuoteRequest.srcChainId,
701-
updatedQuoteRequest.destChainId,
702-
)
703-
? TraceName.BridgeQuotesFetched
704-
: TraceName.SwapQuotesFetched,
705-
data: {
706-
srcChainId: formatChainIdToCaip(updatedQuoteRequest.srcChainId),
707-
destChainId: formatChainIdToCaip(updatedQuoteRequest.destChainId),
708-
},
709-
},
710-
async () => {
711-
// This call is not awaited to prevent blocking quote fetching if the snap takes too long to respond
712-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
713-
this.#setMinimumBalanceForRentExemptionInLamports(
714-
updatedQuoteRequest.srcChainId,
715-
);
716-
await fetchBridgeQuoteStream(
717-
this.#fetchFn,
718-
updatedQuoteRequest,
719-
// AbortController is always defined by this line, because we assign it a few lines above,
720-
// not sure why Jest thinks it's not
721-
// Linters accurately say that it's defined
722-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
723-
this.#abortController!.signal as AbortSignal,
724-
this.#clientId,
725-
this.#config.customBridgeApiBaseUrl ?? BRIDGE_PROD_API_BASE_URL,
726-
{
727-
onValidationFailures: (validationFailures: string[]) => {
728-
this.#trackResponseValidationFailures(validationFailures);
729-
},
730-
onValidQuotesReceived: async (quotes: QuoteResponse[]) => {
731-
const quotesWithL1GasFees = await this.#appendL1GasFees(quotes);
732-
const quotesWithNonEvmFees = await this.#appendNonEvmFees(
733-
quotes,
734-
updatedQuoteRequest.walletAddress,
735-
);
736-
const quotesWithFees =
737-
quotesWithL1GasFees ?? quotesWithNonEvmFees ?? quotes;
738-
this.update((state) => {
739-
// If there are no other quotes yet, set initial load time
740-
if (state.quotes.length === 0 && quotesWithFees.length > 0) {
741-
state.quotesInitialLoadTime = Date.now();
742-
}
743-
state.quotes.push(...quotesWithFees);
744-
});
745-
},
746-
},
747-
);
748-
},
749-
);
750-
this.update((state) => {
751-
state.quotesLoadingStatus = RequestStatus.FETCHED;
752-
});
753-
} catch (error) {
754-
const isAbortError = (error as Error).name === 'AbortError';
755-
if (
756-
isAbortError ||
757-
[
758-
AbortReason.ResetState,
759-
AbortReason.NewQuoteRequest,
760-
AbortReason.QuoteRequestUpdated,
761-
].includes(error as AbortReason)
762-
) {
763-
// Exit the function early to prevent other state updates
764-
return;
765-
}
682+
if (shouldStream) {
683+
state.quoteFetchError =
684+
this.#clientId === BridgeClientId.MOBILE
685+
? 'generic streaming error'
686+
: ((error ?? 'error') as never as string);
687+
} else {
688+
state.quoteFetchError =
689+
error instanceof Error
690+
? error.message
691+
: (error?.toString() ?? null);
692+
}
766693

767-
this.update((state) => {
768-
state.quoteFetchError =
769-
this.#clientId === BridgeClientId.MOBILE
770-
? 'generic streaming error'
771-
: ((error ?? 'error') as never as string);
772694
state.quotesLoadingStatus = RequestStatus.ERROR;
773695
state.quotes = DEFAULT_BRIDGE_CONTROLLER_STATE.quotes;
774696
});
@@ -777,10 +699,11 @@ export class BridgeController extends StaticIntervalPollingController<BridgePoll
777699
UnifiedSwapBridgeEventName.QuotesError,
778700
context,
779701
);
780-
console.log('Failed to stream bridge quotes.', error);
702+
console.log(
703+
`Failed to ${shouldStream ? 'stream' : 'fetch'} bridge quotes`,
704+
error,
705+
);
781706
}
782-
const bridgeFeatureFlags = getBridgeFeatureFlags(this.messagingSystem);
783-
const { maxRefreshCount } = bridgeFeatureFlags;
784707

785708
// Stop polling if the maximum number of refreshes has been reached
786709
if (

packages/bridge-controller/src/utils/fetch.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ export const fetchAssetPrices = async (
271271
export async function fetchBridgeQuoteStream(
272272
fetchFn: FetchFunction,
273273
request: GenericQuoteRequest,
274-
signal: AbortSignal | null,
274+
signal: AbortSignal | undefined,
275275
clientId: string,
276276
bridgeApiBaseUrl: string,
277277
serverEventHandlers: {

0 commit comments

Comments
 (0)