diff --git a/src/assets/translations/en/main.json b/src/assets/translations/en/main.json index d2b0968d772..13a8489cb46 100644 --- a/src/assets/translations/en/main.json +++ b/src/assets/translations/en/main.json @@ -759,7 +759,8 @@ "unknownGas": "(unknown)", "receiveAddress": "Receive Address", "receiveAddressDescription": "No %{chainName} address found from connected wallet. Manually enter address to continue.", - "addressPlaceholder": "%{chainName} address" + "addressPlaceholder": "%{chainName} address", + "priceImpactWarning": "Due to the size of this trade relative to available liquidity, the expected price impact of this trade is %{priceImpactPercentage}%. Are you sure you want to trade?" }, "modals": { "popup": { diff --git a/src/components/MultiHopTrade/components/TradeConfirm/TradeConfirm.tsx b/src/components/MultiHopTrade/components/TradeConfirm/TradeConfirm.tsx index a40490b9519..f30a67372b7 100644 --- a/src/components/MultiHopTrade/components/TradeConfirm/TradeConfirm.tsx +++ b/src/components/MultiHopTrade/components/TradeConfirm/TradeConfirm.tsx @@ -35,7 +35,7 @@ import { WalletActions } from 'context/WalletProvider/actions' import { useErrorHandler } from 'hooks/useErrorToast/useErrorToast' import { useLocaleFormatter } from 'hooks/useLocaleFormatter/useLocaleFormatter' import { useWallet } from 'hooks/useWallet/useWallet' -import { bnOrZero, positiveOrZero } from 'lib/bignumber/bignumber' +import { bn, bnOrZero, positiveOrZero } from 'lib/bignumber/bignumber' import { getTxLink } from 'lib/getTxLink' import { firstNonZeroDecimal } from 'lib/math' import { getMixPanel } from 'lib/mixpanel/mixPanelSingleton' @@ -48,6 +48,7 @@ import { selectActiveStepOrDefault, selectActiveSwapperName, selectBuyAmountBeforeFeesCryptoPrecision, + selectBuyAmountBeforeFeesUserCurrency, selectFirstHop, selectFirstHopNetworkFeeCryptoPrecision, selectFirstHopSellAsset, @@ -124,8 +125,9 @@ export const TradeConfirm = () => { const swapperName = useAppSelector(selectActiveSwapperName) const defaultFeeAsset = useAppSelector(selectFirstHopSellFeeAsset) const netBuyAmountCryptoPrecision = useAppSelector(selectNetBuyAmountCryptoPrecision) - const slippage = useAppSelector(selectTradeSlippagePercentageDecimal) + const slippageDecimal = useAppSelector(selectTradeSlippagePercentageDecimal) const netBuyAmountUserCurrency = useAppSelector(selectNetBuyAmountUserCurrency) + const buyAmountBeforeFeesUserCurrency = useAppSelector(selectBuyAmountBeforeFeesUserCurrency) const sellAmountBeforeFeesUserCurrency = useAppSelector(selectSellAmountUserCurrency) const networkFeeCryptoHuman = useAppSelector(selectFirstHopNetworkFeeCryptoPrecision) const networkFeeUserCurrency = useAppSelector(selectTotalNetworkFeeUserCurrencyPrecision) @@ -150,6 +152,22 @@ export const TradeConfirm = () => { const txHash = buyTxHash ?? sellTxHash + const priceImpactPercentage = useMemo(() => { + if (!sellAmountBeforeFeesUserCurrency || !buyAmountBeforeFeesUserCurrency) return bn(0) + + const tradeDifference = bn(sellAmountBeforeFeesUserCurrency) + .minus(buyAmountBeforeFeesUserCurrency) + .abs() + + return tradeDifference.div(sellAmountBeforeFeesUserCurrency).times(100) + }, [sellAmountBeforeFeesUserCurrency, buyAmountBeforeFeesUserCurrency]) + + const isHighPriceImpact = useMemo(() => { + if (!priceImpactPercentage) return false + + return priceImpactPercentage.gt(10) + }, [priceImpactPercentage]) + const getSellTxLink = useCallback( (sellTxHash: string) => getTxLink({ @@ -206,6 +224,16 @@ export const TradeConfirm = () => { return } + const shouldContinueTrade = + !isHighPriceImpact || + window.confirm( + translate('trade.priceImpactWarning', { + priceImpactPercentage: priceImpactPercentage.toFixed(2), + }), + ) + + if (!shouldContinueTrade) return + await executeTrade() // only track after swapper successfully executes trade // otherwise unsigned txs will be tracked as confirmed trades @@ -224,8 +252,11 @@ export const TradeConfirm = () => { handleBack, history, isConnected, + isHighPriceImpact, mixpanel, + priceImpactPercentage, showErrorToast, + translate, wallet, walletDispatch, ]) @@ -331,7 +362,7 @@ export const TradeConfirm = () => { amountBeforeFeesCryptoPrecision={buyAmountBeforeFeesCryptoPrecision ?? ''} protocolFees={tradeQuoteStep?.feeData.protocolFees} shapeShiftFee='0' - slippage={slippage} + slippage={slippageDecimal} fiatAmount={positiveOrZero(netBuyAmountUserCurrency).toFixed(2)} swapperName={swapperName ?? ''} intermediaryTransactionOutputs={tradeQuoteStep?.intermediaryTransactionOutputs} @@ -349,7 +380,7 @@ export const TradeConfirm = () => { buyAmountBeforeFeesCryptoPrecision, tradeQuoteStep?.feeData.protocolFees, tradeQuoteStep?.intermediaryTransactionOutputs, - slippage, + slippageDecimal, netBuyAmountUserCurrency, swapperName, donationAmount, diff --git a/src/state/slices/tradeQuoteSlice/selectors.ts b/src/state/slices/tradeQuoteSlice/selectors.ts index b19ca9f85e8..22ebcb76ea6 100644 --- a/src/state/slices/tradeQuoteSlice/selectors.ts +++ b/src/state/slices/tradeQuoteSlice/selectors.ts @@ -362,6 +362,15 @@ export const selectNetBuyAmountUserCurrency = createSelector( }, ) +export const selectBuyAmountBeforeFeesUserCurrency = createSelector( + selectBuyAmountBeforeFeesCryptoPrecision, + selectBuyAssetUserCurrencyRate, + (buyAmountBeforeFeesCryptoPrecision, buyAssetUserCurrencyRate) => { + if (!buyAmountBeforeFeesCryptoPrecision || !buyAssetUserCurrencyRate) return + return bn(buyAmountBeforeFeesCryptoPrecision).times(buyAssetUserCurrencyRate).toFixed() + }, +) + export const selectSellAmountUsd = createSelector( selectSellAmountCryptoPrecision, selectSellAssetUsdRate,