diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 39e0bafceac7..953e042d5cca 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -82,6 +82,9 @@ "affirmAgree": { "message": "I Agree" }, + "aggregatorFeeCost": { + "message": "Aggregator network fee" + }, "alertDisableTooltip": { "message": "This can be changed in \"Settings > Alerts\"" }, @@ -129,6 +132,9 @@ "message": "MetaMask", "description": "The name of the application" }, + "approvalAndAggregatorTxFeeCost": { + "message": "Approval and aggregator network fee" + }, "approvalTxGasCost": { "message": "Approval Tx Gas Cost" }, @@ -1685,9 +1691,6 @@ "swapFinalizing": { "message": "Finalizing..." }, - "swapGasFeeSummary": { - "message": "The gas fee covers the cost of processing your swap and storing it on the Ethereum network. MetaMask does not profit from this fee." - }, "swapGetQuotes": { "message": "Get quotes" }, @@ -1736,6 +1739,9 @@ "message": "$1 quotes available", "description": "$1 is the number of quotes that the user can select from when opening the list of quotes on the 'view quote' screen" }, + "swapNetworkFeeSummary": { + "message": "The network fee covers the cost of processing your swap and storing it on the Ethereum network. MetaMask does not profit from this fee." + }, "swapNewQuoteIn": { "message": "New quotes in $1", "description": "Tells the user the amount of time until the currently displayed quotes are update. $1 is a time that is counting down from 1:00 to 0:00" diff --git a/ui/app/pages/swaps/awaiting-swap/awaiting-swap.js b/ui/app/pages/swaps/awaiting-swap/awaiting-swap.js index ba123be40cd3..d44348424500 100644 --- a/ui/app/pages/swaps/awaiting-swap/awaiting-swap.js +++ b/ui/app/pages/swaps/awaiting-swap/awaiting-swap.js @@ -31,7 +31,7 @@ import { import { SUBMITTED_STATUS } from '../../../helpers/constants/transactions' import { ASSET_ROUTE, DEFAULT_ROUTE } from '../../../helpers/constants/routes' -import { getRenderableGasFeesForQuote } from '../swaps.util' +import { getRenderableNetworkFeesForQuote } from '../swaps.util' import SwapsFooter from '../swaps-footer' import SwapFailureIcon from './swap-failure-icon' import SwapSuccessIcon from './swap-success-icon' @@ -70,8 +70,17 @@ export default function AwaitingSwap ({ let feeinFiat if (usedQuote && tradeTxParams) { - const renderableGasFees = getRenderableGasFeesForQuote(usedQuote.gasEstimateWithRefund || usedQuote.averageGas, approveTxParams?.gas || '0x0', tradeTxParams.gasPrice, currentCurrency, conversionRate) - feeinFiat = renderableGasFees.feeinFiat?.slice(1) + const renderableNetworkFees = getRenderableNetworkFeesForQuote( + usedQuote.gasEstimateWithRefund || usedQuote.averageGas, + approveTxParams?.gas || '0x0', + tradeTxParams.gasPrice, + currentCurrency, + conversionRate, + tradeTxParams.value, + sourceTokenInfo?.symbol, + usedQuote.sourceAmount, + ) + feeinFiat = renderableNetworkFees.feeinFiat?.slice(1) } const quotesExpiredEvent = useNewMetricEvent({ diff --git a/ui/app/pages/swaps/fee-card/fee-card.js b/ui/app/pages/swaps/fee-card/fee-card.js index 67f2ee807066..5ff166ef2f4b 100644 --- a/ui/app/pages/swaps/fee-card/fee-card.js +++ b/ui/app/pages/swaps/fee-card/fee-card.js @@ -26,7 +26,7 @@ export default function FeeCard ({ position="top" contentText={( <> -
{ t('swapGasFeeSummary') }
+{ t('swapNetworkFeeSummary') }
{ t('swapEstimatedNetworkFeeSummary', [ { t('swapEstimatedNetworkFee') } diff --git a/ui/app/pages/swaps/swaps.util.js b/ui/app/pages/swaps/swaps.util.js index 6f3ae8d3608b..36f3c697a899 100644 --- a/ui/app/pages/swaps/swaps.util.js +++ b/ui/app/pages/swaps/swaps.util.js @@ -6,6 +6,7 @@ import { ETH_SWAPS_TOKEN_OBJECT } from '../../helpers/constants/swaps' import { calcTokenValue, calcTokenAmount } from '../../helpers/utils/token-util' import { constructTxParams, toPrecisionWithoutTrailingZeros } from '../../helpers/utils/util' import { decimalToHex, getValueFromWeiHex } from '../../helpers/utils/conversions.util' + import { subtractCurrencies } from '../../helpers/utils/conversion-util' import { formatCurrency } from '../../helpers/utils/confirm-tx.util' import fetchWithCache from '../../helpers/utils/fetch-with-cache' @@ -265,17 +266,34 @@ export async function fetchTokenBalance (address, userAddress) { return usersToken } -export function getRenderableGasFeesForQuote (tradeGas, approveGas, gasPrice, currentCurrency, conversionRate) { +export function getRenderableNetworkFeesForQuote ( + tradeGas, + approveGas, + gasPrice, + currentCurrency, + conversionRate, + tradeValue, + sourceSymbol, + sourceAmount, +) { const totalGasLimitForCalculation = (new BigNumber(tradeGas || '0x0', 16)).plus(approveGas || '0x0', 16).toString(16) const gasTotalInWeiHex = calcGasTotal(totalGasLimitForCalculation, gasPrice) + const nonGasFee = new BigNumber(tradeValue, 16) + .minus(sourceSymbol === 'ETH' ? sourceAmount : 0, 10) + .toString(16) + + const totalWeiCost = new BigNumber(gasTotalInWeiHex, 16) + .plus(nonGasFee, 16) + .toString(16) + const ethFee = getValueFromWeiHex({ - value: gasTotalInWeiHex, + value: totalWeiCost, toDenomination: 'ETH', numberOfDecimals: 5, }) const rawNetworkFees = getValueFromWeiHex({ - value: gasTotalInWeiHex, + value: totalWeiCost, toCurrency: currentCurrency, conversionRate, numberOfDecimals: 2, @@ -286,6 +304,7 @@ export function getRenderableGasFeesForQuote (tradeGas, approveGas, gasPrice, cu rawEthFee: ethFee, feeInFiat: formattedNetworkFee, feeInEth: `${ethFee} ETH`, + nonGasFee, } } @@ -302,6 +321,7 @@ export function quotesToRenderableData (quotes, gasPrice, conversionRate, curren gasEstimateWithRefund, averageGas, fee, + trade, } = quote const sourceValue = calcTokenAmount(sourceAmount, sourceTokenInfo.decimals || 18).toString(10) const destinationValue = calcTokenAmount(destinationAmount, destinationTokenInfo.decimals || 18).toPrecision(8) @@ -311,7 +331,7 @@ export function quotesToRenderableData (quotes, gasPrice, conversionRate, curren rawNetworkFees, rawEthFee, feeInEth, - } = getRenderableGasFeesForQuote( + } = getRenderableNetworkFeesForQuote( ( gasEstimateWithRefund || decimalToHex(averageGas || 800000) @@ -320,6 +340,9 @@ export function quotesToRenderableData (quotes, gasPrice, conversionRate, curren gasPrice, currentCurrency, conversionRate, + trade.value, + sourceTokenInfo.symbol, + sourceAmount, ) const slippageMultiplier = (new BigNumber(100 - slippage)).div(100) diff --git a/ui/app/pages/swaps/view-quote/view-quote.js b/ui/app/pages/swaps/view-quote/view-quote.js index a460b552c11a..24a0008fa455 100644 --- a/ui/app/pages/swaps/view-quote/view-quote.js +++ b/ui/app/pages/swaps/view-quote/view-quote.js @@ -67,7 +67,7 @@ import MainQuoteSummary from '../main-quote-summary' import { calcGasTotal } from '../../send/send.utils' import { getCustomTxParamsData } from '../../confirm-approve/confirm-approve.util' import ActionableMessage from '../actionable-message' -import { quotesToRenderableData, getRenderableGasFeesForQuote } from '../swaps.util' +import { quotesToRenderableData, getRenderableNetworkFeesForQuote } from '../swaps.util' import { useTokenTracker } from '../../../hooks/useTokenTracker' import { QUOTES_EXPIRED_ERROR } from '../../../helpers/constants/swaps' import CountdownTimer from '../countdown-timer' @@ -99,7 +99,7 @@ export default function ViewQuote () { // Select necessary data const tradeTxParams = useSelector(getSwapsTradeTxParams) - const { gasPrice } = tradeTxParams || {} + const { gasPrice, value: tradeValue } = tradeTxParams || {} const customMaxGas = useSelector(getCustomSwapsGas) const tokenConversionRates = useSelector(getTokenExchangeRates) const memoizedTokenConversionRates = useEqualityCheck(tokenConversionRates) @@ -161,12 +161,6 @@ export default function ViewQuote () { calcTokenAmount(approveValue, selectedFromToken.decimals).toFixed(9) ) const approveGas = approveTxParams?.gas - const approveGasTotal = calcGasTotal(approveGas || '0x0', gasPrice) - const approveGasTotalInEth = getValueFromWeiHex({ - value: approveGasTotal, - toDenomination: 'ETH', - numberOfDecimals: 4, - }) const renderablePopoverData = useMemo(() => { return quotesToRenderableData( @@ -203,23 +197,30 @@ export default function ViewQuote () { sourceTokenIconUrl, } = renderableDataForUsedQuote - const { feeInFiat, feeInEth } = getRenderableGasFeesForQuote( + const { feeInFiat, feeInEth } = getRenderableNetworkFeesForQuote( usedGasLimit, approveGas, gasPrice, currentCurrency, conversionRate, + tradeValue, + sourceTokenSymbol, + usedQuote.sourceAmount, ) const { feeInFiat: maxFeeInFiat, feeInEth: maxFeeInEth, - } = getRenderableGasFeesForQuote( + nonGasFee, + } = getRenderableNetworkFeesForQuote( maxGasLimit, approveGas, gasPrice, currentCurrency, conversionRate, + tradeValue, + sourceTokenSymbol, + usedQuote.sourceAmount, ) const tokenCost = (new BigNumber(usedQuote.sourceAmount)) @@ -378,6 +379,26 @@ export default function ViewQuote () { })) } + const nonGasFeeIsPositive = (new BigNumber(nonGasFee, 16)).gt(0) + const approveGasTotal = calcGasTotal(approveGas || '0x0', gasPrice) + const extraNetworkFeeTotalInHexWEI = (new BigNumber(nonGasFee, 16)) + .plus(approveGasTotal, 16) + .toString(16) + const extraNetworkFeeTotalInEth = getValueFromWeiHex({ + value: extraNetworkFeeTotalInHexWEI, + toDenomination: 'ETH', + numberOfDecimals: 4, + }) + + let extraInfoRowLabel = '' + if (approveGas && nonGasFeeIsPositive) { + extraInfoRowLabel = t('approvalAndAggregatorTxFeeCost') + } else if (approveGas) { + extraInfoRowLabel = t('approvalTxGasCost') + } else if (nonGasFeeIsPositive) { + extraInfoRowLabel = t('aggregatorFeeCost') + } + const onFeeCardMaxRowClick = () => dispatch(showModal({ name: 'CUSTOMIZE_GAS', txData: { txParams: { ...tradeTxParams, gas: maxGasLimit } }, @@ -389,10 +410,10 @@ export default function ViewQuote () { ), customTotalSupplement: approveGasTotal, extraInfoRow: ( - approveGas + extraInfoRowLabel ? { - label: t('approvalTxGasCost'), - value: t('amountInEth', [approveGasTotalInEth]), + label: extraInfoRowLabel, + value: t('amountInEth', [extraNetworkFeeTotalInEth]), } : null ),