Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/bridge-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/
Expand Down
35 changes: 35 additions & 0 deletions packages/bridge-controller/src/utils/quote.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
25 changes: 16 additions & 9 deletions packages/bridge-controller/src/utils/quote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
Loading