From c62f244e4a071dca7fa55a1e7f0f683cd687b6ca Mon Sep 17 00:00:00 2001 From: Julien Fontanel Date: Tue, 15 Sep 2026 14:17:21 +0200 Subject: [PATCH 1/9] feat: consume and return reserve value from the bridge API --- packages/bridge-controller/CHANGELOG.md | 6 +++++ .../coercers/quote-response-v1-to-v2.test.ts | 23 +++++++++++++++++++ .../src/coercers/quote-response-v1-to-v2.ts | 10 ++++++++ .../src/coercers/quote-response-v2-to-v1.ts | 3 +++ .../src/utils/quote-metadata/merge.test.ts | 21 +++++++++++++++++ .../quote-metadata/to-normalized-amounts.ts | 8 +++++++ .../bridge-controller/src/validators/quote.ts | 4 ++++ 7 files changed, 75 insertions(+) diff --git a/packages/bridge-controller/CHANGELOG.md b/packages/bridge-controller/CHANGELOG.md index 70d66764806..8ade4276ccf 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 optional `quote.feeData.reserve` on V2 quotes for native minimum-balance requirements + - Sibling of `feeData.network`, not a `FeeType`, so fee aggregators do not treat it as spendable + - Normalized via `toNormalizedAmounts`; preserved when coercing V1 ↔ V2 + ## [80.2.1] ### Changed diff --git a/packages/bridge-controller/src/coercers/quote-response-v1-to-v2.test.ts b/packages/bridge-controller/src/coercers/quote-response-v1-to-v2.test.ts index b38b530f5b9..f7f84ce3455 100644 --- a/packages/bridge-controller/src/coercers/quote-response-v1-to-v2.test.ts +++ b/packages/bridge-controller/src/coercers/quote-response-v1-to-v2.test.ts @@ -84,6 +84,29 @@ describe('quote-response-v2 migration', () => { expect(extractedMetadata).toStrictEqual(TEST_METADATA); }); + it('preserves backend network fees and native reserve from a V1-shaped response', () => { + const quoteResponse = structuredClone(quoteResponseV1WithMetadata); + const asset = { + assetId: quoteResponse.quote.srcAsset.assetId, + symbol: quoteResponse.quote.srcAsset.symbol, + name: quoteResponse.quote.srcAsset.name, + decimals: quoteResponse.quote.srcAsset.decimals, + }; + Object.assign(quoteResponse.quote.feeData, { + network: [{ amount: '2000', asset }], + reserve: [{ amount: '15000000', asset }], + }); + + const result = toQuoteResponseV2(quoteResponse); + + expect(result.quote.feeData.network).toStrictEqual([ + { amount: '2000', asset }, + ]); + expect(result.quote.feeData.reserve).toStrictEqual([ + { amount: '15000000', asset }, + ]); + }); + it('should return QuoteResponse with no normalized amounts and preserve metadata (V1 input)', () => { const quoteResponseV2 = mergeQuoteMetadata( toQuoteResponseV2(quoteResponseV1WithMetadata), diff --git a/packages/bridge-controller/src/coercers/quote-response-v1-to-v2.ts b/packages/bridge-controller/src/coercers/quote-response-v1-to-v2.ts index 17a27911ae4..3a6777224f7 100644 --- a/packages/bridge-controller/src/coercers/quote-response-v1-to-v2.ts +++ b/packages/bridge-controller/src/coercers/quote-response-v1-to-v2.ts @@ -77,6 +77,10 @@ const QuoteV2FromV1 = coerce(QuoteSchemaV2, QuoteSchema, (value) => { intent, ...restQuote } = value; + const migratedFeeData = feeData as typeof feeData & { + network?: Infer['feeData']['network']; + reserve?: Infer['feeData']['reserve']; + }; const srcAssetV2 = toBridgeAssetV2(srcAsset); @@ -124,6 +128,12 @@ const QuoteV2FromV1 = coerce(QuoteSchemaV2, QuoteSchema, (value) => { }, ], }), + ...(migratedFeeData.network?.length && { + network: migratedFeeData.network, + }), + ...(migratedFeeData.reserve?.length && { + reserve: migratedFeeData.reserve, + }), }, steps: steps?.map(toStepV2), ...restQuote, diff --git a/packages/bridge-controller/src/coercers/quote-response-v2-to-v1.ts b/packages/bridge-controller/src/coercers/quote-response-v2-to-v1.ts index b604fdfc2d0..858ff2f2ef5 100644 --- a/packages/bridge-controller/src/coercers/quote-response-v2-to-v1.ts +++ b/packages/bridge-controller/src/coercers/quote-response-v2-to-v1.ts @@ -118,6 +118,9 @@ const QuoteV1FromV2 = coerce(QuoteSchema, QuoteSchemaV2, (value) => { asset: toBridgeAssetV1(feeData[FeeType.TX_FEE][0].asset), }, }), + ...(feeData.reserve?.length && { + reserve: feeData.reserve, + }), }, ...(dest.walletAddress && /* istanbul ignore next */ { destWalletAddress: dest.walletAddress, diff --git a/packages/bridge-controller/src/utils/quote-metadata/merge.test.ts b/packages/bridge-controller/src/utils/quote-metadata/merge.test.ts index 820f77c0067..b01b7f3bad7 100644 --- a/packages/bridge-controller/src/utils/quote-metadata/merge.test.ts +++ b/packages/bridge-controller/src/utils/quote-metadata/merge.test.ts @@ -105,6 +105,27 @@ const legacyQuoteMetadata = { }, }; +describe('toNormalizedAmounts', () => { + it('normalizes a quote-carried native reserve', () => { + const quote = structuredClone(quoteResponseV2); + quote.quote.feeData.reserve = [ + { + amount: '15000000', + asset: { + assetId: 'stellar:pubnet/slip44:148', + symbol: 'XLM', + name: 'Stellar Lumens', + decimals: 7, + }, + }, + ]; + + expect( + toNormalizedAmounts(quote).quote.feeData.reserve?.[0].normalizedAmount, + ).toBe('1.5'); + }); +}); + describe('mergeQuoteMetadata', () => { // PHASE 1 it.each([ diff --git a/packages/bridge-controller/src/utils/quote-metadata/to-normalized-amounts.ts b/packages/bridge-controller/src/utils/quote-metadata/to-normalized-amounts.ts index 789459f4b26..1c7d8468719 100644 --- a/packages/bridge-controller/src/utils/quote-metadata/to-normalized-amounts.ts +++ b/packages/bridge-controller/src/utils/quote-metadata/to-normalized-amounts.ts @@ -39,6 +39,14 @@ export const toNormalizedAmounts = ( networkFee?.asset?.decimals, )?.toFixed(), })), + ...(feeData?.reserve && { + reserve: feeData.reserve.map((reserve) => ({ + normalizedAmount: calcNormalizedTokenAmount( + reserve?.amount, + reserve?.asset?.decimals, + )?.toFixed(), + })), + }), relayer: feeData?.[FeeType.RELAYER]?.map((relayerFee) => ({ normalizedAmount: calcNormalizedTokenAmount( relayerFee.amount, diff --git a/packages/bridge-controller/src/validators/quote.ts b/packages/bridge-controller/src/validators/quote.ts index 7514529689d..13618ca5689 100644 --- a/packages/bridge-controller/src/validators/quote.ts +++ b/packages/bridge-controller/src/validators/quote.ts @@ -182,6 +182,10 @@ export const QuoteSchemaV2 = intersection([ * The gas fees for the quote, excluding any provider or relayer fees */ [FeeType.NETWORK]: optional(array(AmountsAndAssetSchema)), + /** + * The native balance that must remain in the source account. + */ + reserve: optional(array(AmountsAndAssetSchema)), /** * The relayer or provider fees for the quote, */ From 06b88e235e93289e8ffede71932d5f7e21c2a37c Mon Sep 17 00:00:00 2001 From: Julien Fontanel Date: Tue, 15 Sep 2026 14:21:53 +0200 Subject: [PATCH 2/9] chore: update changelog --- packages/bridge-controller/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bridge-controller/CHANGELOG.md b/packages/bridge-controller/CHANGELOG.md index 8ade4276ccf..22e5ae3be89 100644 --- a/packages/bridge-controller/CHANGELOG.md +++ b/packages/bridge-controller/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Add optional `quote.feeData.reserve` on V2 quotes for native minimum-balance requirements +- Add optional `quote.feeData.reserve` on V2 quotes for native minimum-balance requirements ([#10241](https://github.com/MetaMask/core/pull/10241)) - Sibling of `feeData.network`, not a `FeeType`, so fee aggregators do not treat it as spendable - Normalized via `toNormalizedAmounts`; preserved when coercing V1 ↔ V2 From b2930ee9802e42b21172f080545242c9c2a036cf Mon Sep 17 00:00:00 2001 From: Julien Fontanel Date: Tue, 15 Sep 2026 15:25:36 +0200 Subject: [PATCH 3/9] fix: copilot's comment --- packages/bridge-controller/CHANGELOG.md | 2 +- .../src/utils/quote-metadata/merge.test.ts | 40 ++++++++++++------- .../src/utils/quote-metadata/merge.ts | 7 ++++ 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/packages/bridge-controller/CHANGELOG.md b/packages/bridge-controller/CHANGELOG.md index cb84ac9cc13..cc18629e443 100644 --- a/packages/bridge-controller/CHANGELOG.md +++ b/packages/bridge-controller/CHANGELOG.md @@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add optional `quote.feeData.reserve` on V2 quotes for native minimum-balance requirements ([#10241](https://github.com/MetaMask/core/pull/10241)) - Sibling of `feeData.network`, not a `FeeType`, so fee aggregators do not treat it as spendable - - Normalized via `toNormalizedAmounts`; preserved when coercing V1 ↔ V2 + - Normalized via `toNormalizedAmounts`; preserved when coercing V1 ↔ V2 and when merging quote metadata in `V1Data` ### Changed diff --git a/packages/bridge-controller/src/utils/quote-metadata/merge.test.ts b/packages/bridge-controller/src/utils/quote-metadata/merge.test.ts index eafcc538f97..0c6bb7e9d60 100644 --- a/packages/bridge-controller/src/utils/quote-metadata/merge.test.ts +++ b/packages/bridge-controller/src/utils/quote-metadata/merge.test.ts @@ -44,6 +44,19 @@ const EMPTY_QUOTE = { const quoteResponseV2 = getMockBridgeQuotesErc20Erc20V2()[0]; const normalizedAmounts = toNormalizedAmounts(quoteResponseV2); +const quoteResponseV2WithReserve = structuredClone(quoteResponseV2); +quoteResponseV2WithReserve.quote.feeData.reserve = [ + { + amount: '15000000', + asset: { + assetId: 'stellar:pubnet/slip44:148', + symbol: 'XLM', + name: 'Stellar Lumens', + decimals: 7, + }, + }, +]; + const v2PartialMetadata = { quote: { feeData: { @@ -107,21 +120,9 @@ const legacyQuoteMetadata = { describe('toNormalizedAmounts', () => { it('normalizes a quote-carried native reserve', () => { - const quote = structuredClone(quoteResponseV2); - quote.quote.feeData.reserve = [ - { - amount: '15000000', - asset: { - assetId: 'stellar:pubnet/slip44:148', - symbol: 'XLM', - name: 'Stellar Lumens', - decimals: 7, - }, - }, - ]; - expect( - toNormalizedAmounts(quote).quote.feeData.reserve?.[0].normalizedAmount, + toNormalizedAmounts(quoteResponseV2WithReserve).quote?.feeData + ?.reserve?.[0]?.normalizedAmount, ).toBe('1.5'); }); }); @@ -189,6 +190,17 @@ describe('mergeQuoteMetadata', () => { quoteMetadata: { b: 2 } as QuoteMetadata, mergedQuote: { a: 1, b: 2, ...EMPTY_QUOTE }, }, + { + title: 'preserves quote-carried native reserve', + quoteResponse: quoteResponseV2WithReserve, + quoteMetadata: {}, + mergedQuote: merge( + {}, + EMPTY_QUOTE, + quoteResponseV2WithReserve, + toNormalizedAmounts(quoteResponseV2WithReserve), + ), + }, ])( 'merged quote $title (Phase 1)', ({ quoteResponse, quoteMetadata, mergedQuote }) => { diff --git a/packages/bridge-controller/src/utils/quote-metadata/merge.ts b/packages/bridge-controller/src/utils/quote-metadata/merge.ts index 5d55db11824..785ded0a81f 100644 --- a/packages/bridge-controller/src/utils/quote-metadata/merge.ts +++ b/packages/bridge-controller/src/utils/quote-metadata/merge.ts @@ -64,6 +64,12 @@ export function mergeQuoteMetadata( metabridge: feeData?.metabridge, }); + // Native reserve is not a FeeType and is not reconstructed from legacy + // QuoteMetadata, so it must survive sanitization. + const reserveData = includeIfTruthy(feeData?.reserve?.[0], { + reserve: feeData?.reserve, + }); + const priceImpactData = priceData?.priceImpact?.amount && { priceData: { priceImpact: { @@ -78,6 +84,7 @@ export function mergeQuoteMetadata( feeData: { ...(metabridgeFeeData ?? {}), ...(txFeeData ?? {}), + ...(reserveData ?? {}), }, ...(priceImpactData ?? {}), }, From 15c1ee8b051d0b310140703b0b3ca0b9378a9551 Mon Sep 17 00:00:00 2001 From: Julien Fontanel Date: Tue, 15 Sep 2026 15:49:54 +0200 Subject: [PATCH 4/9] fix: cursor's comment --- packages/bridge-controller/CHANGELOG.md | 1 + .../coercers/quote-response-v2-to-v1.test.ts | 22 +++++++++++++++++++ .../bridge-controller/src/validators/quote.ts | 5 +++++ 3 files changed, 28 insertions(+) diff --git a/packages/bridge-controller/CHANGELOG.md b/packages/bridge-controller/CHANGELOG.md index cc18629e443..f8b21d4d6dc 100644 --- a/packages/bridge-controller/CHANGELOG.md +++ b/packages/bridge-controller/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add optional `quote.feeData.reserve` on V2 quotes for native minimum-balance requirements ([#10241](https://github.com/MetaMask/core/pull/10241)) - Sibling of `feeData.network`, not a `FeeType`, so fee aggregators do not treat it as spendable - Normalized via `toNormalizedAmounts`; preserved when coercing V1 ↔ V2 and when merging quote metadata in `V1Data` + - Optional on V1 `QuoteSchema` as well so `toQuoteResponseV1` does not strip it ### Changed diff --git a/packages/bridge-controller/src/coercers/quote-response-v2-to-v1.test.ts b/packages/bridge-controller/src/coercers/quote-response-v2-to-v1.test.ts index d59dd70abf1..300cad66e13 100644 --- a/packages/bridge-controller/src/coercers/quote-response-v2-to-v1.test.ts +++ b/packages/bridge-controller/src/coercers/quote-response-v2-to-v1.test.ts @@ -215,6 +215,28 @@ describe('quote-response-v1 compatibility', () => { expect(quoteResponseV1).toStrictEqual(quoteResponse); }); + it('preserves native reserve from a V2 quote', () => { + const quoteResponseV2 = structuredClone( + toQuoteResponseV2(mockBridgeQuotesErc20Erc20V1[0]), + ); + const reserve = [ + { + amount: '15000000', + asset: { + assetId: 'stellar:pubnet/slip44:148' as const, + symbol: 'XLM', + name: 'Stellar Lumens', + decimals: 7, + }, + }, + ]; + quoteResponseV2.quote.feeData.reserve = reserve; + + expect( + toQuoteResponseV1(quoteResponseV2).quote.feeData.reserve, + ).toStrictEqual(reserve); + }); + it('should return a valid QuoteResponseV1 with V2 input (remove metadata)', () => { const quoteResponseV1WithMetadata = { ...mockBridgeQuotesErc20Erc20V1[0], diff --git a/packages/bridge-controller/src/validators/quote.ts b/packages/bridge-controller/src/validators/quote.ts index 13618ca5689..495367fd44c 100644 --- a/packages/bridge-controller/src/validators/quote.ts +++ b/packages/bridge-controller/src/validators/quote.ts @@ -96,6 +96,11 @@ export const QuoteSchema = intersection([ [FeeType.TX_FEE]: optional( intersection([FeeDataSchema, TxFeeGasLimitsSchema]), ), + /** + * Native balance that must remain in the source account after execution. + * Copied through from V2; not a FeeType. + */ + reserve: optional(array(AmountsAndAssetSchema)), }), bridgeId: string(), bridges: array(string()), From 9bf25ceeceb7c31ae2bc18a60b9c7e93cd0750cd Mon Sep 17 00:00:00 2001 From: Julien Fontanel Date: Tue, 15 Sep 2026 16:00:39 +0200 Subject: [PATCH 5/9] fix: lint --- .../utils/quote-metadata/calculators.test.ts | 37 +++++++++++++++++++ .../src/utils/quote-metadata/calculators.ts | 4 +- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/packages/bridge-controller/src/utils/quote-metadata/calculators.test.ts b/packages/bridge-controller/src/utils/quote-metadata/calculators.test.ts index b29c8df1b6b..02bb0d1da47 100644 --- a/packages/bridge-controller/src/utils/quote-metadata/calculators.test.ts +++ b/packages/bridge-controller/src/utils/quote-metadata/calculators.test.ts @@ -208,6 +208,43 @@ describe('Quote Metadata Utils', () => { expect(result.usd).toBeUndefined(); }); + it('does not add quote-carried native reserve to sent amount', () => { + const mockQuote = getMockBridgeQuotesErc20Erc20V1({ + quote: { + srcTokenAmount: '1000000000', + srcAsset: { + decimals: 6, + assetId: + 'eip155:10/erc20:0x0b2c639c533813f4aa9d7837caf62653d097ff85', + }, + feeData: { + metabridge: { + amount: '100000000', + asset: { + assetId: + 'eip155:10/erc20:0x0b2c639c533813f4aa9d7837caf62653d097ff85', + }, + }, + reserve: [ + { + amount: '15000000', + asset: { + assetId: 'stellar:pubnet/slip44:148' as const, + symbol: 'XLM', + name: 'Stellar Lumens', + decimals: 7, + }, + }, + ], + }, + }, + })[0].quote; + + const result = calcSentAmount(mockQuote, {}); + + expect(result.amount).toBe('1100'); + }); + it('should handle zero values', () => { const zeroQuote = getMockBridgeQuotesErc20Erc20V1({ quote: { diff --git a/packages/bridge-controller/src/utils/quote-metadata/calculators.ts b/packages/bridge-controller/src/utils/quote-metadata/calculators.ts index ae15425e7d3..00533a16de9 100644 --- a/packages/bridge-controller/src/utils/quote-metadata/calculators.ts +++ b/packages/bridge-controller/src/utils/quote-metadata/calculators.ts @@ -71,10 +71,12 @@ export const calcSentAmount = ( // already baked in. Adding feeData fees on top would double-count them. // For conventional swaps, srcTokenAmount is the net routing amount (fees // excluded), so the src-token fees must be added to get the wallet deduction. + // `reserve` is not a FeeType; omitting it keeps it out of sent-amount. + const { reserve: _reserve, ...spendableFeeData } = feeData; const sentAmount = intent || isQuoteV2 ? new BigNumber(srcTokenAmount) - : Object.values(feeData) + : Object.values(spendableFeeData) .filter( (fee) => fee?.amount && From 5d6c3ed48058911a995a3237cfd2d92e40c6515c Mon Sep 17 00:00:00 2001 From: Julien Fontanel Date: Tue, 15 Sep 2026 16:11:04 +0200 Subject: [PATCH 6/9] fix: ingored test fixture --- .../src/utils/quote-metadata/calculators.test.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/bridge-controller/src/utils/quote-metadata/calculators.test.ts b/packages/bridge-controller/src/utils/quote-metadata/calculators.test.ts index 02bb0d1da47..02804ecda5b 100644 --- a/packages/bridge-controller/src/utils/quote-metadata/calculators.test.ts +++ b/packages/bridge-controller/src/utils/quote-metadata/calculators.test.ts @@ -229,10 +229,8 @@ describe('Quote Metadata Utils', () => { { amount: '15000000', asset: { - assetId: 'stellar:pubnet/slip44:148' as const, - symbol: 'XLM', - name: 'Stellar Lumens', - decimals: 7, + assetId: + 'eip155:10/erc20:0x0b2c639c533813f4aa9d7837caf62653d097ff85' as const, }, }, ], From f240b9cc93a5c4e749a4b260d20b31a5c32cb343 Mon Sep 17 00:00:00 2001 From: Julien Fontanel Date: Tue, 15 Sep 2026 16:29:02 +0200 Subject: [PATCH 7/9] chore: more copilot's fixing --- .../utils/quote-metadata/calculators.test.ts | 3 ++ .../src/utils/quote-metadata/merge.test.ts | 25 +++++++++++++++++ .../quote-metadata/to-currency-values.ts | 28 +++++++++++++------ 3 files changed, 47 insertions(+), 9 deletions(-) diff --git a/packages/bridge-controller/src/utils/quote-metadata/calculators.test.ts b/packages/bridge-controller/src/utils/quote-metadata/calculators.test.ts index 02804ecda5b..db12004fe9b 100644 --- a/packages/bridge-controller/src/utils/quote-metadata/calculators.test.ts +++ b/packages/bridge-controller/src/utils/quote-metadata/calculators.test.ts @@ -231,6 +231,9 @@ describe('Quote Metadata Utils', () => { asset: { assetId: 'eip155:10/erc20:0x0b2c639c533813f4aa9d7837caf62653d097ff85' as const, + symbol: 'USDC', + name: 'USD Coin', + decimals: 6, }, }, ], diff --git a/packages/bridge-controller/src/utils/quote-metadata/merge.test.ts b/packages/bridge-controller/src/utils/quote-metadata/merge.test.ts index 0c6bb7e9d60..7b0260ce98b 100644 --- a/packages/bridge-controller/src/utils/quote-metadata/merge.test.ts +++ b/packages/bridge-controller/src/utils/quote-metadata/merge.test.ts @@ -1,3 +1,4 @@ +import { BigNumber } from 'bignumber.js'; import { merge } from 'lodash-es'; import { getMockBridgeQuotesErc20Erc20V2 } from '../../../tests/mock-quotes-erc20-erc20.js'; @@ -8,6 +9,7 @@ import { } from '../../index.js'; import type { QuoteResponse } from '../../validators/quote-response.js'; import { mergeQuoteMetadata } from './merge.js'; +import { toCurrencyValues } from './to-currency-values.js'; import { toNormalizedAmounts } from './to-normalized-amounts.js'; import { QuoteMetadataMigrationPhase } from './types.js'; import type { QuoteMetadata } from './types.js'; @@ -127,6 +129,29 @@ describe('toNormalizedAmounts', () => { }); }); +describe('toCurrencyValues', () => { + it('derives fiat for a quote-carried native reserve without treating it as a FeeType', () => { + const quote = structuredClone(quoteResponseV2WithReserve); + quote.quote.feeData.reserve = [ + { + amount: '15000000', + usd: '1.5', + asset: { + assetId: 'stellar:pubnet/slip44:148' as const, + symbol: 'XLM', + name: 'Stellar Lumens', + decimals: 7, + }, + }, + ]; + + expect( + toCurrencyValues(quote, new BigNumber(2)).quote?.feeData?.reserve?.[0] + ?.valueInCurrency, + ).toBe('3'); + }); +}); + describe('mergeQuoteMetadata', () => { // PHASE 1 it.each([ diff --git a/packages/bridge-controller/src/utils/quote-metadata/to-currency-values.ts b/packages/bridge-controller/src/utils/quote-metadata/to-currency-values.ts index c5b511c3fe5..84b2343f516 100644 --- a/packages/bridge-controller/src/utils/quote-metadata/to-currency-values.ts +++ b/packages/bridge-controller/src/utils/quote-metadata/to-currency-values.ts @@ -59,16 +59,26 @@ export const toCurrencyValues = ( }, feeData: feeData && - Object.fromEntries( - Object.values(FeeType) - .filter((feeType) => feeData[feeType]) - .map((feeType) => [ - feeType, - feeData[feeType]?.map((fee) => - toCurrency(fee, usdToFiatExchangeRate), + ({ + ...Object.fromEntries( + Object.values(FeeType) + .filter((feeType) => feeData[feeType]) + .map((feeType) => [ + feeType, + feeData[feeType]?.map((fee) => + toCurrency(fee, usdToFiatExchangeRate), + ), + ]), + ), + ...(feeData.reserve && { + reserve: feeData.reserve + .map((reserve) => toCurrency(reserve, usdToFiatExchangeRate)) + .filter( + (value): value is NonNullable => + value !== undefined, ), - ]), - ), + }), + } as DeepPartial), ...((priceImpactFiat ?? adjustedReturnFiat ?? costFiat) && { priceData: { ...(priceImpactFiat && { From dc1b1a4a975565c59756962f89c17957f840f58a Mon Sep 17 00:00:00 2001 From: Julink Date: Tue, 15 Sep 2026 16:47:49 +0200 Subject: [PATCH 8/9] Refactor reserve mapping to handle undefined values Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../src/utils/quote-metadata/to-currency-values.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/bridge-controller/src/utils/quote-metadata/to-currency-values.ts b/packages/bridge-controller/src/utils/quote-metadata/to-currency-values.ts index 84b2343f516..0e8d93af497 100644 --- a/packages/bridge-controller/src/utils/quote-metadata/to-currency-values.ts +++ b/packages/bridge-controller/src/utils/quote-metadata/to-currency-values.ts @@ -72,10 +72,9 @@ export const toCurrencyValues = ( ), ...(feeData.reserve && { reserve: feeData.reserve - .map((reserve) => toCurrency(reserve, usdToFiatExchangeRate)) - .filter( - (value): value is NonNullable => - value !== undefined, + .map( + (reserve) => + toCurrency(reserve, usdToFiatExchangeRate) ?? {}, ), }), } as DeepPartial), From 8d1cbbbde5c224370e0e73b32d548cfdde0bfa88 Mon Sep 17 00:00:00 2001 From: Julien Fontanel Date: Tue, 15 Sep 2026 16:52:23 +0200 Subject: [PATCH 9/9] fix: lint --- .../src/utils/quote-metadata/to-currency-values.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/bridge-controller/src/utils/quote-metadata/to-currency-values.ts b/packages/bridge-controller/src/utils/quote-metadata/to-currency-values.ts index 0e8d93af497..2c889ee2e12 100644 --- a/packages/bridge-controller/src/utils/quote-metadata/to-currency-values.ts +++ b/packages/bridge-controller/src/utils/quote-metadata/to-currency-values.ts @@ -71,11 +71,9 @@ export const toCurrencyValues = ( ]), ), ...(feeData.reserve && { - reserve: feeData.reserve - .map( - (reserve) => - toCurrency(reserve, usdToFiatExchangeRate) ?? {}, - ), + reserve: feeData.reserve.map( + (reserve) => toCurrency(reserve, usdToFiatExchangeRate) ?? {}, + ), }), } as DeepPartial), ...((priceImpactFiat ?? adjustedReturnFiat ?? costFiat) && {