feat: price impact update (#3496)
* feat: green text on neg price impact * chore: propagate all of usdc price impact * chore: pass price impact to summary details * chore: propagate slippage and impact warnings * feat: update warnings on summary dialog * chore: rm todo
This commit is contained in:
parent
b52273932a
commit
fa163cb938
@ -5,13 +5,12 @@ import { useAtomValue } from 'jotai/utils'
|
|||||||
import BrandedFooter from 'lib/components/BrandedFooter'
|
import BrandedFooter from 'lib/components/BrandedFooter'
|
||||||
import { useIsSwapFieldIndependent, useSwapAmount, useSwapCurrency, useSwapInfo } from 'lib/hooks/swap'
|
import { useIsSwapFieldIndependent, useSwapAmount, useSwapCurrency, useSwapInfo } from 'lib/hooks/swap'
|
||||||
import useCurrencyColor from 'lib/hooks/useCurrencyColor'
|
import useCurrencyColor from 'lib/hooks/useCurrencyColor'
|
||||||
import useUSDCPriceImpact, { toHumanReadablePriceImpact } from 'lib/hooks/useUSDCPriceImpact'
|
import useUSDCPriceImpact from 'lib/hooks/useUSDCPriceImpact'
|
||||||
import { Field } from 'lib/state/swap'
|
import { Field } from 'lib/state/swap'
|
||||||
import styled, { DynamicThemeProvider, ThemedText } from 'lib/theme'
|
import styled, { DynamicThemeProvider, ThemedText } from 'lib/theme'
|
||||||
import { PropsWithChildren, useMemo } from 'react'
|
import { PropsWithChildren } from 'react'
|
||||||
import { TradeState } from 'state/routing/types'
|
import { TradeState } from 'state/routing/types'
|
||||||
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
|
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
|
||||||
import { getPriceImpactWarning } from 'utils/prices'
|
|
||||||
|
|
||||||
import Column from '../Column'
|
import Column from '../Column'
|
||||||
import Row from '../Row'
|
import Row from '../Row'
|
||||||
@ -59,8 +58,11 @@ export default function Output({ disabled, focused, children }: PropsWithChildre
|
|||||||
// different state true/null/false allow smoother color transition
|
// different state true/null/false allow smoother color transition
|
||||||
const hasColor = swapOutputCurrency ? Boolean(color) || null : false
|
const hasColor = swapOutputCurrency ? Boolean(color) || null : false
|
||||||
|
|
||||||
const { outputUSDC, priceImpact } = useUSDCPriceImpact(inputCurrencyAmount, outputCurrencyAmount)
|
const {
|
||||||
const priceImpactWarning = useMemo(() => getPriceImpactWarning(priceImpact), [priceImpact])
|
outputUSDC,
|
||||||
|
priceImpact,
|
||||||
|
warning: priceImpactWarning,
|
||||||
|
} = useUSDCPriceImpact(inputCurrencyAmount, outputCurrencyAmount)
|
||||||
|
|
||||||
const amount = useFormattedFieldAmount({
|
const amount = useFormattedFieldAmount({
|
||||||
disabled,
|
disabled,
|
||||||
@ -88,11 +90,7 @@ export default function Output({ disabled, focused, children }: PropsWithChildre
|
|||||||
<Row>
|
<Row>
|
||||||
<USDC gap={0.5} isLoading={isRouteLoading}>
|
<USDC gap={0.5} isLoading={isRouteLoading}>
|
||||||
{outputUSDC ? `$${formatCurrencyAmount(outputUSDC, 6, 'en', 2)}` : '-'}{' '}
|
{outputUSDC ? `$${formatCurrencyAmount(outputUSDC, 6, 'en', 2)}` : '-'}{' '}
|
||||||
{priceImpact && (
|
{priceImpact && <ThemedText.Body2 color={priceImpactWarning}>({priceImpact})</ThemedText.Body2>}
|
||||||
<ThemedText.Body2 color={priceImpactWarning}>
|
|
||||||
({toHumanReadablePriceImpact(priceImpact)})
|
|
||||||
</ThemedText.Body2>
|
|
||||||
)}
|
|
||||||
</USDC>
|
</USDC>
|
||||||
{balance && (
|
{balance && (
|
||||||
<Balance focused={focused}>
|
<Balance focused={focused}>
|
||||||
|
@ -2,7 +2,7 @@ import { Trans } from '@lingui/macro'
|
|||||||
import { useAtom } from 'jotai'
|
import { useAtom } from 'jotai'
|
||||||
import Popover from 'lib/components/Popover'
|
import Popover from 'lib/components/Popover'
|
||||||
import { useTooltip } from 'lib/components/Tooltip'
|
import { useTooltip } from 'lib/components/Tooltip'
|
||||||
import { getSlippageWarning, toPercent } from 'lib/hooks/useAllowedSlippage'
|
import { getSlippageWarning, toPercent } from 'lib/hooks/useSlippage'
|
||||||
import { AlertTriangle, Check, Icon, LargeIcon, XOctagon } from 'lib/icons'
|
import { AlertTriangle, Check, Icon, LargeIcon, XOctagon } from 'lib/icons'
|
||||||
import { autoSlippageAtom, maxSlippageAtom } from 'lib/state/settings'
|
import { autoSlippageAtom, maxSlippageAtom } from 'lib/state/settings'
|
||||||
import styled, { ThemedText } from 'lib/theme'
|
import styled, { ThemedText } from 'lib/theme'
|
||||||
|
@ -22,7 +22,7 @@ const UNI = (function () {
|
|||||||
function Fixture() {
|
function Fixture() {
|
||||||
const setState = useUpdateAtom(swapAtom)
|
const setState = useUpdateAtom(swapAtom)
|
||||||
const {
|
const {
|
||||||
allowedSlippage,
|
slippage,
|
||||||
trade: { trade },
|
trade: { trade },
|
||||||
} = useSwapInfo()
|
} = useSwapInfo()
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ function Fixture() {
|
|||||||
|
|
||||||
return trade ? (
|
return trade ? (
|
||||||
<Modal color="dialog">
|
<Modal color="dialog">
|
||||||
<SummaryDialog onConfirm={() => void 0} trade={trade} allowedSlippage={allowedSlippage} />
|
<SummaryDialog onConfirm={() => void 0} trade={trade} slippage={slippage} />
|
||||||
</Modal>
|
</Modal>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
|
@ -3,13 +3,12 @@ import { useLingui } from '@lingui/react'
|
|||||||
import { Trade } from '@uniswap/router-sdk'
|
import { Trade } from '@uniswap/router-sdk'
|
||||||
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
|
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
|
||||||
import { useAtomValue } from 'jotai/utils'
|
import { useAtomValue } from 'jotai/utils'
|
||||||
import { getSlippageWarning } from 'lib/hooks/useAllowedSlippage'
|
|
||||||
import { feeOptionsAtom } from 'lib/state/swap'
|
import { feeOptionsAtom } from 'lib/state/swap'
|
||||||
import styled, { Color, ThemedText } from 'lib/theme'
|
import styled, { Color, ThemedText } from 'lib/theme'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { currencyId } from 'utils/currencyId'
|
import { currencyId } from 'utils/currencyId'
|
||||||
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
|
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
|
||||||
import { computeRealizedLPFeeAmount, computeRealizedPriceImpact, getPriceImpactWarning } from 'utils/prices'
|
import { computeRealizedLPFeeAmount } from 'utils/prices'
|
||||||
|
|
||||||
import Row from '../../Row'
|
import Row from '../../Row'
|
||||||
|
|
||||||
@ -37,16 +36,16 @@ function Detail({ label, value, color }: DetailProps) {
|
|||||||
|
|
||||||
interface DetailsProps {
|
interface DetailsProps {
|
||||||
trade: Trade<Currency, Currency, TradeType>
|
trade: Trade<Currency, Currency, TradeType>
|
||||||
allowedSlippage: Percent
|
slippage: { auto: boolean; allowed: Percent; warning?: Color }
|
||||||
|
usdcPriceImpact: { priceImpact?: string; warning?: Color }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Details({ trade, allowedSlippage }: DetailsProps) {
|
export default function Details({ trade, slippage, usdcPriceImpact }: DetailsProps) {
|
||||||
const { inputAmount, outputAmount } = trade
|
const { inputAmount, outputAmount } = trade
|
||||||
const inputCurrency = inputAmount.currency
|
const inputCurrency = inputAmount.currency
|
||||||
const outputCurrency = outputAmount.currency
|
const outputCurrency = outputAmount.currency
|
||||||
const integrator = window.location.hostname
|
const integrator = window.location.hostname
|
||||||
const feeOptions = useAtomValue(feeOptionsAtom)
|
const feeOptions = useAtomValue(feeOptionsAtom)
|
||||||
const priceImpact = useMemo(() => computeRealizedPriceImpact(trade), [trade])
|
|
||||||
const lpFeeAmount = useMemo(() => computeRealizedLPFeeAmount(trade), [trade])
|
const lpFeeAmount = useMemo(() => computeRealizedLPFeeAmount(trade), [trade])
|
||||||
const { i18n } = useLingui()
|
const { i18n } = useLingui()
|
||||||
|
|
||||||
@ -62,7 +61,9 @@ export default function Details({ trade, allowedSlippage }: DetailsProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rows.push([t`Price impact`, `${priceImpact.toFixed(2)}%`, getPriceImpactWarning(priceImpact)])
|
if (usdcPriceImpact.priceImpact) {
|
||||||
|
rows.push([t`Price impact`, usdcPriceImpact.priceImpact, usdcPriceImpact.warning])
|
||||||
|
}
|
||||||
|
|
||||||
if (lpFeeAmount) {
|
if (lpFeeAmount) {
|
||||||
const parsedLpFee = formatCurrencyAmount(lpFeeAmount, 6, i18n.locale)
|
const parsedLpFee = formatCurrencyAmount(lpFeeAmount, 6, i18n.locale)
|
||||||
@ -70,24 +71,24 @@ export default function Details({ trade, allowedSlippage }: DetailsProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (trade.tradeType === TradeType.EXACT_OUTPUT) {
|
if (trade.tradeType === TradeType.EXACT_OUTPUT) {
|
||||||
const localizedMaxSent = formatCurrencyAmount(trade.maximumAmountIn(allowedSlippage), 6, i18n.locale)
|
const localizedMaxSent = formatCurrencyAmount(trade.maximumAmountIn(slippage.allowed), 6, i18n.locale)
|
||||||
rows.push([t`Maximum sent`, `${localizedMaxSent} ${inputCurrency.symbol}`])
|
rows.push([t`Maximum sent`, `${localizedMaxSent} ${inputCurrency.symbol}`])
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trade.tradeType === TradeType.EXACT_INPUT) {
|
if (trade.tradeType === TradeType.EXACT_INPUT) {
|
||||||
const localizedMaxSent = formatCurrencyAmount(trade.minimumAmountOut(allowedSlippage), 6, i18n.locale)
|
const localizedMaxSent = formatCurrencyAmount(trade.minimumAmountOut(slippage.allowed), 6, i18n.locale)
|
||||||
rows.push([t`Minimum received`, `${localizedMaxSent} ${outputCurrency.symbol}`])
|
rows.push([t`Minimum received`, `${localizedMaxSent} ${outputCurrency.symbol}`])
|
||||||
}
|
}
|
||||||
|
|
||||||
rows.push([t`Slippage tolerance`, `${allowedSlippage.toFixed(2)}%`, getSlippageWarning(allowedSlippage)])
|
rows.push([t`Slippage tolerance`, `${slippage.allowed.toFixed(2)}%`, slippage.warning])
|
||||||
|
|
||||||
return rows
|
return rows
|
||||||
}, [
|
}, [
|
||||||
feeOptions,
|
feeOptions,
|
||||||
priceImpact,
|
usdcPriceImpact,
|
||||||
lpFeeAmount,
|
lpFeeAmount,
|
||||||
trade,
|
trade,
|
||||||
allowedSlippage,
|
slippage,
|
||||||
outputAmount,
|
outputAmount,
|
||||||
i18n.locale,
|
i18n.locale,
|
||||||
integrator,
|
integrator,
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import { useLingui } from '@lingui/react'
|
import { useLingui } from '@lingui/react'
|
||||||
import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core'
|
import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||||
import useUSDCPriceImpact, { toHumanReadablePriceImpact } from 'lib/hooks/useUSDCPriceImpact'
|
import useUSDCPriceImpact from 'lib/hooks/useUSDCPriceImpact'
|
||||||
import { ArrowRight } from 'lib/icons'
|
import { ArrowRight } from 'lib/icons'
|
||||||
import { ThemedText } from 'lib/theme'
|
import { ThemedText } from 'lib/theme'
|
||||||
import { useMemo } from 'react'
|
import { PropsWithChildren } from 'react'
|
||||||
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
|
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
|
||||||
import { getPriceImpactWarning } from 'utils/prices'
|
|
||||||
|
|
||||||
import Column from '../../Column'
|
import Column from '../../Column'
|
||||||
import Row from '../../Row'
|
import Row from '../../Row'
|
||||||
@ -14,12 +13,10 @@ import TokenImg from '../../TokenImg'
|
|||||||
interface TokenValueProps {
|
interface TokenValueProps {
|
||||||
input: CurrencyAmount<Currency>
|
input: CurrencyAmount<Currency>
|
||||||
usdc?: CurrencyAmount<Token>
|
usdc?: CurrencyAmount<Token>
|
||||||
priceImpact?: Percent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function TokenValue({ input, usdc, priceImpact }: TokenValueProps) {
|
function TokenValue({ input, usdc, children }: PropsWithChildren<TokenValueProps>) {
|
||||||
const { i18n } = useLingui()
|
const { i18n } = useLingui()
|
||||||
const priceImpactWarning = useMemo(() => getPriceImpactWarning(priceImpact), [priceImpact])
|
|
||||||
return (
|
return (
|
||||||
<Column justify="flex-start">
|
<Column justify="flex-start">
|
||||||
<Row gap={0.375} justify="flex-start">
|
<Row gap={0.375} justify="flex-start">
|
||||||
@ -29,16 +26,12 @@ function TokenValue({ input, usdc, priceImpact }: TokenValueProps) {
|
|||||||
</ThemedText.Body2>
|
</ThemedText.Body2>
|
||||||
</Row>
|
</Row>
|
||||||
{usdc && (
|
{usdc && (
|
||||||
<ThemedText.Caption color="secondary" userSelect>
|
<Row justify="flex-start">
|
||||||
<Row justify="flex-start" gap={0.25}>
|
<ThemedText.Caption color="secondary" userSelect>
|
||||||
${formatCurrencyAmount(usdc, 6, 'en', 2)}
|
${formatCurrencyAmount(usdc, 6, 'en', 2)}
|
||||||
{priceImpact && (
|
{children}
|
||||||
<ThemedText.Caption color={priceImpactWarning}>
|
</ThemedText.Caption>
|
||||||
({toHumanReadablePriceImpact(priceImpact)})
|
</Row>
|
||||||
</ThemedText.Caption>
|
|
||||||
)}
|
|
||||||
</Row>
|
|
||||||
</ThemedText.Caption>
|
|
||||||
)}
|
)}
|
||||||
</Column>
|
</Column>
|
||||||
)
|
)
|
||||||
@ -47,17 +40,19 @@ function TokenValue({ input, usdc, priceImpact }: TokenValueProps) {
|
|||||||
interface SummaryProps {
|
interface SummaryProps {
|
||||||
input: CurrencyAmount<Currency>
|
input: CurrencyAmount<Currency>
|
||||||
output: CurrencyAmount<Currency>
|
output: CurrencyAmount<Currency>
|
||||||
showUSDC?: true
|
usdcPriceImpact?: ReturnType<typeof useUSDCPriceImpact>
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Summary({ input, output, showUSDC }: SummaryProps) {
|
export default function Summary({ input, output, usdcPriceImpact }: SummaryProps) {
|
||||||
const { inputUSDC, outputUSDC, priceImpact } = useUSDCPriceImpact(input, output)
|
const { inputUSDC, outputUSDC, priceImpact, warning: priceImpactWarning } = usdcPriceImpact || {}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row gap={showUSDC ? 1 : 0.25}>
|
<Row gap={usdcPriceImpact ? 1 : 0.25}>
|
||||||
<TokenValue input={input} usdc={showUSDC && inputUSDC} />
|
<TokenValue input={input} usdc={inputUSDC} />
|
||||||
<ArrowRight />
|
<ArrowRight />
|
||||||
<TokenValue input={output} usdc={showUSDC && outputUSDC} priceImpact={priceImpact} />
|
<TokenValue input={output} usdc={outputUSDC}>
|
||||||
|
{priceImpact && <ThemedText.Caption color={priceImpactWarning}>({priceImpact})</ThemedText.Caption>}
|
||||||
|
</TokenValue>
|
||||||
</Row>
|
</Row>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import { Trans } from '@lingui/macro'
|
import { Trans } from '@lingui/macro'
|
||||||
import { useLingui } from '@lingui/react'
|
import { useLingui } from '@lingui/react'
|
||||||
import { Trade } from '@uniswap/router-sdk'
|
import { Trade } from '@uniswap/router-sdk'
|
||||||
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
|
import { Currency, TradeType } from '@uniswap/sdk-core'
|
||||||
import { IconButton } from 'lib/components/Button'
|
import { IconButton } from 'lib/components/Button'
|
||||||
import { useSwapTradeType } from 'lib/hooks/swap'
|
import { useSwapTradeType } from 'lib/hooks/swap'
|
||||||
import { getSlippageWarning } from 'lib/hooks/useAllowedSlippage'
|
|
||||||
import useScrollbar from 'lib/hooks/useScrollbar'
|
import useScrollbar from 'lib/hooks/useScrollbar'
|
||||||
|
import { Slippage } from 'lib/hooks/useSlippage'
|
||||||
|
import useUSDCPriceImpact from 'lib/hooks/useUSDCPriceImpact'
|
||||||
import { AlertTriangle, BarChart, Expando, Info } from 'lib/icons'
|
import { AlertTriangle, BarChart, Expando, Info } from 'lib/icons'
|
||||||
import styled, { ThemedText } from 'lib/theme'
|
import styled, { Color, ThemedText } from 'lib/theme'
|
||||||
import formatLocaleNumber from 'lib/utils/formatLocaleNumber'
|
import formatLocaleNumber from 'lib/utils/formatLocaleNumber'
|
||||||
import { useMemo, useState } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
import { formatCurrencyAmount, formatPrice } from 'utils/formatCurrencyAmount'
|
import { formatCurrencyAmount, formatPrice } from 'utils/formatCurrencyAmount'
|
||||||
import { computeRealizedPriceImpact, getPriceImpactWarning } from 'utils/prices'
|
|
||||||
import { tradeMeaningfullyDiffers } from 'utils/tradeMeaningFullyDiffer'
|
import { tradeMeaningfullyDiffers } from 'utils/tradeMeaningFullyDiffer'
|
||||||
|
|
||||||
import ActionButton, { Action } from '../../ActionButton'
|
import ActionButton, { Action } from '../../ActionButton'
|
||||||
@ -77,17 +77,38 @@ const Body = styled(Column)<{ open: boolean }>`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
function Subhead({ priceImpact, slippage }: { priceImpact: { warning?: Color }; slippage: { warning?: Color } }) {
|
||||||
|
return (
|
||||||
|
<Row gap={0.5}>
|
||||||
|
{priceImpact.warning || slippage.warning ? (
|
||||||
|
<AlertTriangle color={priceImpact.warning || slippage.warning} />
|
||||||
|
) : (
|
||||||
|
<Info color="secondary" />
|
||||||
|
)}
|
||||||
|
<ThemedText.Subhead2 color={priceImpact.warning || slippage.warning || 'secondary'}>
|
||||||
|
{priceImpact.warning ? (
|
||||||
|
<Trans>High price impact</Trans>
|
||||||
|
) : slippage.warning ? (
|
||||||
|
<Trans>High slippage</Trans>
|
||||||
|
) : (
|
||||||
|
<Trans>Swap details</Trans>
|
||||||
|
)}
|
||||||
|
</ThemedText.Subhead2>
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
interface SummaryDialogProps {
|
interface SummaryDialogProps {
|
||||||
trade: Trade<Currency, Currency, TradeType>
|
trade: Trade<Currency, Currency, TradeType>
|
||||||
allowedSlippage: Percent
|
slippage: Slippage
|
||||||
onConfirm: () => void
|
onConfirm: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SummaryDialog({ trade, allowedSlippage, onConfirm }: SummaryDialogProps) {
|
export function SummaryDialog({ trade, slippage, onConfirm }: SummaryDialogProps) {
|
||||||
const { inputAmount, outputAmount, executionPrice } = trade
|
const { inputAmount, outputAmount, executionPrice } = trade
|
||||||
const inputCurrency = inputAmount.currency
|
const inputCurrency = inputAmount.currency
|
||||||
const outputCurrency = outputAmount.currency
|
const outputCurrency = outputAmount.currency
|
||||||
const priceImpact = useMemo(() => computeRealizedPriceImpact(trade), [trade])
|
const usdcPriceImpact = useUSDCPriceImpact(inputAmount, outputAmount)
|
||||||
const tradeType = useSwapTradeType()
|
const tradeType = useSwapTradeType()
|
||||||
const { i18n } = useLingui()
|
const { i18n } = useLingui()
|
||||||
|
|
||||||
@ -95,10 +116,6 @@ export function SummaryDialog({ trade, allowedSlippage, onConfirm }: SummaryDial
|
|||||||
const [details, setDetails] = useState<HTMLDivElement | null>(null)
|
const [details, setDetails] = useState<HTMLDivElement | null>(null)
|
||||||
const scrollbar = useScrollbar(details)
|
const scrollbar = useScrollbar(details)
|
||||||
|
|
||||||
const warning = useMemo(() => {
|
|
||||||
return getPriceImpactWarning(priceImpact) || getSlippageWarning(allowedSlippage)
|
|
||||||
}, [allowedSlippage, priceImpact])
|
|
||||||
|
|
||||||
const [ackPriceImpact, setAckPriceImpact] = useState(false)
|
const [ackPriceImpact, setAckPriceImpact] = useState(false)
|
||||||
|
|
||||||
const [confirmedTrade, setConfirmedTrade] = useState(trade)
|
const [confirmedTrade, setConfirmedTrade] = useState(trade)
|
||||||
@ -115,7 +132,7 @@ export function SummaryDialog({ trade, allowedSlippage, onConfirm }: SummaryDial
|
|||||||
onClick: () => setConfirmedTrade(trade),
|
onClick: () => setConfirmedTrade(trade),
|
||||||
children: <Trans>Accept</Trans>,
|
children: <Trans>Accept</Trans>,
|
||||||
}
|
}
|
||||||
} else if (getPriceImpactWarning(priceImpact) === 'error' && !ackPriceImpact) {
|
} else if (usdcPriceImpact.warning === 'error' && !ackPriceImpact) {
|
||||||
return {
|
return {
|
||||||
message: <Trans>High price impact</Trans>,
|
message: <Trans>High price impact</Trans>,
|
||||||
onClick: () => setAckPriceImpact(true),
|
onClick: () => setAckPriceImpact(true),
|
||||||
@ -123,7 +140,7 @@ export function SummaryDialog({ trade, allowedSlippage, onConfirm }: SummaryDial
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}, [ackPriceImpact, doesTradeDiffer, priceImpact, trade])
|
}, [ackPriceImpact, doesTradeDiffer, trade, usdcPriceImpact.warning])
|
||||||
|
|
||||||
if (!(inputAmount && outputAmount && inputCurrency && outputCurrency)) {
|
if (!(inputAmount && outputAmount && inputCurrency && outputCurrency)) {
|
||||||
return null
|
return null
|
||||||
@ -134,7 +151,7 @@ export function SummaryDialog({ trade, allowedSlippage, onConfirm }: SummaryDial
|
|||||||
<Header title={<Trans>Swap summary</Trans>} ruled />
|
<Header title={<Trans>Swap summary</Trans>} ruled />
|
||||||
<Body flex align="stretch" gap={0.75} padded open={open}>
|
<Body flex align="stretch" gap={0.75} padded open={open}>
|
||||||
<SummaryColumn gap={0.75} flex justify="center">
|
<SummaryColumn gap={0.75} flex justify="center">
|
||||||
<Summary input={inputAmount} output={outputAmount} showUSDC />
|
<Summary input={inputAmount} output={outputAmount} usdcPriceImpact={usdcPriceImpact} />
|
||||||
<Row>
|
<Row>
|
||||||
<ThemedText.Caption userSelect>
|
<ThemedText.Caption userSelect>
|
||||||
{formatLocaleNumber({ number: 1, sigFigs: 1, locale: i18n.locale })} {inputCurrency.symbol} ={' '}
|
{formatLocaleNumber({ number: 1, sigFigs: 1, locale: i18n.locale })} {inputCurrency.symbol} ={' '}
|
||||||
@ -144,19 +161,14 @@ export function SummaryDialog({ trade, allowedSlippage, onConfirm }: SummaryDial
|
|||||||
</SummaryColumn>
|
</SummaryColumn>
|
||||||
<Rule />
|
<Rule />
|
||||||
<Row>
|
<Row>
|
||||||
<Row gap={0.5}>
|
<Subhead priceImpact={usdcPriceImpact} slippage={slippage} />
|
||||||
{warning ? <AlertTriangle color={warning} /> : <Info color="secondary" />}
|
|
||||||
<ThemedText.Subhead2 color="secondary">
|
|
||||||
<Trans>Swap details</Trans>
|
|
||||||
</ThemedText.Subhead2>
|
|
||||||
</Row>
|
|
||||||
<IconButton color="secondary" onClick={() => setOpen(!open)} icon={Expando} iconProps={{ open }} />
|
<IconButton color="secondary" onClick={() => setOpen(!open)} icon={Expando} iconProps={{ open }} />
|
||||||
</Row>
|
</Row>
|
||||||
<ExpandoColumn flex align="stretch">
|
<ExpandoColumn flex align="stretch">
|
||||||
<Rule />
|
<Rule />
|
||||||
<DetailsColumn>
|
<DetailsColumn>
|
||||||
<Column gap={0.5} ref={setDetails} css={scrollbar}>
|
<Column gap={0.5} ref={setDetails} css={scrollbar}>
|
||||||
<Details trade={trade} allowedSlippage={allowedSlippage} />
|
<Details trade={trade} slippage={slippage} usdcPriceImpact={usdcPriceImpact} />
|
||||||
</Column>
|
</Column>
|
||||||
</DetailsColumn>
|
</DetailsColumn>
|
||||||
<Estimate color="secondary">
|
<Estimate color="secondary">
|
||||||
@ -164,13 +176,13 @@ export function SummaryDialog({ trade, allowedSlippage, onConfirm }: SummaryDial
|
|||||||
{tradeType === TradeType.EXACT_INPUT && (
|
{tradeType === TradeType.EXACT_INPUT && (
|
||||||
<Trans>
|
<Trans>
|
||||||
You will receive at least{' '}
|
You will receive at least{' '}
|
||||||
{formatCurrencyAmount(trade.minimumAmountOut(allowedSlippage), 6, i18n.locale)} {outputCurrency.symbol}{' '}
|
{formatCurrencyAmount(trade.minimumAmountOut(slippage.allowed), 6, i18n.locale)} {outputCurrency.symbol}{' '}
|
||||||
or the transaction will revert.
|
or the transaction will revert.
|
||||||
</Trans>
|
</Trans>
|
||||||
)}
|
)}
|
||||||
{tradeType === TradeType.EXACT_OUTPUT && (
|
{tradeType === TradeType.EXACT_OUTPUT && (
|
||||||
<Trans>
|
<Trans>
|
||||||
You will send at most {formatCurrencyAmount(trade.maximumAmountIn(allowedSlippage), 6, i18n.locale)}{' '}
|
You will send at most {formatCurrencyAmount(trade.maximumAmountIn(slippage.allowed), 6, i18n.locale)}{' '}
|
||||||
{inputCurrency.symbol} or the transaction will revert.
|
{inputCurrency.symbol} or the transaction will revert.
|
||||||
</Trans>
|
</Trans>
|
||||||
)}
|
)}
|
||||||
|
@ -41,7 +41,7 @@ export default function SwapButton({ disabled }: SwapButtonProps) {
|
|||||||
const { tokenColorExtraction } = useTheme()
|
const { tokenColorExtraction } = useTheme()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
allowedSlippage,
|
slippage,
|
||||||
currencies: { [Field.INPUT]: inputCurrency },
|
currencies: { [Field.INPUT]: inputCurrency },
|
||||||
currencyBalances: { [Field.INPUT]: inputCurrencyBalance },
|
currencyBalances: { [Field.INPUT]: inputCurrencyBalance },
|
||||||
feeOptions,
|
feeOptions,
|
||||||
@ -64,13 +64,13 @@ export default function SwapButton({ disabled }: SwapButtonProps) {
|
|||||||
// TODO(zzmp): Return an optimized trade directly from useSwapInfo.
|
// TODO(zzmp): Return an optimized trade directly from useSwapInfo.
|
||||||
const optimizedTrade =
|
const optimizedTrade =
|
||||||
// Use trade.trade if there is no swap optimized trade. This occurs if approvals are still pending.
|
// Use trade.trade if there is no swap optimized trade. This occurs if approvals are still pending.
|
||||||
useSwapApprovalOptimizedTrade(trade.trade, allowedSlippage, useIsPendingApproval) || trade.trade
|
useSwapApprovalOptimizedTrade(trade.trade, slippage.allowed, useIsPendingApproval) || trade.trade
|
||||||
|
|
||||||
const approvalCurrencyAmount = useSwapCurrencyAmount(Field.INPUT)
|
const approvalCurrencyAmount = useSwapCurrencyAmount(Field.INPUT)
|
||||||
|
|
||||||
const { approvalState, signatureData, handleApproveOrPermit } = useApproveOrPermit(
|
const { approvalState, signatureData, handleApproveOrPermit } = useApproveOrPermit(
|
||||||
optimizedTrade,
|
optimizedTrade,
|
||||||
allowedSlippage,
|
slippage.allowed,
|
||||||
useIsPendingApproval,
|
useIsPendingApproval,
|
||||||
approvalCurrencyAmount
|
approvalCurrencyAmount
|
||||||
)
|
)
|
||||||
@ -151,7 +151,7 @@ export default function SwapButton({ disabled }: SwapButtonProps) {
|
|||||||
// the callback to execute the swap
|
// the callback to execute the swap
|
||||||
const { callback: swapCallback } = useSwapCallback({
|
const { callback: swapCallback } = useSwapCallback({
|
||||||
trade: optimizedTrade,
|
trade: optimizedTrade,
|
||||||
allowedSlippage,
|
allowedSlippage: slippage.allowed,
|
||||||
recipientAddressOrName: account ?? null,
|
recipientAddressOrName: account ?? null,
|
||||||
signatureData,
|
signatureData,
|
||||||
deadline,
|
deadline,
|
||||||
@ -229,7 +229,7 @@ export default function SwapButton({ disabled }: SwapButtonProps) {
|
|||||||
</ActionButton>
|
</ActionButton>
|
||||||
{activeTrade && (
|
{activeTrade && (
|
||||||
<Dialog color="dialog" onClose={handleDialogClose}>
|
<Dialog color="dialog" onClose={handleDialogClose}>
|
||||||
<SummaryDialog trade={activeTrade} allowedSlippage={allowedSlippage} onConfirm={onConfirm} />
|
<SummaryDialog trade={activeTrade} slippage={slippage} onConfirm={onConfirm} />
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -5,13 +5,12 @@ import Rule from 'lib/components/Rule'
|
|||||||
import Tooltip from 'lib/components/Tooltip'
|
import Tooltip from 'lib/components/Tooltip'
|
||||||
import { loadingCss } from 'lib/css/loading'
|
import { loadingCss } from 'lib/css/loading'
|
||||||
import { WrapType } from 'lib/hooks/swap/useWrapCallback'
|
import { WrapType } from 'lib/hooks/swap/useWrapCallback'
|
||||||
import useUSDCPriceImpact, { toHumanReadablePriceImpact } from 'lib/hooks/useUSDCPriceImpact'
|
import useUSDCPriceImpact from 'lib/hooks/useUSDCPriceImpact'
|
||||||
import { AlertTriangle, Icon, Info, InlineSpinner } from 'lib/icons'
|
import { AlertTriangle, Icon, Info, InlineSpinner } from 'lib/icons'
|
||||||
import styled, { ThemedText } from 'lib/theme'
|
import styled, { ThemedText } from 'lib/theme'
|
||||||
import { ReactNode, useCallback, useMemo, useState } from 'react'
|
import { ReactNode, useCallback, useMemo, useState } from 'react'
|
||||||
import { InterfaceTrade } from 'state/routing/types'
|
import { InterfaceTrade } from 'state/routing/types'
|
||||||
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
|
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
|
||||||
import { getPriceImpactWarning } from 'utils/prices'
|
|
||||||
|
|
||||||
import { TextButton } from '../../Button'
|
import { TextButton } from '../../Button'
|
||||||
import Row from '../../Row'
|
import Row from '../../Row'
|
||||||
@ -83,8 +82,7 @@ export function WrapCurrency({ loading, wrapType }: { loading: boolean; wrapType
|
|||||||
export function Trade({ trade }: { trade: InterfaceTrade<Currency, Currency, TradeType> }) {
|
export function Trade({ trade }: { trade: InterfaceTrade<Currency, Currency, TradeType> }) {
|
||||||
const [flip, setFlip] = useState(true)
|
const [flip, setFlip] = useState(true)
|
||||||
const { inputAmount: input, outputAmount: output, executionPrice } = trade
|
const { inputAmount: input, outputAmount: output, executionPrice } = trade
|
||||||
const { inputUSDC, outputUSDC, priceImpact } = useUSDCPriceImpact(input, output)
|
const { inputUSDC, outputUSDC, priceImpact, warning: priceImpactWarning } = useUSDCPriceImpact(input, output)
|
||||||
const isPriceImpactHigh = priceImpact && getPriceImpactWarning(priceImpact)
|
|
||||||
|
|
||||||
const ratio = useMemo(() => {
|
const ratio = useMemo(() => {
|
||||||
const [a, b] = flip ? [output, input] : [input, output]
|
const [a, b] = flip ? [output, input] : [input, output]
|
||||||
@ -111,13 +109,12 @@ export function Trade({ trade }: { trade: InterfaceTrade<Currency, Currency, Tra
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Tooltip placement="bottom" icon={isPriceImpactHigh ? AlertTriangle : Info}>
|
<Tooltip placement="bottom" icon={priceImpactWarning ? AlertTriangle : Info}>
|
||||||
<Column gap={0.75}>
|
<Column gap={0.75}>
|
||||||
{isPriceImpactHigh && (
|
{priceImpactWarning && (
|
||||||
<>
|
<>
|
||||||
<ThemedText.Caption>
|
<ThemedText.Caption>
|
||||||
The output amount is estimated at {toHumanReadablePriceImpact(priceImpact)} less than the input amount
|
The output amount is estimated at {priceImpact} less than the input amount due to high price impact
|
||||||
due to high price impact
|
|
||||||
</ThemedText.Caption>
|
</ThemedText.Caption>
|
||||||
<Rule />
|
<Rule />
|
||||||
</>
|
</>
|
||||||
|
@ -11,7 +11,7 @@ import { InterfaceTrade, TradeState } from 'state/routing/types'
|
|||||||
|
|
||||||
import { isAddress } from '../../../utils'
|
import { isAddress } from '../../../utils'
|
||||||
import useActiveWeb3React from '../useActiveWeb3React'
|
import useActiveWeb3React from '../useActiveWeb3React'
|
||||||
import useAllowedSlippage from '../useAllowedSlippage'
|
import useSlippage, { Slippage } from '../useSlippage'
|
||||||
import { useBestTrade } from './useBestTrade'
|
import { useBestTrade } from './useBestTrade'
|
||||||
|
|
||||||
interface SwapInfo {
|
interface SwapInfo {
|
||||||
@ -22,7 +22,7 @@ interface SwapInfo {
|
|||||||
trade?: InterfaceTrade<Currency, Currency, TradeType>
|
trade?: InterfaceTrade<Currency, Currency, TradeType>
|
||||||
state: TradeState
|
state: TradeState
|
||||||
}
|
}
|
||||||
allowedSlippage: Percent
|
slippage: Slippage
|
||||||
feeOptions: FeeOptions | undefined
|
feeOptions: FeeOptions | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,7 +89,7 @@ function useComputeSwapInfo(): SwapInfo {
|
|||||||
[trade.trade?.inputAmount, trade.trade?.outputAmount]
|
[trade.trade?.inputAmount, trade.trade?.outputAmount]
|
||||||
)
|
)
|
||||||
|
|
||||||
const allowedSlippage = useAllowedSlippage(trade.trade)
|
const slippage = useSlippage(trade.trade)
|
||||||
|
|
||||||
const inputError = useMemo(() => {
|
const inputError = useMemo(() => {
|
||||||
let inputError: ReactNode | undefined
|
let inputError: ReactNode | undefined
|
||||||
@ -116,14 +116,14 @@ function useComputeSwapInfo(): SwapInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// compare input balance to max input based on version
|
// compare input balance to max input based on version
|
||||||
const [balanceIn, amountIn] = [currencyBalances[Field.INPUT], trade.trade?.maximumAmountIn(allowedSlippage)]
|
const [balanceIn, amountIn] = [currencyBalances[Field.INPUT], trade.trade?.maximumAmountIn(slippage.allowed)]
|
||||||
|
|
||||||
if (balanceIn && amountIn && balanceIn.lessThan(amountIn)) {
|
if (balanceIn && amountIn && balanceIn.lessThan(amountIn)) {
|
||||||
inputError = <Trans>Insufficient {amountIn.currency.symbol} balance</Trans>
|
inputError = <Trans>Insufficient {amountIn.currency.symbol} balance</Trans>
|
||||||
}
|
}
|
||||||
|
|
||||||
return inputError
|
return inputError
|
||||||
}, [account, allowedSlippage, currencies, currencyBalances, parsedAmount, to, trade.trade])
|
}, [account, slippage.allowed, currencies, currencyBalances, parsedAmount, to, trade.trade])
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
@ -132,10 +132,10 @@ function useComputeSwapInfo(): SwapInfo {
|
|||||||
inputError,
|
inputError,
|
||||||
trade,
|
trade,
|
||||||
tradeCurrencyAmounts,
|
tradeCurrencyAmounts,
|
||||||
allowedSlippage,
|
slippage,
|
||||||
feeOptions,
|
feeOptions,
|
||||||
}),
|
}),
|
||||||
[currencies, currencyBalances, inputError, trade, tradeCurrencyAmounts, allowedSlippage, feeOptions]
|
[currencies, currencyBalances, inputError, trade, tradeCurrencyAmounts, slippage, feeOptions]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,7 +144,7 @@ const swapInfoAtom = atom<SwapInfo>({
|
|||||||
currencyBalances: {},
|
currencyBalances: {},
|
||||||
trade: { state: TradeState.INVALID },
|
trade: { state: TradeState.INVALID },
|
||||||
tradeCurrencyAmounts: {},
|
tradeCurrencyAmounts: {},
|
||||||
allowedSlippage: new Percent(0),
|
slippage: { auto: true, allowed: new Percent(0) },
|
||||||
feeOptions: undefined,
|
feeOptions: undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -11,12 +11,24 @@ export function toPercent(maxSlippage: number | undefined): Percent | undefined
|
|||||||
return new Percent(numerator, 10_000)
|
return new Percent(numerator, 10_000)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the user-inputted max slippage. */
|
export interface Slippage {
|
||||||
export default function useAllowedSlippage(trade: InterfaceTrade<Currency, Currency, TradeType> | undefined): Percent {
|
auto: boolean
|
||||||
const autoSlippage = useAutoSlippageTolerance(trade)
|
allowed: Percent
|
||||||
|
warning?: 'warning' | 'error'
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the allowed slippage, and whether it is auto-slippage. */
|
||||||
|
export default function useSlippage(trade: InterfaceTrade<Currency, Currency, TradeType> | undefined): Slippage {
|
||||||
|
const shouldUseAutoSlippage = useAtomValue(autoSlippageAtom)
|
||||||
|
const autoSlippage = useAutoSlippageTolerance(shouldUseAutoSlippage ? trade : undefined)
|
||||||
const maxSlippageValue = useAtomValue(maxSlippageAtom)
|
const maxSlippageValue = useAtomValue(maxSlippageAtom)
|
||||||
const maxSlippage = useMemo(() => toPercent(maxSlippageValue), [maxSlippageValue])
|
const maxSlippage = useMemo(() => toPercent(maxSlippageValue), [maxSlippageValue])
|
||||||
return useAtomValue(autoSlippageAtom) ? autoSlippage : maxSlippage ?? autoSlippage
|
return useMemo(() => {
|
||||||
|
const auto = shouldUseAutoSlippage || !maxSlippage
|
||||||
|
const allowed = shouldUseAutoSlippage ? autoSlippage : maxSlippage ?? autoSlippage
|
||||||
|
const warning = auto ? undefined : getSlippageWarning(allowed)
|
||||||
|
return { auto, allowed, warning }
|
||||||
|
}, [autoSlippage, maxSlippage, shouldUseAutoSlippage])
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MAX_VALID_SLIPPAGE = new Percent(1, 2)
|
export const MAX_VALID_SLIPPAGE = new Percent(1, 2)
|
@ -2,24 +2,36 @@ import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core'
|
|||||||
import { useUSDCValue } from 'hooks/useUSDCPrice'
|
import { useUSDCValue } from 'hooks/useUSDCPrice'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { computeFiatValuePriceImpact } from 'utils/computeFiatValuePriceImpact'
|
import { computeFiatValuePriceImpact } from 'utils/computeFiatValuePriceImpact'
|
||||||
|
import { getPriceImpactWarning } from 'utils/prices'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes input/output USDC equivalents and the price impact.
|
||||||
|
* Returns the price impact as a human readable string.
|
||||||
|
*/
|
||||||
export default function useUSDCPriceImpact(
|
export default function useUSDCPriceImpact(
|
||||||
inputAmount: CurrencyAmount<Currency> | undefined,
|
inputAmount: CurrencyAmount<Currency> | undefined,
|
||||||
outputAmount: CurrencyAmount<Currency> | undefined
|
outputAmount: CurrencyAmount<Currency> | undefined
|
||||||
): {
|
): {
|
||||||
inputUSDC?: CurrencyAmount<Token>
|
inputUSDC?: CurrencyAmount<Token>
|
||||||
outputUSDC?: CurrencyAmount<Token>
|
outputUSDC?: CurrencyAmount<Token>
|
||||||
priceImpact?: Percent
|
priceImpact?: string
|
||||||
|
warning?: 'warning' | 'error'
|
||||||
} {
|
} {
|
||||||
const inputUSDC = useUSDCValue(inputAmount) ?? undefined
|
const inputUSDC = useUSDCValue(inputAmount) ?? undefined
|
||||||
const outputUSDC = useUSDCValue(outputAmount) ?? undefined
|
const outputUSDC = useUSDCValue(outputAmount) ?? undefined
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
const priceImpact = computeFiatValuePriceImpact(inputUSDC, outputUSDC)
|
const priceImpact = computeFiatValuePriceImpact(inputUSDC, outputUSDC)
|
||||||
return { inputUSDC, outputUSDC, priceImpact }
|
const warning = getPriceImpactWarning(priceImpact)
|
||||||
|
return {
|
||||||
|
inputUSDC,
|
||||||
|
outputUSDC,
|
||||||
|
priceImpact: priceImpact && toHumanReadablePriceImpact(priceImpact),
|
||||||
|
warning,
|
||||||
|
}
|
||||||
}, [inputUSDC, outputUSDC])
|
}, [inputUSDC, outputUSDC])
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toHumanReadablePriceImpact(priceImpact: Percent): string {
|
function toHumanReadablePriceImpact(priceImpact: Percent): string {
|
||||||
const sign = priceImpact.lessThan(0) ? '+' : ''
|
const sign = priceImpact.lessThan(0) ? '+' : ''
|
||||||
const number = parseFloat(priceImpact.multiply(-1)?.toSignificant(3))
|
const number = parseFloat(priceImpact.multiply(-1)?.toSignificant(3))
|
||||||
return `${sign}${number}%`
|
return `${sign}${number}%`
|
||||||
|
Loading…
Reference in New Issue
Block a user