From aa1c5cd3773929c42ab0b92417b9a4fcc3e47395 Mon Sep 17 00:00:00 2001 From: Satyajeet Kolhapure Date: Mon, 18 May 2026 17:02:12 +0100 Subject: [PATCH 1/2] fix: avoided fees in intent based swaps sent amount calculation --- .../bridge-controller/src/utils/quote.test.ts | 35 +++++++++++++++++++ packages/bridge-controller/src/utils/quote.ts | 25 ++++++++----- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/packages/bridge-controller/src/utils/quote.test.ts b/packages/bridge-controller/src/utils/quote.test.ts index 821c86c4e1..c6435f6cd6 100644 --- a/packages/bridge-controller/src/utils/quote.test.ts +++ b/packages/bridge-controller/src/utils/quote.test.ts @@ -254,6 +254,41 @@ describe('Quote Metadata Utils', () => { expect(result.valueInCurrency).toBe('2.2'); expect(result.usd).toBe('1.65'); }); + + it('should not add feeData fees for intent-based quotes', () => { + // For intent-based swaps (e.g. CoW Protocol), srcTokenAmount is already + // the total fixed commitment including protocol fees. Adding feeData fees + // on top would double-count them. + const intentQuote = { + srcTokenAmount: '10000000', // 10 USDT (6 decimals), fee already included + srcAsset: { + decimals: 6, + assetId: 'eip155:1/erc20:0xdAC17F958D2ee523a2206206994597C13D831ec7', + }, + feeData: { + metabridge: { + amount: '500000', // 0.5 USDT protocol fee — already inside srcTokenAmount + asset: { + assetId: + 'eip155:1/erc20:0xdAC17F958D2ee523a2206206994597C13D831ec7', + address: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + decimals: 6, + }, + }, + }, + intent: { protocol: 'cow', order: {} }, + } as unknown as Quote; + + const result = calcSentAmount(intentQuote, { + exchangeRate: '1', + usdExchangeRate: '1', + }); + + // Should be exactly 10 USDT — not 10.5 (which would double-count the fee) + expect(result.amount).toBe('10'); + expect(result.valueInCurrency).toBe('10'); + expect(result.usd).toBe('10'); + }); }); describe('calcNonEvmTotalNetworkFee', () => { diff --git a/packages/bridge-controller/src/utils/quote.ts b/packages/bridge-controller/src/utils/quote.ts index f68debdb51..8022ab6e95 100644 --- a/packages/bridge-controller/src/utils/quote.ts +++ b/packages/bridge-controller/src/utils/quote.ts @@ -141,17 +141,24 @@ export const calcToAmount = ( }; export const calcSentAmount = ( - { srcTokenAmount, srcAsset, feeData }: Quote, + { srcTokenAmount, srcAsset, feeData, intent }: Quote, { exchangeRate, usdExchangeRate }: ExchangeRate, ) => { - // Find all fees that will be taken from the src token - const srcTokenFees = Object.values(feeData).filter( - (fee) => fee && fee.amount && fee.asset?.assetId === srcAsset.assetId, - ); - const sentAmount = srcTokenFees.reduce( - (acc, { amount }) => acc.plus(amount), - new BigNumber(srcTokenAmount), - ); + // For intent-based swaps (e.g. CoW Protocol), srcTokenAmount is the total + // fixed commitment the user makes to the protocol — the protocol fee is + // 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. + const sentAmount = intent + ? new BigNumber(srcTokenAmount) + : Object.values(feeData) + .filter( + (fee) => fee && fee.amount && fee.asset?.assetId === srcAsset.assetId, + ) + .reduce( + (acc, { amount }) => acc.plus(amount), + new BigNumber(srcTokenAmount), + ); const normalizedSentAmount = calcTokenAmount(sentAmount, srcAsset.decimals); return { amount: normalizedSentAmount.toString(), From 4babdf89eec28d508c72f390967d01e9fbec6664 Mon Sep 17 00:00:00 2001 From: Satyajeet Kolhapure Date: Tue, 19 May 2026 17:35:11 +0100 Subject: [PATCH 2/2] chore: added changelog entry --- packages/bridge-controller/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/bridge-controller/CHANGELOG.md b/packages/bridge-controller/CHANGELOG.md index 837a0704c4..58ec18a9b6 100644 --- a/packages/bridge-controller/CHANGELOG.md +++ b/packages/bridge-controller/CHANGELOG.md @@ -14,6 +14,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - support transaction batch data fetching with the new `updateBatchSellTrades` handler. Clients will need to call this whenever the recommended quotes update - implement `selectBatchSellTrades` selector which returns whether a batch is submittable, and the `totalNetworkFee` provided by the `obtainGaslessBatch` endpoint and its converted values +### Fixed + +- Fix `calcSentAmount` double-counting fees for intent-based swap quotes ([#8845](https://github.com/MetaMask/core/pull/8845)) + ### Changed - **BREAKING**: Narrow TxData validation from generic string to Hex ([#8805](https://github.com/MetaMask/core/pull/