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