From 8e2e05460cf27d60f3aef12d22490f8b00ab96ce Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Wed, 25 Mar 2026 15:35:10 -0400 Subject: [PATCH 01/12] chore: add validators for complete schema --- .../bridge-controller/src/utils/validators.ts | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/packages/bridge-controller/src/utils/validators.ts b/packages/bridge-controller/src/utils/validators.ts index 660a76810ac..73603b2ee9b 100644 --- a/packages/bridge-controller/src/utils/validators.ts +++ b/packages/bridge-controller/src/utils/validators.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/naming-convention */ import { isValidHexAddress } from '@metamask/controller-utils'; import type { Infer } from '@metamask/superstruct'; import { @@ -495,3 +496,29 @@ export const validateTokenFeature = ( assert(data, TokenFeatureSchema); return true; }; + +export enum QuoteStreamCompleteReason { + RETRY = 'RETRY', + AMOUNT_TOO_HIGH = 'AMOUNT_TOO_HIGH', + AMOUNT_TOO_LOW = 'AMOUNT_TOO_LOW', + SLIPPAGE_TOO_HIGH = 'SLIPPAGE_TOO_HIGH', + SLIPPAGE_TOO_LOW = 'SLIPPAGE_TOO_LOW', + TOKEN_NOT_SUPPORTED = 'TOKEN_NOT_SUPPORTED', + RWA_GEO_RESTRICTED = 'RWA_GEO_RESTRICTED', + RWA_NATIVE_TOKEN_UNSUPPORTED = 'RWA_NATIVE_TOKEN_UNSUPPORTED', + RWA_MARKET_UNAVAILABLE = 'RWA_MARKET_UNAVAILABLE', +} + +export const QuoteStreamCompleteSchema = type({ + quoteCount: number(), + hasQuotes: boolean(), + reason: optional(enums(Object.values(QuoteStreamCompleteReason))), + context: optional(record(string(), any())), +}); + +export const validateQuoteStreamComplete = ( + data: unknown, +): data is Infer => { + assert(data, QuoteStreamCompleteSchema); + return true; +}; From 0ae3b484874995f805353452bab681ef2bbe668d Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Wed, 25 Mar 2026 15:36:02 -0400 Subject: [PATCH 02/12] chore: add complete type and field to state --- packages/bridge-controller/src/types.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/bridge-controller/src/types.ts b/packages/bridge-controller/src/types.ts index 7e9e5ff958c..bce79d3129e 100644 --- a/packages/bridge-controller/src/types.ts +++ b/packages/bridge-controller/src/types.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/naming-convention */ import type { AccountsControllerGetAccountByAddressAction } from '@metamask/accounts-controller'; import type { AssetsControllerGetExchangeRatesForBridgeAction } from '@metamask/assets-controller'; import type { @@ -42,6 +43,7 @@ import type { QuoteSchema, StepSchema, TokenFeatureSchema, + QuoteStreamCompleteSchema, TronTradeDataSchema, TxDataSchema, } from './utils/validators'; @@ -325,6 +327,8 @@ export type FeatureFlagsPlatformConfig = Infer; export type TokenFeature = Infer; +export type QuoteStreamCompleteData = Infer; + export enum RequestStatus { LOADING, FETCHED, @@ -384,6 +388,11 @@ export type BridgeControllerState = { * populated from `token_warning` SSE events. */ tokenWarnings: TokenFeature[]; + /** + * Metadata about the completed quote stream, populated from the `complete` SSE event. + * Set to null at the start of each fetch and updated when the complete event is received. + */ + quoteStreamComplete: QuoteStreamCompleteData | null; }; export type BridgeControllerAction< From 1e09ef6269de8566d549c3acd39ab3f72523ec43 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Wed, 25 Mar 2026 15:36:46 -0400 Subject: [PATCH 03/12] chore: export types and validator --- packages/bridge-controller/src/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/bridge-controller/src/index.ts b/packages/bridge-controller/src/index.ts index 2839194db38..d83ea093cc5 100644 --- a/packages/bridge-controller/src/index.ts +++ b/packages/bridge-controller/src/index.ts @@ -69,6 +69,7 @@ export { BridgeUserAction, BridgeBackgroundAction, type TokenFeature, + type QuoteStreamCompleteData, type BridgeControllerGetStateAction, type BridgeControllerStateChangeEvent, } from './types'; @@ -79,6 +80,8 @@ export { BridgeAssetSchema, FeatureId, TokenFeatureType, + validateQuoteStreamComplete, + QuoteStreamCompleteReason, } from './utils/validators'; export { From e8be59ba2e55a2154307c0aa6ca2ddd9fee1639d Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Wed, 25 Mar 2026 15:37:00 -0400 Subject: [PATCH 04/12] chore: add default complete state --- packages/bridge-controller/src/constants/bridge.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/bridge-controller/src/constants/bridge.ts b/packages/bridge-controller/src/constants/bridge.ts index 0763d0e7d5c..8386adbbb42 100644 --- a/packages/bridge-controller/src/constants/bridge.ts +++ b/packages/bridge-controller/src/constants/bridge.ts @@ -91,6 +91,7 @@ export const DEFAULT_BRIDGE_CONTROLLER_STATE: BridgeControllerState = { assetExchangeRates: {}, minimumBalanceForRentExemptionInLamports: '0', tokenWarnings: [], + quoteStreamComplete: null, }; export const METABRIDGE_CHAIN_TO_ADDRESS_MAP: Record = { From 16608bca7a2f7bbc03c728b3e9a6300ac7b2ce27 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Wed, 25 Mar 2026 15:38:22 -0400 Subject: [PATCH 05/12] feat: handle complete event --- .../bridge-controller/src/bridge-controller.ts | 9 +++++++++ packages/bridge-controller/src/utils/fetch.ts | 16 ++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/packages/bridge-controller/src/bridge-controller.ts b/packages/bridge-controller/src/bridge-controller.ts index ff92d0254c4..75b8949072b 100644 --- a/packages/bridge-controller/src/bridge-controller.ts +++ b/packages/bridge-controller/src/bridge-controller.ts @@ -614,6 +614,8 @@ export class BridgeController extends StaticIntervalPollingController { + this.update((state) => { + state.quoteStreamComplete = data; + }); + }, onClose: async () => { // Wait for all pending appendFeesToQuotes operations to complete // before setting quotesLoadingStatus to FETCHED diff --git a/packages/bridge-controller/src/utils/fetch.ts b/packages/bridge-controller/src/utils/fetch.ts index 11091b651fa..5928000000a 100644 --- a/packages/bridge-controller/src/utils/fetch.ts +++ b/packages/bridge-controller/src/utils/fetch.ts @@ -14,6 +14,7 @@ import { validateQuoteResponse, validateSwapsTokenObject, validateTokenFeature, + validateQuoteStreamComplete, } from './validators'; import type { QuoteResponse, @@ -22,6 +23,7 @@ import type { QuoteRequest, BridgeAsset, TokenFeature, + QuoteStreamCompleteData, } from '../types'; export const getClientHeaders = ({ @@ -300,6 +302,7 @@ export const fetchAssetPrices = async ( * @param serverEventHandlers.onQuoteValidationFailure - The function to handle quote validation failures * @param serverEventHandlers.onValidQuoteReceived - The function to handle valid quotes * @param serverEventHandlers.onTokenWarning - The function to handle token warning events + * @param serverEventHandlers.onComplete - The function to handle the complete event emitted when the stream finishes * @param serverEventHandlers.onClose - The function to run when the stream is closed and there are no thrown errors * @param clientVersion - The client version for metrics (optional) * @returns A list of bridge tx quote promises @@ -316,6 +319,7 @@ export async function fetchBridgeQuoteStream( onQuoteValidationFailure: (validationFailures: string[]) => void; onValidQuoteReceived: (quotes: QuoteResponse) => Promise; onTokenWarning: (warning: TokenFeature) => void; + onComplete: (data: QuoteStreamCompleteData) => void; }, clientVersion?: string, ): Promise { @@ -374,6 +378,16 @@ export async function fetchBridgeQuoteStream( } }; + const onCompleteReceived = (data: unknown): void => { + try { + if (validateQuoteStreamComplete(data)) { + serverEventHandlers.onComplete(data); + } + } catch (error) { + console.warn('Quote stream complete validation failed', error); + } + }; + const onMessage = async ( data: Record, eventName?: string, @@ -383,6 +397,8 @@ export async function fetchBridgeQuoteStream( return await onQuoteReceived(data); case 'token_warning': return onTokenWarningReceived(data); + case 'complete': + return onCompleteReceived(data); default: return undefined; } From e86aeba8c7a2ec26fc20a0714bcf2877d9685081 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Wed, 25 Mar 2026 15:38:29 -0400 Subject: [PATCH 06/12] chore: changelog --- packages/bridge-controller/CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/bridge-controller/CHANGELOG.md b/packages/bridge-controller/CHANGELOG.md index b8e9948ab89..538dad91031 100644 --- a/packages/bridge-controller/CHANGELOG.md +++ b/packages/bridge-controller/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add `quoteStreamComplete` state field to `BridgeControllerState`, populated from the `complete` SSE event emitted by the quote stream ([#8306](https://github.com/MetaMask/core/pull/8306)) + - Exposes `QuoteStreamCompleteData` type and `validateQuoteStreamComplete` validator + - `quoteStreamComplete` is cleared at the start of each fetch and on `resetState` + ### Changed - Bump `@metamask/assets-controller` from `^3.1.0` to `^3.1.1` ([#8298](https://github.com/MetaMask/core/pull/8298)) From aeca4d72e4e204535afea99373005288cda5e178 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Wed, 25 Mar 2026 15:44:29 -0400 Subject: [PATCH 07/12] chore: tests --- .../bridge-controller.sse.test.ts.snap | 2 + .../src/bridge-controller.sse.test.ts | 139 +++++++++++++++++- .../src/utils/validators.test.ts | 66 ++++++++- packages/bridge-controller/tests/mock-sse.ts | 52 ++++++- 4 files changed, 256 insertions(+), 3 deletions(-) diff --git a/packages/bridge-controller/src/__snapshots__/bridge-controller.sse.test.ts.snap b/packages/bridge-controller/src/__snapshots__/bridge-controller.sse.test.ts.snap index b22c5426da0..5257395de2d 100644 --- a/packages/bridge-controller/src/__snapshots__/bridge-controller.sse.test.ts.snap +++ b/packages/bridge-controller/src/__snapshots__/bridge-controller.sse.test.ts.snap @@ -182,6 +182,7 @@ exports[`BridgeController SSE should rethrow error from server 1`] = ` "srcTokenAmount": "1000000000000000000", "walletAddress": "0x30E8ccaD5A980BDF30447f8c2C48e70989D9d294", }, + "quoteStreamComplete": null, "quotes": [], "quotesInitialLoadTime": null, "quotesLoadingStatus": 0, @@ -298,6 +299,7 @@ exports[`BridgeController SSE should trigger quote polling if request is valid 1 "srcTokenAmount": "1000000000000000000", "walletAddress": "0x30E8ccaD5A980BDF30447f8c2C48e70989D9d294", }, + "quoteStreamComplete": null, "quotes": [], "quotesInitialLoadTime": null, "quotesLoadingStatus": 0, diff --git a/packages/bridge-controller/src/bridge-controller.sse.test.ts b/packages/bridge-controller/src/bridge-controller.sse.test.ts index 257545b8a1a..89ab63e6d54 100644 --- a/packages/bridge-controller/src/bridge-controller.sse.test.ts +++ b/packages/bridge-controller/src/bridge-controller.sse.test.ts @@ -16,7 +16,10 @@ import * as balanceUtils from './utils/balance'; import { formatChainIdToDec } from './utils/caip-formatters'; import * as featureFlagUtils from './utils/feature-flags'; import * as fetchUtils from './utils/fetch'; -import { TokenFeatureType } from './utils/validators'; +import { + TokenFeatureType, + QuoteStreamCompleteReason, +} from './utils/validators'; import { flushPromises } from '../../../tests/helpers'; import mockBridgeQuotesErc20Erc20 from '../tests/mock-quotes-erc20-erc20.json'; import mockBridgeQuotesNativeErc20Eth from '../tests/mock-quotes-native-erc20-eth.json'; @@ -25,6 +28,7 @@ import { advanceToNthTimer, advanceToNthTimerThenFlush, mockSseEventSource, + mockSseEventSourceWithComplete, mockSseEventSourceWithMultipleDelays, mockSseEventSourceWithWarnings, mockSseServerError, @@ -198,6 +202,7 @@ describe('BridgeController SSE', function () { onQuoteValidationFailure: expect.any(Function), onValidQuoteReceived: expect.any(Function), onTokenWarning: expect.any(Function), + onComplete: expect.any(Function), onClose: expect.any(Function), }, '13.8.0', @@ -338,6 +343,7 @@ describe('BridgeController SSE', function () { onQuoteValidationFailure: expect.any(Function), onValidQuoteReceived: expect.any(Function), onTokenWarning: expect.any(Function), + onComplete: expect.any(Function), onClose: expect.any(Function), }, '13.8.0', @@ -472,6 +478,7 @@ describe('BridgeController SSE', function () { onQuoteValidationFailure: expect.any(Function), onValidQuoteReceived: expect.any(Function), onTokenWarning: expect.any(Function), + onComplete: expect.any(Function), onClose: expect.any(Function), }, '13.8.0', @@ -1106,6 +1113,7 @@ describe('BridgeController SSE', function () { onQuoteValidationFailure: expect.any(Function), onValidQuoteReceived: expect.any(Function), onTokenWarning: expect.any(Function), + onComplete: expect.any(Function), onClose: expect.any(Function), }, '13.8.0', @@ -1304,4 +1312,133 @@ describe('BridgeController SSE', function () { fakeTokenWarning, ]); }); + + it('should populate quoteStreamComplete from complete SSE event', async function () { + const mockComplete = { + quoteCount: 2, + hasQuotes: true, + reason: QuoteStreamCompleteReason.RETRY, + context: { source: 'bridge-api' }, + }; + mockFetchFn.mockImplementationOnce(async () => { + return mockSseEventSourceWithComplete( + mockBridgeQuotesNativeErc20 as QuoteResponse[], + [], + mockComplete, + ); + }); + + await bridgeController.updateBridgeQuoteRequestParams( + quoteRequest, + metricsContext, + ); + + expect(bridgeController.state.quoteStreamComplete).toBeNull(); + + jest.advanceTimersByTime(1000); + await advanceToNthTimerThenFlush(); + jest.advanceTimersByTime(5000); + await flushPromises(); + + expect(bridgeController.state.quoteStreamComplete).toStrictEqual( + mockComplete, + ); + expect(bridgeController.state.quotes.length).toBeGreaterThan(0); + }); + + it('should populate quoteStreamComplete with optional fields omitted', async function () { + const mockComplete = { + quoteCount: 0, + hasQuotes: false, + }; + mockFetchFn.mockImplementationOnce(async () => { + return mockSseEventSourceWithComplete([], [], mockComplete); + }); + + await bridgeController.updateBridgeQuoteRequestParams( + quoteRequest, + metricsContext, + ); + + jest.advanceTimersByTime(1000); + await advanceToNthTimerThenFlush(); + jest.advanceTimersByTime(5000); + await flushPromises(); + + expect(bridgeController.state.quoteStreamComplete).toStrictEqual( + mockComplete, + ); + }); + + it('should clear quoteStreamComplete on resetState', async function () { + const mockComplete = { + quoteCount: 2, + hasQuotes: true, + }; + mockFetchFn.mockImplementationOnce(async () => { + return mockSseEventSourceWithComplete( + mockBridgeQuotesNativeErc20 as QuoteResponse[], + [], + mockComplete, + ); + }); + + await bridgeController.updateBridgeQuoteRequestParams( + quoteRequest, + metricsContext, + ); + + jest.advanceTimersByTime(1000); + await advanceToNthTimerThenFlush(); + jest.advanceTimersByTime(5000); + await flushPromises(); + + expect(bridgeController.state.quoteStreamComplete).toStrictEqual( + mockComplete, + ); + + bridgeController.resetState(); + expect(bridgeController.state.quoteStreamComplete).toBeNull(); + }); + + it('should clear quoteStreamComplete at the start of each fetch', async function () { + const mockComplete = { + quoteCount: 2, + hasQuotes: true, + }; + mockFetchFn.mockImplementation(async () => { + return mockSseEventSourceWithComplete( + mockBridgeQuotesNativeErc20 as QuoteResponse[], + [], + mockComplete, + ); + }); + + await bridgeController.updateBridgeQuoteRequestParams( + quoteRequest, + metricsContext, + ); + + jest.advanceTimersByTime(1000); + await advanceToNthTimerThenFlush(); + jest.advanceTimersByTime(5000); + await flushPromises(); + + expect(bridgeController.state.quoteStreamComplete).toStrictEqual( + mockComplete, + ); + + // Trigger a second fetch — quoteStreamComplete should be cleared before the stream completes + jest.advanceTimersByTime(1000); + await advanceToNthTimerThenFlush(); + + expect(bridgeController.state.quoteStreamComplete).toBeNull(); + + jest.advanceTimersByTime(5000); + await flushPromises(); + + expect(bridgeController.state.quoteStreamComplete).toStrictEqual( + mockComplete, + ); + }); }); diff --git a/packages/bridge-controller/src/utils/validators.test.ts b/packages/bridge-controller/src/utils/validators.test.ts index da6ff91c0d2..0083201e359 100644 --- a/packages/bridge-controller/src/utils/validators.test.ts +++ b/packages/bridge-controller/src/utils/validators.test.ts @@ -1,6 +1,11 @@ import { is } from '@metamask/superstruct'; -import { validateFeatureFlagsResponse, IntentSchema } from './validators'; +import { + validateFeatureFlagsResponse, + validateQuoteStreamComplete, + QuoteStreamCompleteReason, + IntentSchema, +} from './validators'; describe('validators', () => { describe('validateFeatureFlagsResponse', () => { @@ -444,4 +449,63 @@ describe('validators', () => { ).toBe(true); }); }); + + describe('validateQuoteStreamComplete', () => { + it('accepts a valid complete event with all fields', () => { + expect( + validateQuoteStreamComplete({ + quoteCount: 3, + hasQuotes: true, + reason: QuoteStreamCompleteReason.RETRY, + context: { source: 'bridge-api' }, + }), + ).toBe(true); + }); + + it('accepts a valid complete event with only required fields', () => { + expect( + validateQuoteStreamComplete({ quoteCount: 0, hasQuotes: false }), + ).toBe(true); + }); + + it('accepts all defined reason values', () => { + for (const reason of Object.values(QuoteStreamCompleteReason)) { + expect( + validateQuoteStreamComplete({ + quoteCount: 1, + hasQuotes: true, + reason, + }), + ).toBe(true); + } + }); + + it('rejects an unknown reason string', () => { + expect(() => + validateQuoteStreamComplete({ + quoteCount: 1, + hasQuotes: true, + reason: 'UNKNOWN_REASON', + }), + ).toThrow('At path: reason'); + }); + + it('rejects data missing quoteCount', () => { + expect(() => validateQuoteStreamComplete({ hasQuotes: true })).toThrow( + 'At path: quoteCount', + ); + }); + + it('rejects data missing hasQuotes', () => { + expect(() => validateQuoteStreamComplete({ quoteCount: 1 })).toThrow( + 'At path: hasQuotes', + ); + }); + + it('rejects data with wrong type for quoteCount', () => { + expect(() => + validateQuoteStreamComplete({ quoteCount: 'three', hasQuotes: true }), + ).toThrow('At path: quoteCount'); + }); + }); }); diff --git a/packages/bridge-controller/tests/mock-sse.ts b/packages/bridge-controller/tests/mock-sse.ts index c48c8209de8..ecf7690e3d3 100644 --- a/packages/bridge-controller/tests/mock-sse.ts +++ b/packages/bridge-controller/tests/mock-sse.ts @@ -2,7 +2,12 @@ import { ReadableStream } from 'node:stream/web'; import { flushPromises } from '../../../tests/helpers'; -import type { QuoteResponse, TokenFeature, Trade } from '../src'; +import type { + QuoteResponse, + QuoteStreamCompleteData, + TokenFeature, + Trade, +} from '../src'; type MockSseResponse = { status: number; ok: boolean; body: ReadableStream }; @@ -138,6 +143,51 @@ export const mockSseEventSourceWithWarnings = ( }; }; +/** + * Simulates an SSE stream that emits quote, token_warning, and complete events + * + * @param mockQuotes - a list of quotes to stream + * @param mockWarnings - a list of token warnings to stream + * @param mockComplete - the complete event data to emit + * @param delay - the delay in milliseconds + * @returns a delayed stream of quotes, token warnings, and a complete event + */ +export const mockSseEventSourceWithComplete = ( + mockQuotes: QuoteResponse[], + mockWarnings: TokenFeature[], + mockComplete: QuoteStreamCompleteData, + delay: number = 3000, +): MockSseResponse => { + return { + status: 200, + ok: true, + body: new ReadableStream({ + start(controller): void { + setTimeout(() => { + let eventIndex = 0; + mockWarnings.forEach((warning) => { + emitLine(controller, `event: token_warning\n`); + // eslint-disable-next-line no-plusplus + emitLine(controller, `id: ${getEventId(eventIndex++)}\n`); + emitLine(controller, `data: ${JSON.stringify(warning)}\n\n`); + }); + mockQuotes.forEach((quote) => { + emitLine(controller, `event: quote\n`); + // eslint-disable-next-line no-plusplus + emitLine(controller, `id: ${getEventId(eventIndex++)}\n`); + emitLine(controller, `data: ${JSON.stringify(quote)}\n\n`); + }); + emitLine(controller, `event: complete\n`); + // eslint-disable-next-line no-plusplus + emitLine(controller, `id: ${getEventId(eventIndex++)}\n`); + emitLine(controller, `data: ${JSON.stringify(mockComplete)}\n\n`); + controller.close(); + }, delay); + }, + }), + }; +}; + /** * This simulates responses from the fetch function for unit tests * From 1ad19bb533399172c57c97792e26b8ceebc7ad71 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Wed, 25 Mar 2026 16:03:37 -0400 Subject: [PATCH 08/12] fix: tests --- .../src/__snapshots__/bridge-controller.test.ts.snap | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/bridge-controller/src/__snapshots__/bridge-controller.test.ts.snap b/packages/bridge-controller/src/__snapshots__/bridge-controller.test.ts.snap index a85a63acf59..5fb18da1a0c 100644 --- a/packages/bridge-controller/src/__snapshots__/bridge-controller.test.ts.snap +++ b/packages/bridge-controller/src/__snapshots__/bridge-controller.test.ts.snap @@ -20,6 +20,7 @@ exports[`BridgeController should handle errors from fetchBridgeQuotes 1`] = ` "srcTokenAmount": "991250000000000000", "walletAddress": "eip:id/id:id/0x123", }, + "quoteStreamComplete": null, "quotesInitialLoadTime": 10000, "quotesLoadingStatus": 1, "quotesRefreshCount": 1, @@ -47,6 +48,7 @@ exports[`BridgeController should handle errors from fetchBridgeQuotes 2`] = ` "srcTokenAmount": "991250000000000000", "walletAddress": "eip:id/id:id/0x123", }, + "quoteStreamComplete": null, "quotesInitialLoadTime": 10000, "quotesLoadingStatus": 1, "quotesRefreshCount": 1, @@ -809,6 +811,7 @@ exports[`BridgeController updateBridgeQuoteRequestParams should trigger quote po "srcTokenAmount": "10", "walletAddress": "0x123", }, + "quoteStreamComplete": null, "quotes": [], "quotesInitialLoadTime": null, "quotesLastFetched": null, @@ -840,6 +843,7 @@ exports[`BridgeController updateBridgeQuoteRequestParams should trigger quote po "srcTokenAmount": "10", "walletAddress": "0x123", }, + "quoteStreamComplete": null, "quotesInitialLoadTime": 10000, "quotesLoadingStatus": 1, "quotesRefreshCount": 1, From 54e3b0caa70784cb9376d92248113df135083b8d Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Wed, 25 Mar 2026 16:27:53 -0400 Subject: [PATCH 09/12] chore: add quoteStreamComplete to metadata --- packages/bridge-controller/src/bridge-controller.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/bridge-controller/src/bridge-controller.ts b/packages/bridge-controller/src/bridge-controller.ts index 75b8949072b..07895677836 100644 --- a/packages/bridge-controller/src/bridge-controller.ts +++ b/packages/bridge-controller/src/bridge-controller.ts @@ -146,6 +146,12 @@ const metadata: StateMetadata = { includeInDebugSnapshot: false, usedInUi: true, }, + quoteStreamComplete: { + includeInStateLogs: true, + persist: false, + includeInDebugSnapshot: false, + usedInUi: true, + }, }; /** From 1e0874301dca63b7ce28467f629a66a3cfb0850d Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Wed, 25 Mar 2026 16:36:18 -0400 Subject: [PATCH 10/12] fix: snapshots --- packages/bridge-controller/src/bridge-controller.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/bridge-controller/src/bridge-controller.test.ts b/packages/bridge-controller/src/bridge-controller.test.ts index 0aa2d6aeeb6..fdf271ecbfa 100644 --- a/packages/bridge-controller/src/bridge-controller.test.ts +++ b/packages/bridge-controller/src/bridge-controller.test.ts @@ -3469,6 +3469,7 @@ describe('BridgeController', function () { "quoteRequest": { "srcTokenAddress": "0x0000000000000000000000000000000000000000", }, + "quoteStreamComplete": null, "quotes": [], "quotesInitialLoadTime": null, "quotesLastFetched": null, @@ -3504,6 +3505,7 @@ describe('BridgeController', function () { "quoteRequest": { "srcTokenAddress": "0x0000000000000000000000000000000000000000", }, + "quoteStreamComplete": null, "quotes": [], "quotesInitialLoadTime": null, "quotesLastFetched": null, From 91f972087b5df615737c8f68667eb3899ebc56a9 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Wed, 25 Mar 2026 17:05:03 -0400 Subject: [PATCH 11/12] fix: prune supressions --- eslint-suppressions.json | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 23784193c68..d48af08d326 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -517,9 +517,6 @@ } }, "packages/bridge-controller/src/types.ts": { - "@typescript-eslint/naming-convention": { - "count": 12 - }, "@typescript-eslint/prefer-enum-initializers": { "count": 3 } @@ -604,9 +601,6 @@ "@typescript-eslint/explicit-function-return-type": { "count": 1 }, - "@typescript-eslint/naming-convention": { - "count": 1 - }, "id-length": { "count": 4 } @@ -1840,4 +1834,4 @@ "count": 1 } } -} +} \ No newline at end of file From 2c2da4c230e4fdecee467d75a2977399c1a644f9 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Wed, 25 Mar 2026 17:21:03 -0400 Subject: [PATCH 12/12] fix: lint --- eslint-suppressions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eslint-suppressions.json b/eslint-suppressions.json index d48af08d326..da08304271c 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -1834,4 +1834,4 @@ "count": 1 } } -} \ No newline at end of file +}