Commit 2c74c5f2 authored by Zach Pomerantz's avatar Zach Pomerantz Committed by GitHub

chore: include usdc in swap info (#3539)

* chore: refactor useComputeSwapInfo to include usdc

* chore: use passed usdc if able

* fix: fixture
parent cbc2ff66
import { useLingui } from '@lingui/react' import { useLingui } from '@lingui/react'
import { Currency, CurrencyAmount } from '@uniswap/sdk-core' import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
import { useUSDCValue } from 'hooks/useUSDCPrice'
import { loadingTransitionCss } from 'lib/css/loading' import { loadingTransitionCss } from 'lib/css/loading'
import { import {
useIsSwapFieldIndependent, useIsSwapFieldIndependent,
...@@ -70,18 +69,16 @@ export function useFormattedFieldAmount({ disabled, currencyAmount, fieldAmount ...@@ -70,18 +69,16 @@ export function useFormattedFieldAmount({ disabled, currencyAmount, fieldAmount
export default function Input({ disabled, focused }: InputProps) { export default function Input({ disabled, focused }: InputProps) {
const { i18n } = useLingui() const { i18n } = useLingui()
const { const {
currencyBalances: { [Field.INPUT]: balance }, [Field.INPUT]: { balance, amount: tradeCurrencyAmount, usdc },
trade: { state: tradeState }, trade: { state: tradeState },
tradeCurrencyAmounts: { [Field.INPUT]: swapInputCurrencyAmount },
} = useSwapInfo() } = useSwapInfo()
const inputUSDC = useUSDCValue(swapInputCurrencyAmount)
const [swapInputAmount, updateSwapInputAmount] = useSwapAmount(Field.INPUT) const [inputAmount, updateInputAmount] = useSwapAmount(Field.INPUT)
const [swapInputCurrency, updateSwapInputCurrency] = useSwapCurrency(Field.INPUT) const [inputCurrency, updateInputCurrency] = useSwapCurrency(Field.INPUT)
const inputCurrencyAmount = useSwapCurrencyAmount(Field.INPUT) const inputCurrencyAmount = useSwapCurrencyAmount(Field.INPUT)
// extract eagerly in case of reversal // extract eagerly in case of reversal
usePrefetchCurrencyColor(swapInputCurrency) usePrefetchCurrencyColor(inputCurrency)
const isRouteLoading = tradeState === TradeState.SYNCING || tradeState === TradeState.LOADING const isRouteLoading = tradeState === TradeState.SYNCING || tradeState === TradeState.LOADING
const isDependentField = !useIsSwapFieldIndependent(Field.INPUT) const isDependentField = !useIsSwapFieldIndependent(Field.INPUT)
...@@ -99,32 +96,30 @@ export default function Input({ disabled, focused }: InputProps) { ...@@ -99,32 +96,30 @@ export default function Input({ disabled, focused }: InputProps) {
const balanceColor = useMemo(() => { const balanceColor = useMemo(() => {
const insufficientBalance = const insufficientBalance =
balance && balance &&
(inputCurrencyAmount ? inputCurrencyAmount.greaterThan(balance) : swapInputCurrencyAmount?.greaterThan(balance)) (inputCurrencyAmount ? inputCurrencyAmount.greaterThan(balance) : tradeCurrencyAmount?.greaterThan(balance))
return insufficientBalance ? 'error' : undefined return insufficientBalance ? 'error' : undefined
}, [balance, inputCurrencyAmount, swapInputCurrencyAmount]) }, [balance, inputCurrencyAmount, tradeCurrencyAmount])
const amount = useFormattedFieldAmount({ const amount = useFormattedFieldAmount({
disabled, disabled,
currencyAmount: swapInputCurrencyAmount, currencyAmount: tradeCurrencyAmount,
fieldAmount: swapInputAmount, fieldAmount: inputAmount,
}) })
return ( return (
<InputColumn gap={0.5} approved={mockApproved}> <InputColumn gap={0.5} approved={mockApproved}>
<TokenInput <TokenInput
currency={swapInputCurrency} currency={inputCurrency}
amount={amount} amount={amount}
max={max} max={max}
disabled={disabled} disabled={disabled}
onChangeInput={updateSwapInputAmount} onChangeInput={updateInputAmount}
onChangeCurrency={updateSwapInputCurrency} onChangeCurrency={updateInputCurrency}
loading={isLoading} loading={isLoading}
> >
<ThemedText.Body2 color="secondary" userSelect> <ThemedText.Body2 color="secondary" userSelect>
<Row> <Row>
<USDC isLoading={isRouteLoading}> <USDC isLoading={isRouteLoading}>{usdc ? `$${formatCurrencyAmount(usdc, 6, 'en', 2)}` : '-'}</USDC>
{inputUSDC ? `$${formatCurrencyAmount(inputUSDC, 6, 'en', 2)}` : '-'}
</USDC>
{balance && ( {balance && (
<Balance color={balanceColor} focused={focused}> <Balance color={balanceColor} focused={focused}>
Balance: <span>{formatCurrencyAmount(balance, 4, i18n.locale)}</span> Balance: <span>{formatCurrencyAmount(balance, 4, i18n.locale)}</span>
......
...@@ -5,7 +5,6 @@ import { useAtomValue } from 'jotai/utils' ...@@ -5,7 +5,6 @@ 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 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 } from 'react' import { PropsWithChildren } from 'react'
...@@ -39,9 +38,9 @@ export default function Output({ disabled, focused, children }: PropsWithChildre ...@@ -39,9 +38,9 @@ export default function Output({ disabled, focused, children }: PropsWithChildre
const { i18n } = useLingui() const { i18n } = useLingui()
const { const {
currencyBalances: { [Field.OUTPUT]: balance }, [Field.OUTPUT]: { balance, amount: outputCurrencyAmount, usdc: outputUSDC },
trade: { state: tradeState }, trade: { state: tradeState },
tradeCurrencyAmounts: { [Field.INPUT]: inputCurrencyAmount, [Field.OUTPUT]: outputCurrencyAmount }, impact,
} = useSwapInfo() } = useSwapInfo()
const [swapOutputAmount, updateSwapOutputAmount] = useSwapAmount(Field.OUTPUT) const [swapOutputAmount, updateSwapOutputAmount] = useSwapAmount(Field.OUTPUT)
...@@ -58,12 +57,6 @@ export default function Output({ disabled, focused, children }: PropsWithChildre ...@@ -58,12 +57,6 @@ 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,
warning: priceImpactWarning,
} = useUSDCPriceImpact(inputCurrencyAmount, outputCurrencyAmount)
const amount = useFormattedFieldAmount({ const amount = useFormattedFieldAmount({
disabled, disabled,
currencyAmount: outputCurrencyAmount, currencyAmount: outputCurrencyAmount,
...@@ -90,7 +83,7 @@ export default function Output({ disabled, focused, children }: PropsWithChildre ...@@ -90,7 +83,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 && <ThemedText.Body2 color={priceImpactWarning}>({priceImpact})</ThemedText.Body2>} {impact.display && <ThemedText.Body2 color={impact.warning}>({impact.display})</ThemedText.Body2>}
</USDC> </USDC>
{balance && ( {balance && (
<Balance focused={focused}> <Balance focused={focused}>
......
import { useLingui } from '@lingui/react' import { useLingui } from '@lingui/react'
import { Trade } from '@uniswap/router-sdk' import { Trade } from '@uniswap/router-sdk'
import { Currency, CurrencyAmount, Token, TradeType } from '@uniswap/sdk-core' import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
import Row from 'lib/components/Row' import Row from 'lib/components/Row'
import { ThemedText } from 'lib/theme' import { ThemedText } from 'lib/theme'
import formatLocaleNumber from 'lib/utils/formatLocaleNumber' import formatLocaleNumber from 'lib/utils/formatLocaleNumber'
...@@ -11,7 +11,7 @@ import { TextButton } from '../Button' ...@@ -11,7 +11,7 @@ import { TextButton } from '../Button'
interface PriceProps { interface PriceProps {
trade: Trade<Currency, Currency, TradeType> trade: Trade<Currency, Currency, TradeType>
outputUSDC?: CurrencyAmount<Token> outputUSDC?: CurrencyAmount<Currency>
} }
/** Displays the price of a trade. If outputUSDC is included, also displays the unit price. */ /** Displays the price of a trade. If outputUSDC is included, also displays the unit price. */
......
...@@ -22,8 +22,11 @@ const UNI = (function () { ...@@ -22,8 +22,11 @@ const UNI = (function () {
function Fixture() { function Fixture() {
const setState = useUpdateAtom(swapAtom) const setState = useUpdateAtom(swapAtom)
const { const {
slippage, [Field.INPUT]: { usdc: inputUSDC },
[Field.OUTPUT]: { usdc: outputUSDC },
trade: { trade }, trade: { trade },
slippage,
impact,
} = useSwapInfo() } = useSwapInfo()
useEffect(() => { useEffect(() => {
...@@ -37,7 +40,14 @@ function Fixture() { ...@@ -37,7 +40,14 @@ function Fixture() {
return trade ? ( return trade ? (
<Modal color="dialog"> <Modal color="dialog">
<SummaryDialog onConfirm={() => void 0} trade={trade} slippage={slippage} /> <SummaryDialog
onConfirm={() => void 0}
trade={trade}
slippage={slippage}
inputUSDC={inputUSDC}
outputUSDC={outputUSDC}
impact={impact}
/>
</Modal> </Modal>
) : null ) : null
} }
......
...@@ -37,10 +37,10 @@ function Detail({ label, value, color }: DetailProps) { ...@@ -37,10 +37,10 @@ function Detail({ label, value, color }: DetailProps) {
interface DetailsProps { interface DetailsProps {
trade: Trade<Currency, Currency, TradeType> trade: Trade<Currency, Currency, TradeType>
slippage: { auto: boolean; allowed: Percent; warning?: Color } slippage: { auto: boolean; allowed: Percent; warning?: Color }
usdcPriceImpact: { priceImpact?: string; warning?: Color } priceImpact: { priceImpact?: string; warning?: Color }
} }
export default function Details({ trade, slippage, usdcPriceImpact }: DetailsProps) { export default function Details({ trade, slippage, priceImpact }: 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
...@@ -61,8 +61,8 @@ export default function Details({ trade, slippage, usdcPriceImpact }: DetailsPro ...@@ -61,8 +61,8 @@ export default function Details({ trade, slippage, usdcPriceImpact }: DetailsPro
} }
} }
if (usdcPriceImpact.priceImpact) { if (priceImpact.priceImpact) {
rows.push([t`Price impact`, usdcPriceImpact.priceImpact, usdcPriceImpact.warning]) rows.push([t`Price impact`, priceImpact.priceImpact, priceImpact.warning])
} }
if (lpFeeAmount) { if (lpFeeAmount) {
...@@ -85,7 +85,7 @@ export default function Details({ trade, slippage, usdcPriceImpact }: DetailsPro ...@@ -85,7 +85,7 @@ export default function Details({ trade, slippage, usdcPriceImpact }: DetailsPro
return rows return rows
}, [ }, [
feeOptions, feeOptions,
usdcPriceImpact, priceImpact,
lpFeeAmount, lpFeeAmount,
trade, trade,
slippage, slippage,
......
import { useLingui } from '@lingui/react' import { useLingui } from '@lingui/react'
import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core' import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
import useUSDCPriceImpact from 'lib/hooks/useUSDCPriceImpact' import { PriceImpact } 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 { PropsWithChildren } from 'react' import { PropsWithChildren } from 'react'
...@@ -12,7 +12,7 @@ import TokenImg from '../../TokenImg' ...@@ -12,7 +12,7 @@ import TokenImg from '../../TokenImg'
interface TokenValueProps { interface TokenValueProps {
input: CurrencyAmount<Currency> input: CurrencyAmount<Currency>
usdc?: CurrencyAmount<Token> usdc?: CurrencyAmount<Currency>
} }
function TokenValue({ input, usdc, children }: PropsWithChildren<TokenValueProps>) { function TokenValue({ input, usdc, children }: PropsWithChildren<TokenValueProps>) {
...@@ -40,18 +40,18 @@ function TokenValue({ input, usdc, children }: PropsWithChildren<TokenValueProps ...@@ -40,18 +40,18 @@ function TokenValue({ input, usdc, children }: PropsWithChildren<TokenValueProps
interface SummaryProps { interface SummaryProps {
input: CurrencyAmount<Currency> input: CurrencyAmount<Currency>
output: CurrencyAmount<Currency> output: CurrencyAmount<Currency>
usdcPriceImpact?: ReturnType<typeof useUSDCPriceImpact> inputUSDC?: CurrencyAmount<Currency>
outputUSDC?: CurrencyAmount<Currency>
priceImpact?: PriceImpact
} }
export default function Summary({ input, output, usdcPriceImpact }: SummaryProps) { export default function Summary({ input, output, inputUSDC, outputUSDC, priceImpact }: SummaryProps) {
const { inputUSDC, outputUSDC, priceImpact, warning: priceImpactWarning } = usdcPriceImpact || {}
return ( return (
<Row gap={usdcPriceImpact ? 1 : 0.25}> <Row gap={priceImpact ? 1 : 0.25}>
<TokenValue input={input} usdc={inputUSDC} /> <TokenValue input={input} usdc={inputUSDC} />
<ArrowRight /> <ArrowRight />
<TokenValue input={output} usdc={outputUSDC}> <TokenValue input={output} usdc={outputUSDC}>
{priceImpact && <ThemedText.Caption color={priceImpactWarning}>({priceImpact})</ThemedText.Caption>} {priceImpact && <ThemedText.Caption color={priceImpact.warning}>({priceImpact.display})</ThemedText.Caption>}
</TokenValue> </TokenValue>
</Row> </Row>
) )
......
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, TradeType } from '@uniswap/sdk-core' import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
import ActionButton, { Action } from 'lib/components/ActionButton' import ActionButton, { Action } from 'lib/components/ActionButton'
import Column from 'lib/components/Column' import Column from 'lib/components/Column'
import { Header } from 'lib/components/Dialog' import { Header } from 'lib/components/Dialog'
import Expando from 'lib/components/Expando' import Expando from 'lib/components/Expando'
import Row from 'lib/components/Row' import Row from 'lib/components/Row'
import { Slippage } from 'lib/hooks/useSlippage' import { Slippage } from 'lib/hooks/useSlippage'
import useUSDCPriceImpact from 'lib/hooks/useUSDCPriceImpact' import { PriceImpact } from 'lib/hooks/useUSDCPriceImpact'
import { AlertTriangle, BarChart, Info } from 'lib/icons' import { AlertTriangle, BarChart, Info } from 'lib/icons'
import styled, { Color, ThemedText } from 'lib/theme' import styled, { Color, ThemedText } from 'lib/theme'
import { useCallback, useMemo, useState } from 'react' import { useCallback, useMemo, useState } from 'react'
...@@ -130,12 +130,14 @@ function ConfirmButton({ ...@@ -130,12 +130,14 @@ function ConfirmButton({
interface SummaryDialogProps { interface SummaryDialogProps {
trade: Trade<Currency, Currency, TradeType> trade: Trade<Currency, Currency, TradeType>
slippage: Slippage slippage: Slippage
inputUSDC?: CurrencyAmount<Currency>
outputUSDC?: CurrencyAmount<Currency>
impact: PriceImpact
onConfirm: () => void onConfirm: () => void
} }
export function SummaryDialog({ trade, slippage, onConfirm }: SummaryDialogProps) { export function SummaryDialog({ trade, slippage, inputUSDC, outputUSDC, impact, onConfirm }: SummaryDialogProps) {
const { inputAmount, outputAmount } = trade const { inputAmount, outputAmount } = trade
const usdcPriceImpact = useUSDCPriceImpact(inputAmount, outputAmount)
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const onExpand = useCallback(() => setOpen((open) => !open), []) const onExpand = useCallback(() => setOpen((open) => !open), [])
...@@ -145,22 +147,28 @@ export function SummaryDialog({ trade, slippage, onConfirm }: SummaryDialogProps ...@@ -145,22 +147,28 @@ export function SummaryDialog({ trade, slippage, onConfirm }: SummaryDialogProps
<Header title={<Trans>Swap summary</Trans>} ruled /> <Header title={<Trans>Swap summary</Trans>} ruled />
<Body flex align="stretch" padded gap={0.75} open={open}> <Body flex align="stretch" padded gap={0.75} open={open}>
<Heading gap={0.75} flex justify="center"> <Heading gap={0.75} flex justify="center">
<Summary input={inputAmount} output={outputAmount} usdcPriceImpact={usdcPriceImpact} /> <Summary
input={inputAmount}
output={outputAmount}
inputUSDC={inputUSDC}
outputUSDC={outputUSDC}
priceImpact={impact}
/>
<Price trade={trade} /> <Price trade={trade} />
</Heading> </Heading>
<Column gap={open ? 0 : 0.75} style={{ transition: 'gap 0.25s' }}> <Column gap={open ? 0 : 0.75} style={{ transition: 'gap 0.25s' }}>
<Expando <Expando
title={<Subhead priceImpact={usdcPriceImpact} slippage={slippage} />} title={<Subhead priceImpact={impact} slippage={slippage} />}
open={open} open={open}
onExpand={onExpand} onExpand={onExpand}
height={7.25} height={7.25}
> >
<Details trade={trade} slippage={slippage} usdcPriceImpact={usdcPriceImpact} /> <Details trade={trade} slippage={slippage} priceImpact={impact} />
</Expando> </Expando>
<Footing> <Footing>
<Estimate trade={trade} slippage={slippage} /> <Estimate trade={trade} slippage={slippage} />
</Footing> </Footing>
<ConfirmButton trade={trade} highPriceImpact={usdcPriceImpact.warning === 'error'} onConfirm={onConfirm} /> <ConfirmButton trade={trade} highPriceImpact={impact.warning === 'error'} onConfirm={onConfirm} />
</Column> </Column>
</Body> </Body>
</> </>
......
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import { Token } from '@uniswap/sdk-core' import { Token } from '@uniswap/sdk-core'
import { useUpdateAtom } from 'jotai/utils' import { useAtomValue, useUpdateAtom } from 'jotai/utils'
import { WrapErrorText } from 'lib/components/Swap/WrapErrorText' import { WrapErrorText } from 'lib/components/Swap/WrapErrorText'
import { useSwapCurrencyAmount, useSwapInfo, useSwapTradeType } from 'lib/hooks/swap' import { useSwapCurrencyAmount, useSwapInfo, useSwapTradeType } from 'lib/hooks/swap'
import { import {
...@@ -15,7 +15,7 @@ import { useAddTransaction, usePendingApproval } from 'lib/hooks/transactions' ...@@ -15,7 +15,7 @@ import { useAddTransaction, usePendingApproval } from 'lib/hooks/transactions'
import useActiveWeb3React from 'lib/hooks/useActiveWeb3React' import useActiveWeb3React from 'lib/hooks/useActiveWeb3React'
import useTransactionDeadline from 'lib/hooks/useTransactionDeadline' import useTransactionDeadline from 'lib/hooks/useTransactionDeadline'
import { Spinner } from 'lib/icons' import { Spinner } from 'lib/icons'
import { displayTxHashAtom, Field } from 'lib/state/swap' import { displayTxHashAtom, feeOptionsAtom, Field } from 'lib/state/swap'
import { TransactionType } from 'lib/state/transactions' import { TransactionType } from 'lib/state/transactions'
import { useTheme } from 'lib/theme' import { useTheme } from 'lib/theme'
import { memo, useCallback, useEffect, useMemo, useState } from 'react' import { memo, useCallback, useEffect, useMemo, useState } from 'react'
...@@ -41,13 +41,18 @@ export default memo(function SwapButton({ disabled }: SwapButtonProps) { ...@@ -41,13 +41,18 @@ export default memo(function SwapButton({ disabled }: SwapButtonProps) {
const { tokenColorExtraction } = useTheme() const { tokenColorExtraction } = useTheme()
const { const {
slippage, [Field.INPUT]: {
currencies: { [Field.INPUT]: inputCurrency }, currency: inputCurrency,
currencyBalances: { [Field.INPUT]: inputCurrencyBalance }, amount: inputTradeCurrencyAmount,
feeOptions, balance: inputCurrencyBalance,
usdc: inputUSDC,
},
[Field.OUTPUT]: { amount: outputTradeCurrencyAmount, usdc: outputUSDC },
trade, trade,
tradeCurrencyAmounts: { [Field.INPUT]: inputTradeCurrencyAmount, [Field.OUTPUT]: outputTradeCurrencyAmount }, slippage,
impact,
} = useSwapInfo() } = useSwapInfo()
const feeOptions = useAtomValue(feeOptionsAtom)
const tradeType = useSwapTradeType() const tradeType = useSwapTradeType()
...@@ -240,7 +245,14 @@ export default memo(function SwapButton({ disabled }: SwapButtonProps) { ...@@ -240,7 +245,14 @@ export default memo(function SwapButton({ disabled }: SwapButtonProps) {
</ActionButton> </ActionButton>
{activeTrade && ( {activeTrade && (
<Dialog color="dialog" onClose={handleDialogClose}> <Dialog color="dialog" onClose={handleDialogClose}>
<SummaryDialog trade={activeTrade} slippage={slippage} onConfirm={onConfirm} /> <SummaryDialog
trade={activeTrade}
slippage={slippage}
inputUSDC={inputUSDC}
outputUSDC={outputUSDC}
impact={impact}
onConfirm={onConfirm}
/>
</Dialog> </Dialog>
)} )}
</> </>
......
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import { Currency, TradeType } from '@uniswap/sdk-core' import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
import Column from 'lib/components/Column' import Column from 'lib/components/Column'
import Rule from 'lib/components/Rule' 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 from 'lib/hooks/useUSDCPriceImpact' import { PriceImpact } 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 } from 'react' import { ReactNode, useCallback } from 'react'
...@@ -77,17 +77,23 @@ export function WrapCurrency({ loading, wrapType }: { loading: boolean; wrapType ...@@ -77,17 +77,23 @@ export function WrapCurrency({ loading, wrapType }: { loading: boolean; wrapType
return <Caption icon={Info} caption={<WrapText />} /> return <Caption icon={Info} caption={<WrapText />} />
} }
export function Trade({ trade }: { trade: InterfaceTrade<Currency, Currency, TradeType> }) { export function Trade({
const { inputAmount: input, outputAmount: output } = trade trade,
const { outputUSDC, priceImpact, warning: priceImpactWarning } = useUSDCPriceImpact(input, output) outputUSDC,
impact,
}: {
trade: InterfaceTrade<Currency, Currency, TradeType>
outputUSDC?: CurrencyAmount<Currency>
impact: PriceImpact
}) {
return ( return (
<> <>
<Tooltip placement="bottom" icon={priceImpactWarning ? AlertTriangle : Info}> <Tooltip placement="bottom" icon={impact.warning ? AlertTriangle : Info}>
<Column gap={0.75}> <Column gap={0.75}>
{priceImpactWarning && ( {impact.warning && (
<> <>
<ThemedText.Caption> <ThemedText.Caption>
The output amount is estimated at {priceImpact} less than the input amount due to high price impact The output amount is estimated at {impact.display} less than the input amount due to high price impact
</ThemedText.Caption> </ThemedText.Caption>
<Rule /> <Rule />
</> </>
......
...@@ -20,9 +20,10 @@ const ToolbarRow = styled(Row)` ...@@ -20,9 +20,10 @@ const ToolbarRow = styled(Row)`
export default memo(function Toolbar({ disabled }: { disabled?: boolean }) { export default memo(function Toolbar({ disabled }: { disabled?: boolean }) {
const { chainId } = useActiveWeb3React() const { chainId } = useActiveWeb3React()
const { const {
[Field.INPUT]: { currency: inputCurrency, balance },
[Field.OUTPUT]: { currency: outputCurrency, usdc: outputUSDC },
trade: { trade, state }, trade: { trade, state },
currencies: { [Field.INPUT]: inputCurrency, [Field.OUTPUT]: outputCurrency }, impact,
currencyBalances: { [Field.INPUT]: balance },
} = useSwapInfo() } = useSwapInfo()
const isRouteLoading = state === TradeState.SYNCING || state === TradeState.LOADING const isRouteLoading = state === TradeState.SYNCING || state === TradeState.LOADING
const isAmountPopulated = useIsAmountPopulated() const isAmountPopulated = useIsAmountPopulated()
...@@ -50,7 +51,7 @@ export default memo(function Toolbar({ disabled }: { disabled?: boolean }) { ...@@ -50,7 +51,7 @@ export default memo(function Toolbar({ disabled }: { disabled?: boolean }) {
return <Caption.InsufficientBalance currency={trade.inputAmount.currency} /> return <Caption.InsufficientBalance currency={trade.inputAmount.currency} />
} }
if (trade.inputAmount && trade.outputAmount) { if (trade.inputAmount && trade.outputAmount) {
return <Caption.Trade trade={trade} /> return <Caption.Trade trade={trade} outputUSDC={outputUSDC} impact={impact} />
} }
} }
...@@ -59,10 +60,12 @@ export default memo(function Toolbar({ disabled }: { disabled?: boolean }) { ...@@ -59,10 +60,12 @@ export default memo(function Toolbar({ disabled }: { disabled?: boolean }) {
balance, balance,
chainId, chainId,
disabled, disabled,
impact,
inputCurrency, inputCurrency,
isAmountPopulated, isAmountPopulated,
isRouteLoading, isRouteLoading,
outputCurrency, outputCurrency,
outputUSDC,
trade, trade,
wrapLoading, wrapLoading,
wrapType, wrapType,
......
import { Trans } from '@lingui/macro'
import { Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core' import { Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core'
import { FeeOptions } from '@uniswap/v3-sdk'
import { atom } from 'jotai' import { atom } from 'jotai'
import { useAtomValue, useUpdateAtom } from 'jotai/utils' import { useAtomValue, useUpdateAtom } from 'jotai/utils'
import { useCurrencyBalances } from 'lib/hooks/useCurrencyBalance' import { useCurrencyBalances } from 'lib/hooks/useCurrencyBalance'
import { feeOptionsAtom, Field, swapAtom } from 'lib/state/swap' import { Field, swapAtom } from 'lib/state/swap'
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount' import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
import { ReactNode, useEffect, useMemo } from 'react' import { useEffect, useMemo } from 'react'
import { InterfaceTrade, TradeState } from 'state/routing/types' import { InterfaceTrade, TradeState } from 'state/routing/types'
import { isAddress } from '../../../utils'
import useActiveWeb3React from '../useActiveWeb3React' import useActiveWeb3React from '../useActiveWeb3React'
import useSlippage, { Slippage } from '../useSlippage' import useSlippage, { Slippage } from '../useSlippage'
import useUSDCPriceImpact, { PriceImpact } from '../useUSDCPriceImpact'
import { useBestTrade } from './useBestTrade' import { useBestTrade } from './useBestTrade'
import useWrapCallback, { WrapType } from './useWrapCallback' import useWrapCallback, { WrapType } from './useWrapCallback'
interface SwapField {
currency?: Currency
amount?: CurrencyAmount<Currency>
balance?: CurrencyAmount<Currency>
usdc?: CurrencyAmount<Currency>
}
interface SwapInfo { interface SwapInfo {
currencies: { [field in Field]?: Currency } [Field.INPUT]: SwapField
currencyBalances: { [field in Field]?: CurrencyAmount<Currency> } [Field.OUTPUT]: SwapField
tradeCurrencyAmounts: { [field in Field]?: CurrencyAmount<Currency> }
trade: { trade: {
trade?: InterfaceTrade<Currency, Currency, TradeType> trade?: InterfaceTrade<Currency, Currency, TradeType>
state: TradeState state: TradeState
} }
slippage: Slippage slippage: Slippage
feeOptions: FeeOptions | undefined impact: PriceImpact
}
const BAD_RECIPIENT_ADDRESSES: { [address: string]: true } = {
'0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f': true, // v2 factory
'0xf164fC0Ec4E93095b804a4795bBe1e041497b92a': true, // v2 router 01
'0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D': true, // v2 router 02
} }
// from the current swap inputs, compute the best trade and return it. // from the current swap inputs, compute the best trade and return it.
...@@ -38,106 +36,63 @@ function useComputeSwapInfo(): SwapInfo { ...@@ -38,106 +36,63 @@ function useComputeSwapInfo(): SwapInfo {
const { account } = useActiveWeb3React() const { account } = useActiveWeb3React()
const { type: wrapType } = useWrapCallback() const { type: wrapType } = useWrapCallback()
const isWrapping = wrapType === WrapType.WRAP || wrapType === WrapType.UNWRAP const isWrapping = wrapType === WrapType.WRAP || wrapType === WrapType.UNWRAP
const { const { independentField, amount, [Field.INPUT]: currencyIn, [Field.OUTPUT]: currencyOut } = useAtomValue(swapAtom)
independentField,
amount,
[Field.INPUT]: inputCurrency,
[Field.OUTPUT]: outputCurrency,
} = useAtomValue(swapAtom)
const isExactIn = independentField === Field.INPUT const isExactIn = independentField === Field.INPUT
const feeOptions = useAtomValue(feeOptionsAtom)
const parsedAmount = useMemo( const parsedAmount = useMemo(
() => tryParseCurrencyAmount(amount, (isExactIn ? inputCurrency : outputCurrency) ?? undefined), () => tryParseCurrencyAmount(amount, (isExactIn ? currencyIn : currencyOut) ?? undefined),
[inputCurrency, isExactIn, outputCurrency, amount] [amount, isExactIn, currencyIn, currencyOut]
) )
// TODO(ianlapham): this would eventually be replaced with routing api logic.
const trade = useBestTrade( const trade = useBestTrade(
isExactIn ? TradeType.EXACT_INPUT : TradeType.EXACT_OUTPUT, isExactIn ? TradeType.EXACT_INPUT : TradeType.EXACT_OUTPUT,
parsedAmount, parsedAmount,
(isExactIn ? outputCurrency : inputCurrency) ?? undefined (isExactIn ? currencyOut : currencyIn) ?? undefined
)
const tradeCurrencyAmounts = useMemo(
() => ({
// Use same amount for input and output if user is wrapping.
[Field.INPUT]: isWrapping || isExactIn ? parsedAmount : trade.trade?.inputAmount,
[Field.OUTPUT]: isWrapping || !isExactIn ? parsedAmount : trade.trade?.outputAmount,
}),
[isExactIn, isWrapping, parsedAmount, trade.trade?.inputAmount, trade.trade?.outputAmount]
) )
const slippage = useSlippage(trade.trade)
const currencies = useMemo( const amountIn = useMemo(
() => ({ [Field.INPUT]: inputCurrency, [Field.OUTPUT]: outputCurrency }), () => (isWrapping || isExactIn ? parsedAmount : trade.trade?.inputAmount),
[inputCurrency, outputCurrency] [isExactIn, isWrapping, parsedAmount, trade.trade?.inputAmount]
) )
const [inputCurrencyBalance, outputCurrencyBalance] = useCurrencyBalances( const amountOut = useMemo(
account, () => (isWrapping || !isExactIn ? parsedAmount : trade.trade?.outputAmount),
useMemo(() => [inputCurrency, outputCurrency], [inputCurrency, outputCurrency]) [isExactIn, isWrapping, parsedAmount, trade.trade?.outputAmount]
) )
const currencyBalances = useMemo( const [balanceIn, balanceOut] = useCurrencyBalances(
() => ({ account,
[Field.INPUT]: inputCurrencyBalance, useMemo(() => [currencyIn, currencyOut], [currencyIn, currencyOut])
[Field.OUTPUT]: outputCurrencyBalance,
}),
[inputCurrencyBalance, outputCurrencyBalance]
) )
const inputError = useMemo(() => { const slippage = useSlippage(trade.trade)
let inputError: ReactNode | undefined const { inputUSDC: usdcIn, outputUSDC: usdcOut, priceImpact: impact } = useUSDCPriceImpact(amountIn, amountOut)
if (!account) {
inputError = <Trans>Connect Wallet</Trans>
}
if (!currencies[Field.INPUT] || !currencies[Field.OUTPUT]) {
inputError = inputError ?? <Trans>Select a token</Trans>
}
if (!parsedAmount) {
inputError = inputError ?? <Trans>Enter an amount</Trans>
}
const formattedAddress = isAddress(account)
if (!account || !formattedAddress) {
inputError = inputError ?? <Trans>Enter a recipient</Trans>
} else {
if (BAD_RECIPIENT_ADDRESSES[formattedAddress]) {
inputError = inputError ?? <Trans>Invalid recipient</Trans>
}
}
// compare input balance to max input based on version
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, slippage.allowed, currencies, currencyBalances, parsedAmount, trade.trade])
return useMemo( return useMemo(
() => ({ () => ({
currencies, [Field.INPUT]: {
currencyBalances, currency: currencyIn,
inputError, amount: amountIn,
balance: balanceIn,
usdc: usdcIn,
},
[Field.OUTPUT]: {
currency: currencyOut,
amount: amountOut,
balance: balanceOut,
usdc: usdcOut,
},
trade, trade,
tradeCurrencyAmounts,
slippage, slippage,
feeOptions, impact,
}), }),
[currencies, currencyBalances, inputError, trade, tradeCurrencyAmounts, slippage, feeOptions] [amountIn, amountOut, balanceIn, balanceOut, currencyIn, currencyOut, impact, slippage, trade, usdcIn, usdcOut]
) )
} }
const swapInfoAtom = atom<SwapInfo>({ const swapInfoAtom = atom<SwapInfo>({
currencies: {}, [Field.INPUT]: {},
currencyBalances: {}, [Field.OUTPUT]: {},
trade: { state: TradeState.INVALID }, trade: { state: TradeState.INVALID },
tradeCurrencyAmounts: {},
slippage: { auto: true, allowed: new Percent(0) }, slippage: { auto: true, allowed: new Percent(0) },
feeOptions: undefined, impact: {},
}) })
export function SwapInfoUpdater() { export function SwapInfoUpdater() {
......
...@@ -4,6 +4,11 @@ import { useMemo } from 'react' ...@@ -4,6 +4,11 @@ import { useMemo } from 'react'
import { computeFiatValuePriceImpact } from 'utils/computeFiatValuePriceImpact' import { computeFiatValuePriceImpact } from 'utils/computeFiatValuePriceImpact'
import { getPriceImpactWarning } from 'utils/prices' import { getPriceImpactWarning } from 'utils/prices'
export interface PriceImpact {
display?: string
warning?: 'warning' | 'error'
}
/** /**
* Computes input/output USDC equivalents and the price impact. * Computes input/output USDC equivalents and the price impact.
* Returns the price impact as a human readable string. * Returns the price impact as a human readable string.
...@@ -14,8 +19,7 @@ export default function useUSDCPriceImpact( ...@@ -14,8 +19,7 @@ export default function useUSDCPriceImpact(
): { ): {
inputUSDC?: CurrencyAmount<Token> inputUSDC?: CurrencyAmount<Token>
outputUSDC?: CurrencyAmount<Token> outputUSDC?: CurrencyAmount<Token>
priceImpact?: string priceImpact: PriceImpact
warning?: 'warning' | 'error'
} { } {
const inputUSDC = useUSDCValue(inputAmount) ?? undefined const inputUSDC = useUSDCValue(inputAmount) ?? undefined
const outputUSDC = useUSDCValue(outputAmount) ?? undefined const outputUSDC = useUSDCValue(outputAmount) ?? undefined
...@@ -25,8 +29,11 @@ export default function useUSDCPriceImpact( ...@@ -25,8 +29,11 @@ export default function useUSDCPriceImpact(
return { return {
inputUSDC, inputUSDC,
outputUSDC, outputUSDC,
priceImpact: priceImpact && toHumanReadablePriceImpact(priceImpact), priceImpact: {
warning, priceImpact,
display: priceImpact && toHumanReadablePriceImpact(priceImpact),
warning,
},
} }
}, [inputUSDC, outputUSDC]) }, [inputUSDC, outputUSDC])
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment