From a568e245cee452e42ef44783523f15f7d2e4c70b Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Thu, 16 Apr 2026 14:30:42 +0100 Subject: [PATCH 1/3] fix(transaction-pay-controller): stop adding subsidized fee to target amount Relay now returns currencyOut as the full amount the user receives, with subsidized fees no longer deducted from the output. Our code was adding the subsidized fee back to the target amount for EXACT_INPUT trade types, which now double-counts the fee. --- .../src/strategy/relay/relay-quotes.test.ts | 62 +------------------ .../src/strategy/relay/relay-quotes.ts | 16 +---- 2 files changed, 2 insertions(+), 76 deletions(-) diff --git a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts index a264d75a73e..e5e577055e1 100644 --- a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts +++ b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts @@ -2402,7 +2402,7 @@ describe('Relay Quotes Utils', () => { }); }); - it('adds subsidized fee to target amount fiat values when trade type is EXACT_INPUT', async () => { + it('does not add subsidized fee to target amount', async () => { const quoteMock = cloneDeep(QUOTE_MOCK); quoteMock.fees.subsidized = { amount: '500000000000000', @@ -2426,66 +2426,6 @@ describe('Relay Quotes Utils', () => { transaction: TRANSACTION_META_MOCK, }); - expect(result[0].targetAmount).toStrictEqual({ - usd: '1.73', - fiat: '3.46', - }); - }); - - it('uses amountFormatted for subsidized fee when fee token is a stablecoin', async () => { - const quoteMock = cloneDeep(QUOTE_MOCK); - quoteMock.fees.subsidized = { - amount: '500000', - amountFormatted: '0.50', - amountUsd: '0.49', - currency: { - address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' as Hex, - chainId: 1, - decimals: 6, - }, - minimumAmount: '500000', - }; - - successfulFetchMock.mockResolvedValue({ - json: async () => quoteMock, - } as never); - - const result = await getRelayQuotes({ - messenger, - requests: [{ ...QUOTE_REQUEST_MOCK, isMaxAmount: true }], - transaction: TRANSACTION_META_MOCK, - }); - - expect(result[0].targetAmount).toStrictEqual({ - usd: '1.73', - fiat: '3.46', - }); - }); - - it('does not add subsidized fee to target amount when trade type is not EXACT_INPUT', async () => { - const quoteMock = cloneDeep(QUOTE_MOCK); - quoteMock.fees.subsidized = { - amount: '500000000000000', - amountFormatted: '0.0005', - amountUsd: '0.50', - currency: { - address: '0xdef' as Hex, - chainId: 1, - decimals: 18, - }, - minimumAmount: '500000000000000', - }; - - successfulFetchMock.mockResolvedValue({ - json: async () => quoteMock, - } as never); - - const result = await getRelayQuotes({ - messenger, - requests: [QUOTE_REQUEST_MOCK], - transaction: TRANSACTION_META_MOCK, - }); - expect(result[0].targetAmount).toStrictEqual({ usd: '1.23', fiat: '2.46', diff --git a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts index 976b9f4f633..e8f04bb3d9a 100644 --- a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts +++ b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts @@ -471,24 +471,10 @@ async function normalizeQuote( request.targetTokenAddress, ); - const additionalTargetAmountUsd = - quote.request.tradeType === 'EXACT_INPUT' - ? subsidizedFeeUsd - : new BigNumber(0); - - if (additionalTargetAmountUsd.gt(0)) { - log( - 'Including subsidized fee in target amount', - additionalTargetAmountUsd.toString(10), - ); - } - - const baseTargetAmountUsd = isTargetStablecoin + const targetAmountUsd = isTargetStablecoin ? new BigNumber(currencyOut.amountFormatted) : new BigNumber(currencyOut.amountUsd); - const targetAmountUsd = baseTargetAmountUsd.plus(additionalTargetAmountUsd); - const targetAmount = getFiatValueFromUsd(targetAmountUsd, usdToFiatRate); const metamask: RelayQuoteMetamask = { From 0c6fe54f15954315cff337df1bc0e554077f157f Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Thu, 16 Apr 2026 14:32:35 +0100 Subject: [PATCH 2/3] fix: add changelog entry for subsidized fee fix --- packages/transaction-pay-controller/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/transaction-pay-controller/CHANGELOG.md b/packages/transaction-pay-controller/CHANGELOG.md index d089f19cda1..b5faa90c035 100644 --- a/packages/transaction-pay-controller/CHANGELOG.md +++ b/packages/transaction-pay-controller/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Resolve correct `networkClientId` for source chain in Relay execute flow ([#8492](https://github.com/MetaMask/core/pull/8492)) +- Stop double-counting subsidized fees in Relay quote target amounts ([#8488](https://github.com/MetaMask/core/pull/8488)) ## [19.2.0] From 7a08f64aa0bcb50fbf38e22abf5391eb36005682 Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Thu, 16 Apr 2026 14:43:59 +0100 Subject: [PATCH 3/3] fix: restore stablecoin branch coverage for subsidized fee --- .../src/strategy/relay/relay-quotes.test.ts | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts index e5e577055e1..4432974b027 100644 --- a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts +++ b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts @@ -1779,6 +1779,36 @@ describe('Relay Quotes Utils', () => { }); }); + it('uses amountFormatted for subsidized fee when fee token is a stablecoin', async () => { + const quoteMock = cloneDeep(QUOTE_MOCK); + quoteMock.fees.subsidized = { + amount: '500000', + amountFormatted: '0.50', + amountUsd: '0.49', + currency: { + address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' as Hex, + chainId: 1, + decimals: 6, + }, + minimumAmount: '500000', + }; + + successfulFetchMock.mockResolvedValue({ + json: async () => quoteMock, + } as never); + + const result = await getRelayQuotes({ + messenger, + requests: [QUOTE_REQUEST_MOCK], + transaction: TRANSACTION_META_MOCK, + }); + + expect(result[0].fees.provider).toStrictEqual({ + usd: '0', + fiat: '0', + }); + }); + it('includes dust in quote', async () => { successfulFetchMock.mockResolvedValue({ json: async () => QUOTE_MOCK,