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:
Zach Pomerantz 2022-03-10 13:07:37 -08:00 committed by GitHub
parent b52273932a
commit fa163cb938
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 124 additions and 97 deletions

@ -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}%`