refactor: swap line items and tooltips decomposition (#7390)
* refactor: swap line items and tooltips decomposition * test: test line items directly * refactor: added tooltip prop * refactor: preview trade logic * fix: percentage color * lint * fix: exchange rate alignment * fix: initial pr comments * test: fix snapshots * refactor: var naming * fix: uneeded dep array var * refactor: small nit
This commit is contained in:
parent
55a509cad8
commit
82aaf0784a
@ -32,6 +32,13 @@ export const LoadingRows = styled.div`
|
||||
}
|
||||
`
|
||||
|
||||
export const LoadingRow = styled.div<{ height: number; width: number }>`
|
||||
${shimmerMixin}
|
||||
border-radius: 12px;
|
||||
height: ${({ height }) => height}px;
|
||||
width: ${({ width }) => width}px;
|
||||
`
|
||||
|
||||
export const loadingOpacityMixin = css<{ $loading: boolean }>`
|
||||
filter: ${({ $loading }) => ($loading ? 'grayscale(1)' : 'none')};
|
||||
opacity: ${({ $loading }) => ($loading ? '0.6' : '1')};
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { QuoteMethod, SubmittableTrade } from 'state/routing/types'
|
||||
import { isUniswapXTrade } from 'state/routing/utils'
|
||||
import { DefaultTheme } from 'styled-components'
|
||||
import { ThemedText } from 'theme/components'
|
||||
|
||||
import UniswapXRouterLabel from './UniswapXRouterLabel'
|
||||
|
||||
export default function RouterLabel({ trade }: { trade: SubmittableTrade }) {
|
||||
export default function RouterLabel({ trade, color }: { trade: SubmittableTrade; color?: keyof DefaultTheme }) {
|
||||
if (isUniswapXTrade(trade)) {
|
||||
return (
|
||||
<UniswapXRouterLabel>
|
||||
@ -13,7 +14,7 @@ export default function RouterLabel({ trade }: { trade: SubmittableTrade }) {
|
||||
)
|
||||
}
|
||||
if (trade.quoteMethod === QuoteMethod.CLIENT_SIDE || trade.quoteMethod === QuoteMethod.CLIENT_SIDE_FALLBACK) {
|
||||
return <ThemedText.BodySmall>Uniswap Client</ThemedText.BodySmall>
|
||||
return <ThemedText.BodySmall color={color}>Uniswap Client</ThemedText.BodySmall>
|
||||
}
|
||||
return <ThemedText.BodySmall>Uniswap API</ThemedText.BodySmall>
|
||||
return <ThemedText.BodySmall color={color}>Uniswap API</ThemedText.BodySmall>
|
||||
}
|
||||
|
@ -77,9 +77,11 @@ type MouseoverTooltipProps = Omit<PopoverProps, 'content' | 'show'> &
|
||||
timeout?: number
|
||||
placement?: PopoverProps['placement']
|
||||
onOpen?: () => void
|
||||
forceShow?: boolean
|
||||
}>
|
||||
|
||||
export function MouseoverTooltip({ text, disabled, children, onOpen, timeout, ...rest }: MouseoverTooltipProps) {
|
||||
export function MouseoverTooltip(props: MouseoverTooltipProps) {
|
||||
const { text, disabled, children, onOpen, forceShow, timeout, ...rest } = props
|
||||
const [show, setShow] = useState(false)
|
||||
const open = () => {
|
||||
setShow(true)
|
||||
@ -101,7 +103,14 @@ export function MouseoverTooltip({ text, disabled, children, onOpen, timeout, ..
|
||||
}, [timeout, show])
|
||||
|
||||
return (
|
||||
<Tooltip {...rest} open={open} close={close} disabled={disabled} show={show} text={disabled ? null : text}>
|
||||
<Tooltip
|
||||
{...rest}
|
||||
open={open}
|
||||
close={close}
|
||||
disabled={disabled}
|
||||
show={forceShow || show}
|
||||
text={disabled ? null : text}
|
||||
>
|
||||
<div onMouseEnter={disabled ? noop : open} onMouseLeave={disabled || timeout ? noop : close}>
|
||||
{children}
|
||||
</div>
|
||||
|
@ -1,38 +0,0 @@
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { TEST_ALLOWED_SLIPPAGE, TEST_TRADE_EXACT_INPUT, TEST_TRADE_EXACT_OUTPUT } from 'test-utils/constants'
|
||||
import { act, render, screen } from 'test-utils/render'
|
||||
|
||||
import { AdvancedSwapDetails } from './AdvancedSwapDetails'
|
||||
|
||||
describe('AdvancedSwapDetails.tsx', () => {
|
||||
it('matches base snapshot', () => {
|
||||
const { asFragment } = render(
|
||||
<AdvancedSwapDetails trade={TEST_TRADE_EXACT_INPUT} allowedSlippage={TEST_ALLOWED_SLIPPAGE} />
|
||||
)
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('renders correct copy on mouseover', async () => {
|
||||
render(<AdvancedSwapDetails trade={TEST_TRADE_EXACT_INPUT} allowedSlippage={TEST_ALLOWED_SLIPPAGE} />)
|
||||
await act(() => userEvent.hover(screen.getByText('Expected output')))
|
||||
expect(await screen.getByText(/The amount you expect to receive at the current market price./i)).toBeVisible()
|
||||
await act(() => userEvent.hover(screen.getByText(/Minimum output/i)))
|
||||
expect(await screen.getByText(/The minimum amount you are guaranteed to receive./i)).toBeVisible()
|
||||
})
|
||||
|
||||
it('renders correct tooltips for test trade with exact output and gas use estimate USD', async () => {
|
||||
TEST_TRADE_EXACT_OUTPUT.gasUseEstimateUSD = 1.0
|
||||
render(<AdvancedSwapDetails trade={TEST_TRADE_EXACT_OUTPUT} allowedSlippage={TEST_ALLOWED_SLIPPAGE} />)
|
||||
await act(() => userEvent.hover(screen.getByText(/Maximum input/i)))
|
||||
expect(await screen.getByText(/The minimum amount you are guaranteed to receive./i)).toBeVisible()
|
||||
await act(() => userEvent.hover(screen.getByText('Network fee')))
|
||||
expect(await screen.getByText(/The fee paid to miners who process your transaction./i)).toBeVisible()
|
||||
})
|
||||
|
||||
it('renders loading rows when syncing', async () => {
|
||||
render(
|
||||
<AdvancedSwapDetails trade={TEST_TRADE_EXACT_OUTPUT} allowedSlippage={TEST_ALLOWED_SLIPPAGE} syncing={true} />
|
||||
)
|
||||
expect(screen.getAllByTestId('loading-rows').length).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
@ -1,228 +0,0 @@
|
||||
import { Plural, Trans } from '@lingui/macro'
|
||||
import { InterfaceElementName, SwapEventName } from '@uniswap/analytics-events'
|
||||
import { Percent, TradeType } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { sendAnalyticsEvent } from 'analytics'
|
||||
import { LoadingRows } from 'components/Loader/styled'
|
||||
import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains'
|
||||
import { ZERO_PERCENT } from 'constants/misc'
|
||||
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
|
||||
import { ClassicTrade, InterfaceTrade } from 'state/routing/types'
|
||||
import { getTransactionCount, isClassicTrade, isSubmittableTrade } from 'state/routing/utils'
|
||||
import { ExternalLink, Separator, ThemedText } from 'theme/components'
|
||||
import { NumberType, useFormatter } from 'utils/formatNumbers'
|
||||
|
||||
import Column from '../Column'
|
||||
import RouterLabel from '../RouterLabel'
|
||||
import { RowBetween, RowFixed } from '../Row'
|
||||
import { MouseoverTooltip, TooltipSize } from '../Tooltip'
|
||||
import { GasBreakdownTooltip } from './GasBreakdownTooltip'
|
||||
import SwapRoute from './SwapRoute'
|
||||
|
||||
interface AdvancedSwapDetailsProps {
|
||||
trade: InterfaceTrade
|
||||
allowedSlippage: Percent
|
||||
syncing?: boolean
|
||||
}
|
||||
|
||||
function TextWithLoadingPlaceholder({
|
||||
syncing,
|
||||
width,
|
||||
children,
|
||||
}: {
|
||||
syncing: boolean
|
||||
width: number
|
||||
children: JSX.Element
|
||||
}) {
|
||||
return syncing ? (
|
||||
<LoadingRows data-testid="loading-rows">
|
||||
<div style={{ height: '15px', width: `${width}px` }} />
|
||||
</LoadingRows>
|
||||
) : (
|
||||
children
|
||||
)
|
||||
}
|
||||
|
||||
export function AdvancedSwapDetails({ trade, allowedSlippage, syncing = false }: AdvancedSwapDetailsProps) {
|
||||
const { chainId } = useWeb3React()
|
||||
const nativeCurrency = useNativeCurrency(chainId)
|
||||
const txCount = getTransactionCount(trade)
|
||||
const { formatCurrencyAmount, formatNumber, formatPriceImpact } = useFormatter()
|
||||
|
||||
const supportsGasEstimate = chainId && SUPPORTED_GAS_ESTIMATE_CHAIN_IDS.includes(chainId) && isSubmittableTrade(trade)
|
||||
|
||||
return (
|
||||
<Column gap="md">
|
||||
<Separator />
|
||||
{supportsGasEstimate && (
|
||||
<RowBetween>
|
||||
<MouseoverTooltip
|
||||
text={
|
||||
<Trans>
|
||||
The fee paid to miners who process your transaction. This must be paid in {nativeCurrency.symbol}.
|
||||
</Trans>
|
||||
}
|
||||
>
|
||||
<ThemedText.BodySmall color="neutral2">
|
||||
<Plural value={txCount} one="Network fee" other="Network fees" />
|
||||
</ThemedText.BodySmall>
|
||||
</MouseoverTooltip>
|
||||
<MouseoverTooltip
|
||||
placement="right"
|
||||
size={TooltipSize.Small}
|
||||
text={<GasBreakdownTooltip trade={trade} hideUniswapXDescription />}
|
||||
>
|
||||
<TextWithLoadingPlaceholder syncing={syncing} width={50}>
|
||||
<ThemedText.BodySmall>
|
||||
{`${trade.totalGasUseEstimateUSD ? '~' : ''}${formatNumber({
|
||||
input: trade.totalGasUseEstimateUSD,
|
||||
type: NumberType.FiatGasPrice,
|
||||
})}`}
|
||||
</ThemedText.BodySmall>
|
||||
</TextWithLoadingPlaceholder>
|
||||
</MouseoverTooltip>
|
||||
</RowBetween>
|
||||
)}
|
||||
{isClassicTrade(trade) && (
|
||||
<>
|
||||
<TokenTaxLineItem trade={trade} type="input" syncing={syncing} />
|
||||
<TokenTaxLineItem trade={trade} type="output" syncing={syncing} />
|
||||
<RowBetween>
|
||||
<MouseoverTooltip text={<Trans>The impact your trade has on the market price of this pool.</Trans>}>
|
||||
<ThemedText.BodySmall color="neutral2">
|
||||
<Trans>Price Impact</Trans>
|
||||
</ThemedText.BodySmall>
|
||||
</MouseoverTooltip>
|
||||
<TextWithLoadingPlaceholder syncing={syncing} width={50}>
|
||||
<ThemedText.BodySmall>{formatPriceImpact(trade.priceImpact)}</ThemedText.BodySmall>
|
||||
</TextWithLoadingPlaceholder>
|
||||
</RowBetween>
|
||||
</>
|
||||
)}
|
||||
<RowBetween>
|
||||
<RowFixed>
|
||||
<MouseoverTooltip
|
||||
text={
|
||||
<Trans>
|
||||
The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will
|
||||
revert.
|
||||
</Trans>
|
||||
}
|
||||
>
|
||||
<ThemedText.BodySmall color="neutral2">
|
||||
{trade.tradeType === TradeType.EXACT_INPUT ? <Trans>Minimum output</Trans> : <Trans>Maximum input</Trans>}
|
||||
</ThemedText.BodySmall>
|
||||
</MouseoverTooltip>
|
||||
</RowFixed>
|
||||
<TextWithLoadingPlaceholder syncing={syncing} width={70}>
|
||||
<ThemedText.BodySmall>
|
||||
{trade.tradeType === TradeType.EXACT_INPUT
|
||||
? `${formatCurrencyAmount({
|
||||
amount: trade.minimumAmountOut(allowedSlippage),
|
||||
type: NumberType.SwapTradeAmount,
|
||||
})} ${trade.outputAmount.currency.symbol}`
|
||||
: `${trade.maximumAmountIn(allowedSlippage).toSignificant(6)} ${trade.inputAmount.currency.symbol}`}
|
||||
</ThemedText.BodySmall>
|
||||
</TextWithLoadingPlaceholder>
|
||||
</RowBetween>
|
||||
<RowBetween>
|
||||
<RowFixed>
|
||||
<MouseoverTooltip
|
||||
text={
|
||||
<Trans>
|
||||
The amount you expect to receive at the current market price. You may receive less or more if the market
|
||||
price changes while your transaction is pending.
|
||||
</Trans>
|
||||
}
|
||||
>
|
||||
<ThemedText.BodySmall color="neutral2">
|
||||
<Trans>Expected output</Trans>
|
||||
</ThemedText.BodySmall>
|
||||
</MouseoverTooltip>
|
||||
</RowFixed>
|
||||
<TextWithLoadingPlaceholder syncing={syncing} width={65}>
|
||||
<ThemedText.BodySmall>
|
||||
{`${formatCurrencyAmount({
|
||||
amount: trade.postTaxOutputAmount,
|
||||
type: NumberType.SwapTradeAmount,
|
||||
})} ${trade.outputAmount.currency.symbol}`}
|
||||
</ThemedText.BodySmall>
|
||||
</TextWithLoadingPlaceholder>
|
||||
</RowBetween>
|
||||
<Separator />
|
||||
{isSubmittableTrade(trade) && (
|
||||
<RowBetween>
|
||||
<ThemedText.BodySmall color="neutral2">
|
||||
<Trans>Order routing</Trans>
|
||||
</ThemedText.BodySmall>
|
||||
{isClassicTrade(trade) ? (
|
||||
<MouseoverTooltip
|
||||
size={TooltipSize.Large}
|
||||
text={<SwapRoute data-testid="swap-route-info" trade={trade} syncing={syncing} />}
|
||||
onOpen={() => {
|
||||
sendAnalyticsEvent(SwapEventName.SWAP_AUTOROUTER_VISUALIZATION_EXPANDED, {
|
||||
element: InterfaceElementName.AUTOROUTER_VISUALIZATION_ROW,
|
||||
})
|
||||
}}
|
||||
>
|
||||
<RouterLabel trade={trade} />
|
||||
</MouseoverTooltip>
|
||||
) : (
|
||||
<MouseoverTooltip
|
||||
size={TooltipSize.Small}
|
||||
text={<GasBreakdownTooltip trade={trade} hideFees />}
|
||||
placement="right"
|
||||
onOpen={() => {
|
||||
sendAnalyticsEvent(SwapEventName.SWAP_AUTOROUTER_VISUALIZATION_EXPANDED, {
|
||||
element: InterfaceElementName.AUTOROUTER_VISUALIZATION_ROW,
|
||||
})
|
||||
}}
|
||||
>
|
||||
<RouterLabel trade={trade} />
|
||||
</MouseoverTooltip>
|
||||
)}
|
||||
</RowBetween>
|
||||
)}
|
||||
</Column>
|
||||
)
|
||||
}
|
||||
|
||||
function TokenTaxLineItem({
|
||||
trade,
|
||||
type,
|
||||
syncing,
|
||||
}: {
|
||||
trade: ClassicTrade
|
||||
type: 'input' | 'output'
|
||||
syncing: boolean
|
||||
}) {
|
||||
const { formatPriceImpact } = useFormatter()
|
||||
|
||||
if (syncing) return null
|
||||
|
||||
const [currency, percentage] =
|
||||
type === 'input' ? [trade.inputAmount.currency, trade.inputTax] : [trade.outputAmount.currency, trade.outputTax]
|
||||
|
||||
if (percentage.equalTo(ZERO_PERCENT)) return null
|
||||
|
||||
return (
|
||||
<RowBetween>
|
||||
<MouseoverTooltip
|
||||
text={
|
||||
<>
|
||||
<Trans>
|
||||
Some tokens take a fee when they are bought or sold, which is set by the token issuer. Uniswap does not
|
||||
receive any of these fees.
|
||||
</Trans>{' '}
|
||||
<ExternalLink href="https://support.uniswap.org/hc/en-us/articles/18673568523789-What-is-a-token-fee-">
|
||||
Learn more
|
||||
</ExternalLink>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<ThemedText.BodySmall color="neutral2">{`${currency.symbol} fee`}</ThemedText.BodySmall>
|
||||
</MouseoverTooltip>
|
||||
<ThemedText.BodySmall>{formatPriceImpact(percentage)}</ThemedText.BodySmall>
|
||||
</RowBetween>
|
||||
)
|
||||
}
|
@ -1,10 +1,12 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import UniswapXRouterLabel, { UniswapXGradient } from 'components/RouterLabel/UniswapXRouterLabel'
|
||||
import Row from 'components/Row'
|
||||
import { nativeOnChain } from 'constants/tokens'
|
||||
import { ReactNode } from 'react'
|
||||
import { SubmittableTrade } from 'state/routing/types'
|
||||
import { isClassicTrade, isUniswapXTrade } from 'state/routing/utils'
|
||||
import { InterfaceTrade } from 'state/routing/types'
|
||||
import { isPreviewTrade, isUniswapXTrade } from 'state/routing/utils'
|
||||
import styled from 'styled-components'
|
||||
import { Divider, ExternalLink, ThemedText } from 'theme/components'
|
||||
import { NumberType, useFormatter } from 'utils/formatNumbers'
|
||||
@ -13,99 +15,83 @@ const Container = styled(AutoColumn)`
|
||||
padding: 4px;
|
||||
`
|
||||
|
||||
const InlineLink = styled(ThemedText.BodySmall)`
|
||||
color: ${({ theme }) => theme.accent1};
|
||||
display: inline;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
opacity: ${({ theme }) => theme.opacity.hover};
|
||||
}
|
||||
`
|
||||
type GasCostItemProps = { title: ReactNode; itemValue?: React.ReactNode; amount?: number }
|
||||
|
||||
const InlineUniswapXGradient = styled(UniswapXGradient)`
|
||||
display: inline;
|
||||
`
|
||||
|
||||
const GasCostItem = ({
|
||||
title,
|
||||
amount,
|
||||
itemValue,
|
||||
}: {
|
||||
title: ReactNode
|
||||
itemValue?: React.ReactNode
|
||||
amount?: number
|
||||
}) => {
|
||||
const GasCostItem = ({ title, amount, itemValue }: GasCostItemProps) => {
|
||||
const { formatNumber } = useFormatter()
|
||||
|
||||
if (!amount && !itemValue) return null
|
||||
|
||||
const value = itemValue ?? formatNumber({ input: amount, type: NumberType.FiatGasPrice })
|
||||
return (
|
||||
<Row justify="space-between">
|
||||
<ThemedText.SubHeaderSmall>{title}</ThemedText.SubHeaderSmall>
|
||||
<ThemedText.SubHeaderSmall color="neutral1">
|
||||
{itemValue ??
|
||||
formatNumber({
|
||||
input: amount,
|
||||
type: NumberType.FiatGasPrice,
|
||||
})}
|
||||
</ThemedText.SubHeaderSmall>
|
||||
<ThemedText.SubHeaderSmall color="neutral1">{value}</ThemedText.SubHeaderSmall>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
|
||||
export function GasBreakdownTooltip({
|
||||
trade,
|
||||
hideFees = false,
|
||||
hideUniswapXDescription = false,
|
||||
}: {
|
||||
trade: SubmittableTrade
|
||||
hideFees?: boolean
|
||||
hideUniswapXDescription?: boolean
|
||||
}) {
|
||||
const swapEstimate = isClassicTrade(trade) ? trade.gasUseEstimateUSD : undefined
|
||||
const GaslessSwapLabel = () => <UniswapXRouterLabel>$0</UniswapXRouterLabel>
|
||||
|
||||
type GasBreakdownTooltipProps = { trade: InterfaceTrade; hideUniswapXDescription?: boolean }
|
||||
|
||||
export function GasBreakdownTooltip({ trade, hideUniswapXDescription }: GasBreakdownTooltipProps) {
|
||||
const isUniswapX = isUniswapXTrade(trade)
|
||||
const inputCurrency = trade.inputAmount.currency
|
||||
const native = nativeOnChain(inputCurrency.chainId)
|
||||
|
||||
if (isPreviewTrade(trade)) return <NetworkFeesDescription native={native} />
|
||||
|
||||
const swapEstimate = !isUniswapX ? trade.gasUseEstimateUSD : undefined
|
||||
const approvalEstimate = trade.approveInfo.needsApprove ? trade.approveInfo.approveGasEstimateUSD : undefined
|
||||
const wrapEstimate =
|
||||
isUniswapXTrade(trade) && trade.wrapInfo.needsWrap ? trade.wrapInfo.wrapGasEstimateUSD : undefined
|
||||
const wrapEstimate = isUniswapX && trade.wrapInfo.needsWrap ? trade.wrapInfo.wrapGasEstimateUSD : undefined
|
||||
const showEstimateDetails = Boolean(wrapEstimate || approvalEstimate)
|
||||
|
||||
const description =
|
||||
isUniswapX && !hideUniswapXDescription ? <UniswapXDescription /> : <NetworkFeesDescription native={native} />
|
||||
|
||||
if (!showEstimateDetails) return description
|
||||
|
||||
return (
|
||||
<Container gap="md">
|
||||
{(wrapEstimate || approvalEstimate) && !hideFees && (
|
||||
<>
|
||||
<AutoColumn gap="sm">
|
||||
{wrapEstimate && <GasCostItem title={<Trans>Wrap ETH</Trans>} amount={wrapEstimate} />}
|
||||
{approvalEstimate && (
|
||||
<GasCostItem
|
||||
title={<Trans>Allow {trade.inputAmount.currency.symbol} (one time)</Trans>}
|
||||
amount={approvalEstimate}
|
||||
/>
|
||||
)}
|
||||
{swapEstimate && <GasCostItem title={<Trans>Swap</Trans>} amount={swapEstimate} />}
|
||||
{isUniswapXTrade(trade) && (
|
||||
<GasCostItem title={<Trans>Swap</Trans>} itemValue={<UniswapXRouterLabel>$0</UniswapXRouterLabel>} />
|
||||
)}
|
||||
</AutoColumn>
|
||||
<Divider />
|
||||
</>
|
||||
)}
|
||||
{isUniswapXTrade(trade) && !hideUniswapXDescription ? (
|
||||
<ThemedText.BodySmall color="neutral2">
|
||||
<Trans>
|
||||
<InlineUniswapXGradient>UniswapX</InlineUniswapXGradient> aggregates liquidity sources for better prices and
|
||||
gas free swaps.
|
||||
</Trans>{' '}
|
||||
<ExternalLink href="https://support.uniswap.org/hc/en-us/articles/17515415311501">
|
||||
<InlineLink>
|
||||
<Trans>Learn more</Trans>
|
||||
</InlineLink>
|
||||
</ExternalLink>
|
||||
</ThemedText.BodySmall>
|
||||
) : (
|
||||
<ThemedText.BodySmall color="neutral2">
|
||||
<Trans>Network Fees are paid to the Ethereum network to secure transactions.</Trans>{' '}
|
||||
<ExternalLink href="https://support.uniswap.org/hc/en-us/articles/8370337377805-What-is-a-network-fee-">
|
||||
<InlineLink>
|
||||
<Trans>Learn more</Trans>
|
||||
</InlineLink>
|
||||
</ExternalLink>
|
||||
</ThemedText.BodySmall>
|
||||
)}
|
||||
<AutoColumn gap="sm">
|
||||
<GasCostItem title={<Trans>Wrap {native.symbol}</Trans>} amount={wrapEstimate} />
|
||||
<GasCostItem title={<Trans>Allow {inputCurrency.symbol} (one time)</Trans>} amount={approvalEstimate} />
|
||||
<GasCostItem title={<Trans>Swap</Trans>} amount={swapEstimate} />
|
||||
{isUniswapX && <GasCostItem title={<Trans>Swap</Trans>} itemValue={<GaslessSwapLabel />} />}
|
||||
</AutoColumn>
|
||||
<Divider />
|
||||
{description}
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
function NetworkFeesDescription({ native }: { native: Currency }) {
|
||||
return (
|
||||
<ThemedText.LabelMicro>
|
||||
<Trans>
|
||||
The fee paid to the Ethereum network to process your transaction. This must be paid in {native.symbol}.
|
||||
</Trans>{' '}
|
||||
<ExternalLink href="https://support.uniswap.org/hc/en-us/articles/8370337377805-What-is-a-network-fee-">
|
||||
<Trans>Learn more</Trans>
|
||||
</ExternalLink>
|
||||
</ThemedText.LabelMicro>
|
||||
)
|
||||
}
|
||||
|
||||
const InlineUniswapXGradient = styled(UniswapXGradient)`
|
||||
display: inline;
|
||||
`
|
||||
export function UniswapXDescription() {
|
||||
return (
|
||||
<ThemedText.Caption color="neutral2">
|
||||
<Trans>
|
||||
<InlineUniswapXGradient>UniswapX</InlineUniswapXGradient> aggregates liquidity sources for better prices and gas
|
||||
free swaps.
|
||||
</Trans>{' '}
|
||||
<ExternalLink href="https://support.uniswap.org/hc/en-us/articles/17515415311501">
|
||||
<Trans>Learn more</Trans>
|
||||
</ExternalLink>
|
||||
</ThemedText.Caption>
|
||||
)
|
||||
}
|
||||
|
@ -12,10 +12,11 @@ import { ChevronDown } from 'react-feather'
|
||||
import { InterfaceTrade } from 'state/routing/types'
|
||||
import { isSubmittableTrade } from 'state/routing/utils'
|
||||
import styled, { useTheme } from 'styled-components'
|
||||
import { ThemedText } from 'theme/components'
|
||||
import { Separator, ThemedText } from 'theme/components'
|
||||
import { useFormatter } from 'utils/formatNumbers'
|
||||
|
||||
import { AdvancedSwapDetails } from './AdvancedSwapDetails'
|
||||
import GasEstimateTooltip from './GasEstimateTooltip'
|
||||
import SwapLineItem, { SwapLineItemType } from './SwapLineItem'
|
||||
import TradePrice from './TradePrice'
|
||||
|
||||
const StyledHeaderRow = styled(RowBetween)<{ disabled: boolean; open: boolean }>`
|
||||
@ -29,7 +30,7 @@ const RotatingArrow = styled(ChevronDown)<{ open?: boolean }>`
|
||||
transition: transform 0.1s linear;
|
||||
`
|
||||
|
||||
const SwapDetailsWrapper = styled.div`
|
||||
const SwapDetailsWrapper = styled(Column)`
|
||||
padding-top: ${({ theme }) => theme.grids.md};
|
||||
`
|
||||
|
||||
@ -39,14 +40,15 @@ const Wrapper = styled(Column)`
|
||||
padding: 12px 16px;
|
||||
`
|
||||
|
||||
interface SwapDetailsInlineProps {
|
||||
interface SwapDetailsProps {
|
||||
trade?: InterfaceTrade
|
||||
syncing: boolean
|
||||
loading: boolean
|
||||
allowedSlippage: Percent
|
||||
}
|
||||
|
||||
export default function SwapDetailsDropdown({ trade, syncing, loading, allowedSlippage }: SwapDetailsInlineProps) {
|
||||
export default function SwapDetailsDropdown(props: SwapDetailsProps) {
|
||||
const { trade, syncing, loading, allowedSlippage } = props
|
||||
const theme = useTheme()
|
||||
const [showDetails, setShowDetails] = useState(false)
|
||||
const trace = useTrace()
|
||||
@ -88,13 +90,33 @@ export default function SwapDetailsDropdown({ trade, syncing, loading, allowedSl
|
||||
</RowFixed>
|
||||
</StyledHeaderRow>
|
||||
</TraceEvent>
|
||||
{trade && (
|
||||
<AnimatedDropdown open={showDetails}>
|
||||
<SwapDetailsWrapper data-testid="advanced-swap-details">
|
||||
<AdvancedSwapDetails trade={trade} allowedSlippage={allowedSlippage} syncing={syncing} />
|
||||
</SwapDetailsWrapper>
|
||||
</AnimatedDropdown>
|
||||
)}
|
||||
<AdvancedSwapDetails {...props} open={showDetails} />
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
|
||||
function AdvancedSwapDetails(props: SwapDetailsProps & { open: boolean }) {
|
||||
const { open, trade, allowedSlippage, syncing = false } = props
|
||||
const format = useFormatter()
|
||||
|
||||
if (!trade) return null
|
||||
|
||||
const lineItemProps = { trade, allowedSlippage, format, syncing }
|
||||
|
||||
return (
|
||||
<AnimatedDropdown open={open}>
|
||||
<SwapDetailsWrapper gap="md" data-testid="advanced-swap-details">
|
||||
<Separator />
|
||||
<SwapLineItem {...lineItemProps} type={SwapLineItemType.NETWORK_FEE} />
|
||||
<SwapLineItem {...lineItemProps} type={SwapLineItemType.PRICE_IMPACT} />
|
||||
<SwapLineItem {...lineItemProps} type={SwapLineItemType.INPUT_TOKEN_FEE_ON_TRANSFER} />
|
||||
<SwapLineItem {...lineItemProps} type={SwapLineItemType.OUTPUT_TOKEN_FEE_ON_TRANSFER} />
|
||||
<SwapLineItem {...lineItemProps} type={SwapLineItemType.MAXIMUM_INPUT} />
|
||||
<SwapLineItem {...lineItemProps} type={SwapLineItemType.MINIMUM_OUTPUT} />
|
||||
<SwapLineItem {...lineItemProps} type={SwapLineItemType.EXPECTED_OUTPUT} />
|
||||
<Separator />
|
||||
<SwapLineItem {...lineItemProps} type={SwapLineItemType.ROUTING_INFO} />
|
||||
</SwapDetailsWrapper>
|
||||
</AnimatedDropdown>
|
||||
)
|
||||
}
|
||||
|
73
src/components/swap/SwapLineItem.test.tsx
Normal file
73
src/components/swap/SwapLineItem.test.tsx
Normal file
@ -0,0 +1,73 @@
|
||||
import { InterfaceTrade } from 'state/routing/types'
|
||||
import {
|
||||
PREVIEW_EXACT_IN_TRADE,
|
||||
TEST_ALLOWED_SLIPPAGE,
|
||||
TEST_DUTCH_TRADE_ETH_INPUT,
|
||||
TEST_TRADE_EXACT_INPUT,
|
||||
TEST_TRADE_EXACT_INPUT_API,
|
||||
TEST_TRADE_EXACT_OUTPUT,
|
||||
TEST_TRADE_FEE_ON_BUY,
|
||||
TEST_TRADE_FEE_ON_SELL,
|
||||
} from 'test-utils/constants'
|
||||
import { render } from 'test-utils/render'
|
||||
|
||||
// Forces tooltips to render in snapshots
|
||||
jest.mock('react-dom', () => {
|
||||
const original = jest.requireActual('react-dom')
|
||||
return {
|
||||
...original,
|
||||
createPortal: (node: any) => node,
|
||||
}
|
||||
})
|
||||
|
||||
// Prevents uuid from generating unpredictable values in snapshots
|
||||
jest.mock('uuid', () => ({
|
||||
v4: () => 'fixed-uuid-value',
|
||||
}))
|
||||
|
||||
import SwapLineItem, { SwapLineItemType } from './SwapLineItem'
|
||||
|
||||
const AllLineItemsTypes = Object.keys(SwapLineItemType).map(Number).filter(Boolean)
|
||||
const lineItemProps = {
|
||||
syncing: false,
|
||||
allowedSlippage: TEST_ALLOWED_SLIPPAGE,
|
||||
}
|
||||
|
||||
function testTradeLineItems(trade: InterfaceTrade, props: Partial<typeof lineItemProps> = {}) {
|
||||
const { asFragment } = render(
|
||||
<>
|
||||
{AllLineItemsTypes.map((type) => (
|
||||
<SwapLineItem key={type} trade={trade} type={type} {...lineItemProps} {...props} />
|
||||
))}
|
||||
</>
|
||||
)
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
}
|
||||
|
||||
/* eslint-disable jest/expect-expect */ // allow expect inside testTradeLineItems
|
||||
describe('SwapLineItem.tsx', () => {
|
||||
it('exact input', () => {
|
||||
testTradeLineItems(TEST_TRADE_EXACT_INPUT)
|
||||
})
|
||||
it('exact output', () => {
|
||||
testTradeLineItems(TEST_TRADE_EXACT_OUTPUT)
|
||||
})
|
||||
it('fee on buy', () => {
|
||||
testTradeLineItems(TEST_TRADE_FEE_ON_BUY)
|
||||
})
|
||||
it('fee on sell', () => {
|
||||
testTradeLineItems(TEST_TRADE_FEE_ON_SELL)
|
||||
})
|
||||
it('exact input api', () => {
|
||||
testTradeLineItems(TEST_TRADE_EXACT_INPUT_API)
|
||||
})
|
||||
it('dutch order eth input', () => {
|
||||
testTradeLineItems(TEST_DUTCH_TRADE_ETH_INPUT)
|
||||
})
|
||||
it('syncing', () => {
|
||||
testTradeLineItems(TEST_TRADE_EXACT_INPUT, { syncing: true })
|
||||
})
|
||||
it('preview exact in', () => {
|
||||
testTradeLineItems(PREVIEW_EXACT_IN_TRADE)
|
||||
})
|
||||
})
|
252
src/components/swap/SwapLineItem.tsx
Normal file
252
src/components/swap/SwapLineItem.tsx
Normal file
@ -0,0 +1,252 @@
|
||||
import { Plural, t, Trans } from '@lingui/macro'
|
||||
import { Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core'
|
||||
import { LoadingRow } from 'components/Loader/styled'
|
||||
import RouterLabel from 'components/RouterLabel'
|
||||
import { RowBetween } from 'components/Row'
|
||||
import { MouseoverTooltip, TooltipSize } from 'components/Tooltip'
|
||||
import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains'
|
||||
import useHoverProps from 'hooks/useHoverProps'
|
||||
import { useIsMobile } from 'nft/hooks'
|
||||
import React, { PropsWithChildren, useEffect, useState } from 'react'
|
||||
import { InterfaceTrade, TradeFillType } from 'state/routing/types'
|
||||
import { getTransactionCount, isPreviewTrade, isUniswapXTrade } from 'state/routing/utils'
|
||||
import styled, { DefaultTheme } from 'styled-components'
|
||||
import { ExternalLink, ThemedText } from 'theme/components'
|
||||
import { NumberType, useFormatter } from 'utils/formatNumbers'
|
||||
import { getPriceImpactColor } from 'utils/prices'
|
||||
|
||||
import { GasBreakdownTooltip, UniswapXDescription } from './GasBreakdownTooltip'
|
||||
import SwapRoute from './SwapRoute'
|
||||
|
||||
export enum SwapLineItemType {
|
||||
EXCHANGE_RATE,
|
||||
NETWORK_FEE,
|
||||
INPUT_TOKEN_FEE_ON_TRANSFER,
|
||||
OUTPUT_TOKEN_FEE_ON_TRANSFER,
|
||||
PRICE_IMPACT,
|
||||
MAXIMUM_INPUT,
|
||||
MINIMUM_OUTPUT,
|
||||
EXPECTED_OUTPUT,
|
||||
ROUTING_INFO,
|
||||
}
|
||||
|
||||
const DetailRowValue = styled(ThemedText.BodySmall)`
|
||||
text-align: right;
|
||||
overflow-wrap: break-word;
|
||||
`
|
||||
const LabelText = styled(ThemedText.BodySmall)<{ hasTooltip?: boolean }>`
|
||||
cursor: ${({ hasTooltip }) => (hasTooltip ? 'help' : 'auto')};
|
||||
color: ${({ theme }) => theme.neutral2};
|
||||
`
|
||||
const ColorWrapper = styled.span<{ textColor?: keyof DefaultTheme }>`
|
||||
${({ textColor, theme }) => textColor && `color: ${theme[textColor]};`}
|
||||
`
|
||||
|
||||
function FOTTooltipContent() {
|
||||
return (
|
||||
<>
|
||||
<Trans>
|
||||
Some tokens take a fee when they are bought or sold, which is set by the token issuer. Uniswap does not receive
|
||||
any of these fees.
|
||||
</Trans>{' '}
|
||||
<ExternalLink href="https://support.uniswap.org/hc/en-us/articles/18673568523789-What-is-a-token-fee-">
|
||||
Learn more
|
||||
</ExternalLink>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function Loading({ width = 50 }: { width?: number }) {
|
||||
return <LoadingRow data-testid="loading-row" height={15} width={width} />
|
||||
}
|
||||
|
||||
function ExchangeRateRow({ trade }: { trade: InterfaceTrade }) {
|
||||
const { formatNumber } = useFormatter()
|
||||
const rate = `1 ${trade.executionPrice.quoteCurrency.symbol} = ${formatNumber({
|
||||
input: parseFloat(trade.executionPrice.toFixed(9)),
|
||||
type: NumberType.TokenTx,
|
||||
})} ${trade.executionPrice.baseCurrency.symbol}`
|
||||
return <>{rate}</>
|
||||
}
|
||||
|
||||
function ColoredPercentRow({ percent }: { percent: Percent }) {
|
||||
const { formatPriceImpact } = useFormatter()
|
||||
return <ColorWrapper textColor={getPriceImpactColor(percent)}>{formatPriceImpact(percent)}</ColorWrapper>
|
||||
}
|
||||
|
||||
function CurrencyAmountRow({ amount }: { amount: CurrencyAmount<Currency> }) {
|
||||
const { formatCurrencyAmount } = useFormatter()
|
||||
const formattedAmount = formatCurrencyAmount({ amount, type: NumberType.SwapDetailsAmount })
|
||||
return <>{`${formattedAmount} ${amount.currency.symbol}`}</>
|
||||
}
|
||||
|
||||
type LineItemData = {
|
||||
Label: React.FC
|
||||
Value: React.FC
|
||||
TooltipBody?: React.FC
|
||||
tooltipSize?: TooltipSize
|
||||
loaderWidth?: number
|
||||
}
|
||||
|
||||
function useLineItem(props: SwapLineItemProps): LineItemData | undefined {
|
||||
const { trade, syncing, allowedSlippage, type } = props
|
||||
const { formatNumber } = useFormatter()
|
||||
|
||||
const isUniswapX = isUniswapXTrade(trade)
|
||||
const isPreview = isPreviewTrade(trade)
|
||||
const chainId = trade.inputAmount.currency.chainId
|
||||
|
||||
// Tracks the latest submittable trade's fill type, used to 'guess' whether or not to show price impact during preview
|
||||
const [lastSubmittableFillType, setLastSubmittableFillType] = useState<TradeFillType>()
|
||||
useEffect(() => {
|
||||
if (trade.fillType !== TradeFillType.None) setLastSubmittableFillType(trade.fillType)
|
||||
}, [trade.fillType])
|
||||
|
||||
switch (type) {
|
||||
case SwapLineItemType.EXCHANGE_RATE:
|
||||
return {
|
||||
Label: () => <Trans>Exchange rate</Trans>,
|
||||
Value: () => <ExchangeRateRow trade={trade} />,
|
||||
}
|
||||
case SwapLineItemType.NETWORK_FEE:
|
||||
if (!SUPPORTED_GAS_ESTIMATE_CHAIN_IDS.includes(chainId)) return
|
||||
return {
|
||||
Label: () => <Plural value={getTransactionCount(trade) || 1} one="Network fee" other="Network fees" />,
|
||||
TooltipBody: () => <GasBreakdownTooltip trade={trade} hideUniswapXDescription />,
|
||||
Value: () => {
|
||||
if (isPreview) return <Loading />
|
||||
return <>{formatNumber({ input: trade.totalGasUseEstimateUSD, type: NumberType.FiatGasPrice })}</>
|
||||
},
|
||||
}
|
||||
case SwapLineItemType.PRICE_IMPACT:
|
||||
// Hides price impact row if the current trade is UniswapX or we're expecting a preview trade to result in UniswapX
|
||||
if (isUniswapX || (isPreview && lastSubmittableFillType === TradeFillType.UniswapX)) return
|
||||
return {
|
||||
Label: () => <Trans>Price impact</Trans>,
|
||||
TooltipBody: () => <Trans>The impact your trade has on the market price of this pool.</Trans>,
|
||||
Value: () => (isPreview ? <Loading /> : <ColoredPercentRow percent={trade.priceImpact} />),
|
||||
}
|
||||
case SwapLineItemType.MAXIMUM_INPUT:
|
||||
if (trade.tradeType === TradeType.EXACT_INPUT) return
|
||||
return {
|
||||
Label: () => <Trans>Maximum input</Trans>,
|
||||
TooltipBody: () => (
|
||||
<Trans>
|
||||
The maximum amount you are guaranteed to spend. If the price slips any further, your transaction will
|
||||
revert.
|
||||
</Trans>
|
||||
),
|
||||
Value: () => <CurrencyAmountRow amount={trade.maximumAmountIn(allowedSlippage)} />,
|
||||
loaderWidth: 70,
|
||||
}
|
||||
case SwapLineItemType.MINIMUM_OUTPUT:
|
||||
if (trade.tradeType === TradeType.EXACT_OUTPUT) return
|
||||
return {
|
||||
Label: () => <Trans>Minimum output</Trans>,
|
||||
TooltipBody: () => (
|
||||
<Trans>
|
||||
The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will
|
||||
revert.
|
||||
</Trans>
|
||||
),
|
||||
Value: () => <CurrencyAmountRow amount={trade.minimumAmountOut(allowedSlippage)} />,
|
||||
loaderWidth: 70,
|
||||
}
|
||||
case SwapLineItemType.EXPECTED_OUTPUT:
|
||||
return {
|
||||
Label: () => <Trans>Expected output</Trans>,
|
||||
TooltipBody: () => (
|
||||
<Trans>
|
||||
The amount you expect to receive at the current market price. You may receive less or more if the market
|
||||
price changes while your transaction is pending.
|
||||
</Trans>
|
||||
),
|
||||
Value: () => <CurrencyAmountRow amount={trade.postTaxOutputAmount} />,
|
||||
loaderWidth: 65,
|
||||
}
|
||||
case SwapLineItemType.ROUTING_INFO:
|
||||
if (isPreview) return { Label: () => <Trans>Order routing</Trans>, Value: () => <Loading /> }
|
||||
return {
|
||||
Label: () => <Trans>Order routing</Trans>,
|
||||
TooltipBody: () => {
|
||||
if (isUniswapX) return <UniswapXDescription />
|
||||
return <SwapRoute data-testid="swap-route-info" trade={trade} syncing={syncing} />
|
||||
},
|
||||
tooltipSize: isUniswapX ? TooltipSize.Small : TooltipSize.Large,
|
||||
Value: () => <RouterLabel trade={trade} />,
|
||||
}
|
||||
case SwapLineItemType.INPUT_TOKEN_FEE_ON_TRANSFER:
|
||||
case SwapLineItemType.OUTPUT_TOKEN_FEE_ON_TRANSFER:
|
||||
return getFOTLineItem(props)
|
||||
}
|
||||
}
|
||||
|
||||
function getFOTLineItem({ type, trade }: SwapLineItemProps): LineItemData | undefined {
|
||||
const isInput = type === SwapLineItemType.INPUT_TOKEN_FEE_ON_TRANSFER
|
||||
const currency = isInput ? trade.inputAmount.currency : trade.outputAmount.currency
|
||||
const tax = isInput ? trade.inputTax : trade.outputTax
|
||||
if (tax.equalTo(0)) return
|
||||
|
||||
return {
|
||||
Label: () => <>{t`${currency.symbol ?? currency.name ?? t`Token`} fee`}</>,
|
||||
TooltipBody: FOTTooltipContent,
|
||||
Value: () => <ColoredPercentRow percent={tax} />,
|
||||
}
|
||||
}
|
||||
|
||||
type ValueWrapperProps = PropsWithChildren<{
|
||||
lineItem: LineItemData
|
||||
labelHovered: boolean
|
||||
syncing: boolean
|
||||
}>
|
||||
|
||||
function ValueWrapper({ children, lineItem, labelHovered, syncing }: ValueWrapperProps) {
|
||||
const { TooltipBody, tooltipSize, loaderWidth } = lineItem
|
||||
const isMobile = useIsMobile()
|
||||
|
||||
if (syncing) return <Loading width={loaderWidth} />
|
||||
|
||||
if (!TooltipBody) return <DetailRowValue>{children}</DetailRowValue>
|
||||
|
||||
return (
|
||||
<MouseoverTooltip
|
||||
placement={isMobile ? 'auto' : 'right'}
|
||||
forceShow={labelHovered} // displays tooltip when hovering either both label or value
|
||||
size={tooltipSize}
|
||||
text={
|
||||
<ThemedText.Caption color="neutral2">
|
||||
<TooltipBody />
|
||||
</ThemedText.Caption>
|
||||
}
|
||||
>
|
||||
<DetailRowValue>{children}</DetailRowValue>
|
||||
</MouseoverTooltip>
|
||||
)
|
||||
}
|
||||
|
||||
interface SwapLineItemProps {
|
||||
trade: InterfaceTrade
|
||||
syncing: boolean
|
||||
allowedSlippage: Percent
|
||||
type: SwapLineItemType
|
||||
}
|
||||
|
||||
function SwapLineItem(props: SwapLineItemProps) {
|
||||
const [labelHovered, hoverProps] = useHoverProps()
|
||||
|
||||
const LineItem = useLineItem(props)
|
||||
if (!LineItem) return null
|
||||
|
||||
return (
|
||||
<RowBetween>
|
||||
<LabelText {...hoverProps} hasTooltip={!!LineItem.TooltipBody} data-testid="swap-li-label">
|
||||
<LineItem.Label />
|
||||
</LabelText>
|
||||
<ValueWrapper lineItem={LineItem} labelHovered={labelHovered} syncing={props.syncing}>
|
||||
<LineItem.Value />
|
||||
</ValueWrapper>
|
||||
</RowBetween>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(SwapLineItem)
|
@ -1,13 +1,4 @@
|
||||
import {
|
||||
PREVIEW_EXACT_IN_TRADE,
|
||||
TEST_ALLOWED_SLIPPAGE,
|
||||
TEST_TOKEN_1,
|
||||
TEST_TOKEN_2,
|
||||
TEST_TRADE_EXACT_INPUT,
|
||||
TEST_TRADE_EXACT_OUTPUT,
|
||||
TEST_TRADE_FEE_ON_BUY,
|
||||
TEST_TRADE_FEE_ON_SELL,
|
||||
} from 'test-utils/constants'
|
||||
import { PREVIEW_EXACT_IN_TRADE, TEST_ALLOWED_SLIPPAGE, TEST_TRADE_EXACT_INPUT } from 'test-utils/constants'
|
||||
import { render, screen, within } from 'test-utils/render'
|
||||
|
||||
import SwapModalFooter from './SwapModalFooter'
|
||||
@ -43,7 +34,7 @@ describe('SwapModalFooter.tsx', () => {
|
||||
)
|
||||
).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByText('The fee paid to miners who process your transaction. This must be paid in $ETH.')
|
||||
screen.getByText('The fee paid to the Ethereum network to process your transaction. This must be paid in ETH.')
|
||||
).toBeInTheDocument()
|
||||
expect(screen.getByText('The impact your trade has on the market price of this pool.')).toBeInTheDocument()
|
||||
})
|
||||
@ -77,99 +68,6 @@ describe('SwapModalFooter.tsx', () => {
|
||||
expect(within(showAcceptChanges).getByText('Accept')).toBeVisible()
|
||||
})
|
||||
|
||||
it('test trade exact output, no recipient', () => {
|
||||
render(
|
||||
<SwapModalFooter
|
||||
isLoading={false}
|
||||
trade={TEST_TRADE_EXACT_OUTPUT}
|
||||
allowedSlippage={TEST_ALLOWED_SLIPPAGE}
|
||||
swapResult={undefined}
|
||||
onConfirm={jest.fn()}
|
||||
swapErrorMessage={undefined}
|
||||
disabledConfirm={false}
|
||||
fiatValueInput={{
|
||||
data: undefined,
|
||||
isLoading: false,
|
||||
}}
|
||||
fiatValueOutput={{
|
||||
data: undefined,
|
||||
isLoading: false,
|
||||
}}
|
||||
showAcceptChanges={true}
|
||||
onAcceptChanges={jest.fn()}
|
||||
/>
|
||||
)
|
||||
expect(
|
||||
screen.getByText(
|
||||
'The maximum amount you are guaranteed to spend. If the price slips any further, your transaction will revert.'
|
||||
)
|
||||
).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByText('The fee paid to miners who process your transaction. This must be paid in $ETH.')
|
||||
).toBeInTheDocument()
|
||||
expect(screen.getByText('The impact your trade has on the market price of this pool.')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('test trade fee on input token transfer', () => {
|
||||
render(
|
||||
<SwapModalFooter
|
||||
isLoading={false}
|
||||
trade={TEST_TRADE_FEE_ON_SELL}
|
||||
allowedSlippage={TEST_ALLOWED_SLIPPAGE}
|
||||
swapResult={undefined}
|
||||
onConfirm={jest.fn()}
|
||||
swapErrorMessage={undefined}
|
||||
disabledConfirm={false}
|
||||
fiatValueInput={{
|
||||
data: undefined,
|
||||
isLoading: false,
|
||||
}}
|
||||
fiatValueOutput={{
|
||||
data: undefined,
|
||||
isLoading: false,
|
||||
}}
|
||||
showAcceptChanges={true}
|
||||
onAcceptChanges={jest.fn()}
|
||||
/>
|
||||
)
|
||||
expect(
|
||||
screen.getByText(
|
||||
'Some tokens take a fee when they are bought or sold, which is set by the token issuer. Uniswap does not receive any of these fees.'
|
||||
)
|
||||
).toBeInTheDocument()
|
||||
expect(screen.getByText(`${TEST_TOKEN_1.symbol} fee`)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('test trade fee on output token transfer', () => {
|
||||
render(
|
||||
<SwapModalFooter
|
||||
isLoading={false}
|
||||
trade={TEST_TRADE_FEE_ON_BUY}
|
||||
allowedSlippage={TEST_ALLOWED_SLIPPAGE}
|
||||
swapResult={undefined}
|
||||
onConfirm={jest.fn()}
|
||||
swapErrorMessage={undefined}
|
||||
disabledConfirm={false}
|
||||
fiatValueInput={{
|
||||
data: undefined,
|
||||
isLoading: false,
|
||||
}}
|
||||
fiatValueOutput={{
|
||||
data: undefined,
|
||||
isLoading: false,
|
||||
}}
|
||||
showAcceptChanges={true}
|
||||
onAcceptChanges={jest.fn()}
|
||||
/>
|
||||
)
|
||||
expect(
|
||||
screen.getByText(
|
||||
'Some tokens take a fee when they are bought or sold, which is set by the token issuer. Uniswap does not receive any of these fees.'
|
||||
)
|
||||
).toBeInTheDocument()
|
||||
expect(screen.getByText(`${TEST_TOKEN_2.symbol} fee`)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders a preview trade while disabling submission', () => {
|
||||
const { asFragment } = render(
|
||||
<SwapModalFooter
|
||||
|
@ -1,34 +1,26 @@
|
||||
import { Plural, t, Trans } from '@lingui/macro'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { BrowserEvent, InterfaceElementName, SwapEventName } from '@uniswap/analytics-events'
|
||||
import { Percent, TradeType } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { Percent } from '@uniswap/sdk-core'
|
||||
import { TraceEvent } from 'analytics'
|
||||
import Column from 'components/Column'
|
||||
import SpinningLoader from 'components/Loader/SpinningLoader'
|
||||
import { MouseoverTooltip, TooltipSize } from 'components/Tooltip'
|
||||
import { ZERO_PERCENT } from 'constants/misc'
|
||||
import { SwapResult } from 'hooks/useSwapCallback'
|
||||
import useTransactionDeadline from 'hooks/useTransactionDeadline'
|
||||
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
|
||||
import { ReactNode } from 'react'
|
||||
import { AlertTriangle } from 'react-feather'
|
||||
import { ClassicTrade, InterfaceTrade, PreviewTrade, RouterPreference } from 'state/routing/types'
|
||||
import { getTransactionCount, isClassicTrade, isPreviewTrade, isSubmittableTrade } from 'state/routing/utils'
|
||||
import { InterfaceTrade, RouterPreference } from 'state/routing/types'
|
||||
import { isClassicTrade } from 'state/routing/utils'
|
||||
import { useRouterPreference, useUserSlippageTolerance } from 'state/user/hooks'
|
||||
import styled, { DefaultTheme, useTheme } from 'styled-components'
|
||||
import { ExternalLink, ThemedText } from 'theme/components'
|
||||
import { FormatterRule, NumberType, SIX_SIG_FIGS_NO_COMMAS, useFormatter } from 'utils/formatNumbers'
|
||||
import styled, { useTheme } from 'styled-components'
|
||||
import { ThemedText } from 'theme/components'
|
||||
import getRoutingDiagramEntries from 'utils/getRoutingDiagramEntries'
|
||||
import { formatSwapButtonClickEventProperties } from 'utils/loggingFormatters'
|
||||
import { getPriceImpactColor } from 'utils/prices'
|
||||
|
||||
import { ButtonError, SmallButtonPrimary } from '../Button'
|
||||
import Row, { AutoRow, RowBetween, RowFixed } from '../Row'
|
||||
import { GasBreakdownTooltip } from './GasBreakdownTooltip'
|
||||
import { SwapCallbackError, SwapShowAcceptChanges } from './styled'
|
||||
import { Label } from './SwapModalHeaderAmount'
|
||||
|
||||
const sixFigsFormatterRules: FormatterRule[] = [{ upperBound: Infinity, formatterOptions: SIX_SIG_FIGS_NO_COMMAS }]
|
||||
import { SwapLineItemType } from './SwapLineItem'
|
||||
import SwapLineItem from './SwapLineItem'
|
||||
|
||||
const DetailsContainer = styled(Column)`
|
||||
padding: 0 8px;
|
||||
@ -44,12 +36,6 @@ const ConfirmButton = styled(ButtonError)`
|
||||
margin-top: 10px;
|
||||
`
|
||||
|
||||
const DetailRowValue = styled(ThemedText.BodySmall)<{ warningColor?: keyof DefaultTheme }>`
|
||||
text-align: right;
|
||||
overflow-wrap: break-word;
|
||||
${({ warningColor, theme }) => warningColor && `color: ${theme[warningColor]};`};
|
||||
`
|
||||
|
||||
export default function SwapModalFooter({
|
||||
trade,
|
||||
allowedSlippage,
|
||||
@ -80,116 +66,19 @@ export default function SwapModalFooter({
|
||||
const [routerPreference] = useRouterPreference()
|
||||
const routes = isClassicTrade(trade) ? getRoutingDiagramEntries(trade) : undefined
|
||||
const theme = useTheme()
|
||||
const { chainId } = useWeb3React()
|
||||
const nativeCurrency = useNativeCurrency(chainId)
|
||||
const { formatCurrencyAmount, formatNumber, formatPriceImpact } = useFormatter()
|
||||
|
||||
const label = `${trade.executionPrice.baseCurrency?.symbol} `
|
||||
const labelInverted = `${trade.executionPrice.quoteCurrency?.symbol}`
|
||||
const formattedPrice = formatNumber({
|
||||
input: trade.executionPrice ? parseFloat(trade.executionPrice.toFixed(9)) : undefined,
|
||||
type: NumberType.TokenTx,
|
||||
})
|
||||
const txCount = getTransactionCount(trade)
|
||||
const lineItemProps = { trade, allowedSlippage, syncing: false }
|
||||
|
||||
return (
|
||||
<>
|
||||
<DetailsContainer gap="md">
|
||||
<ThemedText.BodySmall>
|
||||
<Row align="flex-start" justify="space-between" gap="sm">
|
||||
<Label>
|
||||
<Trans>Exchange rate</Trans>
|
||||
</Label>
|
||||
<DetailRowValue>{`1 ${labelInverted} = ${formattedPrice ?? '-'} ${label}`}</DetailRowValue>
|
||||
</Row>
|
||||
</ThemedText.BodySmall>
|
||||
<ThemedText.BodySmall>
|
||||
<Row align="flex-start" justify="space-between" gap="sm">
|
||||
<MouseoverTooltip
|
||||
text={
|
||||
<Trans>
|
||||
The fee paid to miners who process your transaction. This must be paid in ${nativeCurrency.symbol}.
|
||||
</Trans>
|
||||
}
|
||||
>
|
||||
<Label cursor="help">
|
||||
<Plural value={txCount} one="Network fee" other="Network fees" />
|
||||
</Label>
|
||||
</MouseoverTooltip>
|
||||
<MouseoverTooltip
|
||||
placement="right"
|
||||
size={TooltipSize.Small}
|
||||
text={isSubmittableTrade(trade) ? <GasBreakdownTooltip trade={trade} /> : undefined}
|
||||
>
|
||||
<DetailRowValue>
|
||||
{isSubmittableTrade(trade)
|
||||
? formatNumber({
|
||||
input: trade.totalGasUseEstimateUSD,
|
||||
type: NumberType.FiatGasPrice,
|
||||
})
|
||||
: '-'}
|
||||
</DetailRowValue>
|
||||
</MouseoverTooltip>
|
||||
</Row>
|
||||
</ThemedText.BodySmall>
|
||||
{(isClassicTrade(trade) || isPreviewTrade(trade)) && (
|
||||
<>
|
||||
<TokenTaxLineItem trade={trade} type="input" />
|
||||
<TokenTaxLineItem trade={trade} type="output" />
|
||||
<ThemedText.BodySmall>
|
||||
<Row align="flex-start" justify="space-between" gap="sm">
|
||||
<MouseoverTooltip text={<Trans>The impact your trade has on the market price of this pool.</Trans>}>
|
||||
<Label cursor="help">
|
||||
<Trans>Price impact</Trans>
|
||||
</Label>
|
||||
</MouseoverTooltip>
|
||||
<DetailRowValue
|
||||
warningColor={isClassicTrade(trade) ? getPriceImpactColor(trade.priceImpact) : undefined}
|
||||
>
|
||||
{isClassicTrade(trade) && trade.priceImpact ? formatPriceImpact(trade.priceImpact) : '-'}
|
||||
</DetailRowValue>
|
||||
</Row>
|
||||
</ThemedText.BodySmall>
|
||||
</>
|
||||
)}
|
||||
<ThemedText.BodySmall>
|
||||
<Row align="flex-start" justify="space-between" gap="sm">
|
||||
<MouseoverTooltip
|
||||
text={
|
||||
trade.tradeType === TradeType.EXACT_INPUT ? (
|
||||
<Trans>
|
||||
The minimum amount you are guaranteed to receive. If the price slips any further, your transaction
|
||||
will revert.
|
||||
</Trans>
|
||||
) : (
|
||||
<Trans>
|
||||
The maximum amount you are guaranteed to spend. If the price slips any further, your transaction
|
||||
will revert.
|
||||
</Trans>
|
||||
)
|
||||
}
|
||||
>
|
||||
<Label cursor="help">
|
||||
{trade.tradeType === TradeType.EXACT_INPUT ? (
|
||||
<Trans>Minimum received</Trans>
|
||||
) : (
|
||||
<Trans>Maximum sent</Trans>
|
||||
)}
|
||||
</Label>
|
||||
</MouseoverTooltip>
|
||||
<DetailRowValue>
|
||||
{trade.tradeType === TradeType.EXACT_INPUT
|
||||
? `${formatCurrencyAmount({
|
||||
amount: trade.minimumAmountOut(allowedSlippage),
|
||||
type: sixFigsFormatterRules,
|
||||
})} ${trade.outputAmount.currency.symbol}`
|
||||
: `${formatCurrencyAmount({
|
||||
amount: trade.maximumAmountIn(allowedSlippage),
|
||||
type: sixFigsFormatterRules,
|
||||
})} ${trade.inputAmount.currency.symbol}`}
|
||||
</DetailRowValue>
|
||||
</Row>
|
||||
</ThemedText.BodySmall>
|
||||
<SwapLineItem {...lineItemProps} type={SwapLineItemType.EXCHANGE_RATE} />
|
||||
<SwapLineItem {...lineItemProps} type={SwapLineItemType.NETWORK_FEE} />
|
||||
<SwapLineItem {...lineItemProps} type={SwapLineItemType.PRICE_IMPACT} />
|
||||
<SwapLineItem {...lineItemProps} type={SwapLineItemType.INPUT_TOKEN_FEE_ON_TRANSFER} />
|
||||
<SwapLineItem {...lineItemProps} type={SwapLineItemType.OUTPUT_TOKEN_FEE_ON_TRANSFER} />
|
||||
<SwapLineItem {...lineItemProps} type={SwapLineItemType.MAXIMUM_INPUT} />
|
||||
<SwapLineItem {...lineItemProps} type={SwapLineItemType.MINIMUM_OUTPUT} />
|
||||
</DetailsContainer>
|
||||
{showAcceptChanges ? (
|
||||
<SwapShowAcceptChanges data-testid="show-accept-changes">
|
||||
@ -251,35 +140,3 @@ export default function SwapModalFooter({
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function TokenTaxLineItem({ trade, type }: { trade: ClassicTrade | PreviewTrade; type: 'input' | 'output' }) {
|
||||
const { formatPriceImpact } = useFormatter()
|
||||
|
||||
const [currency, percentage] =
|
||||
type === 'input' ? [trade.inputAmount.currency, trade.inputTax] : [trade.outputAmount.currency, trade.outputTax]
|
||||
|
||||
if (percentage.equalTo(ZERO_PERCENT)) return null
|
||||
|
||||
return (
|
||||
<ThemedText.BodySmall>
|
||||
<Row align="flex-start" justify="space-between" gap="sm">
|
||||
<MouseoverTooltip
|
||||
text={
|
||||
<>
|
||||
<Trans>
|
||||
Some tokens take a fee when they are bought or sold, which is set by the token issuer. Uniswap does not
|
||||
receive any of these fees.
|
||||
</Trans>{' '}
|
||||
<ExternalLink href="https://support.uniswap.org/hc/en-us/articles/18673568523789-What-is-a-token-fee-">
|
||||
Learn more
|
||||
</ExternalLink>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Label cursor="help">{t`${currency.symbol} fee`}</Label>
|
||||
</MouseoverTooltip>
|
||||
<DetailRowValue warningColor={getPriceImpactColor(percentage)}>{formatPriceImpact(percentage)}</DetailRowValue>
|
||||
</Row>
|
||||
</ThemedText.BodySmall>
|
||||
)
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import { BREAKPOINTS } from 'theme'
|
||||
import { ThemedText } from 'theme/components'
|
||||
import { NumberType, useFormatter } from 'utils/formatNumbers'
|
||||
|
||||
export const Label = styled(ThemedText.BodySmall)<{ cursor?: string }>`
|
||||
const Label = styled(ThemedText.BodySmall)<{ cursor?: string }>`
|
||||
cursor: ${({ cursor }) => cursor};
|
||||
color: ${({ theme }) => theme.neutral2};
|
||||
margin-right: 8px;
|
||||
|
@ -28,7 +28,7 @@ export default function SwapRoute({ trade, syncing }: { trade: ClassicTrade; syn
|
||||
|
||||
return (
|
||||
<Column gap="md">
|
||||
<RouterLabel trade={trade} />
|
||||
<RouterLabel trade={trade} color="neutral2" />
|
||||
<Separator />
|
||||
{syncing ? (
|
||||
<LoadingRows>
|
||||
@ -49,13 +49,13 @@ export default function SwapRoute({ trade, syncing }: { trade: ClassicTrade; syn
|
||||
<div style={{ width: '100%', height: '15px' }} />
|
||||
</LoadingRows>
|
||||
) : (
|
||||
<ThemedText.BodySmall color="neutral2">
|
||||
<ThemedText.Caption color="neutral2">
|
||||
{gasPrice ? <Trans>Best price route costs ~{gasPrice} in gas.</Trans> : null}{' '}
|
||||
<Trans>
|
||||
This route optimizes your total output by considering split routes, multiple hops, and the gas cost of
|
||||
each step.
|
||||
</Trans>
|
||||
</ThemedText.BodySmall>
|
||||
</ThemedText.Caption>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
@ -1,200 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`AdvancedSwapDetails.tsx matches base snapshot 1`] = `
|
||||
<DocumentFragment>
|
||||
.c2 {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.c3 {
|
||||
width: 100%;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
padding: 0;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: start;
|
||||
-webkit-justify-content: flex-start;
|
||||
-ms-flex-pack: start;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.c4 {
|
||||
-webkit-box-pack: justify;
|
||||
-webkit-justify-content: space-between;
|
||||
-ms-flex-pack: justify;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.c8 {
|
||||
width: -webkit-fit-content;
|
||||
width: -moz-fit-content;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.c6 {
|
||||
color: #7D7D7D;
|
||||
}
|
||||
|
||||
.c7 {
|
||||
color: #222222;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: #22222212;
|
||||
}
|
||||
|
||||
.c0 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: column;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
-webkit-box-pack: start;
|
||||
-webkit-justify-content: flex-start;
|
||||
-ms-flex-pack: start;
|
||||
justify-content: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.c5 {
|
||||
display: inline-block;
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
<div
|
||||
class="c0"
|
||||
>
|
||||
<div
|
||||
class="c1"
|
||||
/>
|
||||
<div
|
||||
class="c2 c3 c4"
|
||||
>
|
||||
<div
|
||||
class="c5"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c6 css-142zc9n"
|
||||
>
|
||||
Network fee
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c5"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c7 css-142zc9n"
|
||||
>
|
||||
~$1.00
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c2 c3 c4"
|
||||
>
|
||||
<div
|
||||
class="c5"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c6 css-142zc9n"
|
||||
>
|
||||
Price Impact
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c7 css-142zc9n"
|
||||
>
|
||||
105566.373%
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c2 c3 c4"
|
||||
>
|
||||
<div
|
||||
class="c2 c3 c8"
|
||||
>
|
||||
<div
|
||||
class="c5"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c6 css-142zc9n"
|
||||
>
|
||||
Minimum output
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c7 css-142zc9n"
|
||||
>
|
||||
0.00000000000000098 DEF
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c2 c3 c4"
|
||||
>
|
||||
<div
|
||||
class="c2 c3 c8"
|
||||
>
|
||||
<div
|
||||
class="c5"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c6 css-142zc9n"
|
||||
>
|
||||
Expected output
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c7 css-142zc9n"
|
||||
>
|
||||
0.000000000000001 DEF
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c1"
|
||||
/>
|
||||
<div
|
||||
class="c2 c3 c4"
|
||||
>
|
||||
<div
|
||||
class="c6 css-142zc9n"
|
||||
>
|
||||
Order routing
|
||||
</div>
|
||||
<div
|
||||
class="c5"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c7 css-142zc9n"
|
||||
>
|
||||
Uniswap Client
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
@ -91,7 +91,7 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.c17 {
|
||||
.c16 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
@ -128,6 +128,16 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
|
||||
fill: #7D7D7D;
|
||||
}
|
||||
|
||||
.c20 {
|
||||
text-align: right;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.c19 {
|
||||
cursor: help;
|
||||
color: #7D7D7D;
|
||||
}
|
||||
|
||||
.c8 {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
@ -178,7 +188,7 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
|
||||
transition: transform 0.1s linear;
|
||||
}
|
||||
|
||||
.c16 {
|
||||
.c17 {
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
@ -281,126 +291,121 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c16"
|
||||
class="c16 c17"
|
||||
data-testid="advanced-swap-details"
|
||||
>
|
||||
<div
|
||||
class="c17"
|
||||
class="c18"
|
||||
/>
|
||||
<div
|
||||
class="c2 c3 c4"
|
||||
>
|
||||
<div
|
||||
class="c18"
|
||||
/>
|
||||
<div
|
||||
class="c2 c3 c4"
|
||||
class="c9 c19 css-142zc9n"
|
||||
data-testid="swap-li-label"
|
||||
>
|
||||
<div
|
||||
class="c12"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c14 css-142zc9n"
|
||||
>
|
||||
Network fee
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c12"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c9 css-142zc9n"
|
||||
>
|
||||
~$1.00
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Network fee
|
||||
</div>
|
||||
<div
|
||||
class="c2 c3 c4"
|
||||
class="c12"
|
||||
>
|
||||
<div
|
||||
class="c12"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c14 css-142zc9n"
|
||||
>
|
||||
Price Impact
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c9 css-142zc9n"
|
||||
>
|
||||
105566.373%
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c2 c3 c4"
|
||||
>
|
||||
<div
|
||||
class="c2 c3 c6"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c12"
|
||||
class="c9 c20 css-142zc9n"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c14 css-142zc9n"
|
||||
>
|
||||
Minimum output
|
||||
</div>
|
||||
</div>
|
||||
$1.00
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c9 css-142zc9n"
|
||||
>
|
||||
0.00000000000000098 DEF
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c2 c3 c4"
|
||||
>
|
||||
<div
|
||||
class="c9 c19 css-142zc9n"
|
||||
data-testid="swap-li-label"
|
||||
>
|
||||
Price impact
|
||||
</div>
|
||||
<div
|
||||
class="c2 c3 c4"
|
||||
class="c12"
|
||||
>
|
||||
<div
|
||||
class="c2 c3 c6"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c12"
|
||||
class="c9 c20 css-142zc9n"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c14 css-142zc9n"
|
||||
>
|
||||
Expected output
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
class=""
|
||||
>
|
||||
105566.373%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c9 css-142zc9n"
|
||||
>
|
||||
0.000000000000001 DEF
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c2 c3 c4"
|
||||
>
|
||||
<div
|
||||
class="c9 c19 css-142zc9n"
|
||||
data-testid="swap-li-label"
|
||||
>
|
||||
Minimum output
|
||||
</div>
|
||||
<div
|
||||
class="c18"
|
||||
/>
|
||||
<div
|
||||
class="c2 c3 c4"
|
||||
class="c12"
|
||||
>
|
||||
<div
|
||||
class="c14 css-142zc9n"
|
||||
>
|
||||
Order routing
|
||||
<div>
|
||||
<div
|
||||
class="c9 c20 css-142zc9n"
|
||||
>
|
||||
0.00000000000000098 DEF
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c12"
|
||||
>
|
||||
<div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c2 c3 c4"
|
||||
>
|
||||
<div
|
||||
class="c9 c19 css-142zc9n"
|
||||
data-testid="swap-li-label"
|
||||
>
|
||||
Expected output
|
||||
</div>
|
||||
<div
|
||||
class="c12"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c9 c20 css-142zc9n"
|
||||
>
|
||||
0.000000000000001 DEF
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c18"
|
||||
/>
|
||||
<div
|
||||
class="c2 c3 c4"
|
||||
>
|
||||
<div
|
||||
class="c9 c19 css-142zc9n"
|
||||
data-testid="swap-li-label"
|
||||
>
|
||||
Order routing
|
||||
</div>
|
||||
<div
|
||||
class="c12"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c9 c20 css-142zc9n"
|
||||
>
|
||||
<div
|
||||
class="c9 css-142zc9n"
|
||||
class="css-142zc9n"
|
||||
>
|
||||
Uniswap Client
|
||||
</div>
|
||||
|
9031
src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap
Normal file
9031
src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap
Normal file
File diff suppressed because it is too large
Load Diff
@ -2,31 +2,37 @@
|
||||
|
||||
exports[`SwapModalFooter.tsx matches base snapshot, test trade exact input 1`] = `
|
||||
<DocumentFragment>
|
||||
.c3 {
|
||||
.c2 {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.c4 {
|
||||
.c3 {
|
||||
width: 100%;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
padding: 0;
|
||||
-webkit-align-items: flex-start;
|
||||
-webkit-box-align: flex-start;
|
||||
-ms-flex-align: flex-start;
|
||||
align-items: flex-start;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: start;
|
||||
-webkit-justify-content: flex-start;
|
||||
-ms-flex-pack: start;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.c4 {
|
||||
-webkit-box-pack: justify;
|
||||
-webkit-justify-content: space-between;
|
||||
-ms-flex-pack: justify;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.c2 {
|
||||
.c5 {
|
||||
color: #222222;
|
||||
}
|
||||
|
||||
@ -45,131 +51,113 @@ exports[`SwapModalFooter.tsx matches base snapshot, test trade exact input 1`] =
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.c7 {
|
||||
.c9 {
|
||||
display: inline-block;
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
.c5 {
|
||||
.c7 {
|
||||
text-align: right;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.c6 {
|
||||
cursor: auto;
|
||||
color: #7D7D7D;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.c8 {
|
||||
cursor: help;
|
||||
color: #7D7D7D;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.c6 {
|
||||
text-align: right;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
<div
|
||||
class="c0 c1"
|
||||
>
|
||||
<div
|
||||
class="c2 css-142zc9n"
|
||||
class="c2 c3 c4"
|
||||
>
|
||||
<div
|
||||
class="c3 c4"
|
||||
class="c5 c6 css-142zc9n"
|
||||
data-testid="swap-li-label"
|
||||
>
|
||||
<div
|
||||
class="c2 c5 css-142zc9n"
|
||||
>
|
||||
Exchange rate
|
||||
</div>
|
||||
<div
|
||||
class="c2 c6 css-142zc9n"
|
||||
>
|
||||
1 DEF = 1.00 ABC
|
||||
</div>
|
||||
Exchange rate
|
||||
</div>
|
||||
<div
|
||||
class="c5 c7 css-142zc9n"
|
||||
>
|
||||
1 DEF = 1.00 ABC
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c2 css-142zc9n"
|
||||
class="c2 c3 c4"
|
||||
>
|
||||
<div
|
||||
class="c3 c4"
|
||||
class="c5 c8 css-142zc9n"
|
||||
data-testid="swap-li-label"
|
||||
>
|
||||
<div
|
||||
class="c7"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c2 c8 css-142zc9n"
|
||||
cursor="help"
|
||||
>
|
||||
Network fee
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c7"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c2 c6 css-142zc9n"
|
||||
>
|
||||
$1.00
|
||||
</div>
|
||||
Network fee
|
||||
</div>
|
||||
<div
|
||||
class="c9"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c5 c7 css-142zc9n"
|
||||
>
|
||||
$1.00
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c2 css-142zc9n"
|
||||
class="c2 c3 c4"
|
||||
>
|
||||
<div
|
||||
class="c3 c4"
|
||||
class="c5 c8 css-142zc9n"
|
||||
data-testid="swap-li-label"
|
||||
>
|
||||
<div
|
||||
class="c7"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c2 c8 css-142zc9n"
|
||||
cursor="help"
|
||||
Price impact
|
||||
</div>
|
||||
<div
|
||||
class="c9"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c5 c7 css-142zc9n"
|
||||
>
|
||||
<span
|
||||
class=""
|
||||
>
|
||||
Price impact
|
||||
</div>
|
||||
105566.373%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c2 c6 css-142zc9n"
|
||||
>
|
||||
105566.373%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c2 css-142zc9n"
|
||||
class="c2 c3 c4"
|
||||
>
|
||||
<div
|
||||
class="c3 c4"
|
||||
class="c5 c8 css-142zc9n"
|
||||
data-testid="swap-li-label"
|
||||
>
|
||||
<div
|
||||
class="c7"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c2 c8 css-142zc9n"
|
||||
cursor="help"
|
||||
>
|
||||
Minimum received
|
||||
</div>
|
||||
Minimum output
|
||||
</div>
|
||||
<div
|
||||
class="c9"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c5 c7 css-142zc9n"
|
||||
>
|
||||
0.00000000000000098 DEF
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c2 c6 css-142zc9n"
|
||||
>
|
||||
0.00000000000000098 DEF
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -346,31 +334,37 @@ exports[`SwapModalFooter.tsx matches base snapshot, test trade exact input 1`] =
|
||||
|
||||
exports[`SwapModalFooter.tsx renders a preview trade while disabling submission 1`] = `
|
||||
<DocumentFragment>
|
||||
.c3 {
|
||||
.c2 {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.c4 {
|
||||
.c3 {
|
||||
width: 100%;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
padding: 0;
|
||||
-webkit-align-items: flex-start;
|
||||
-webkit-box-align: flex-start;
|
||||
-ms-flex-align: flex-start;
|
||||
align-items: flex-start;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: start;
|
||||
-webkit-justify-content: flex-start;
|
||||
-ms-flex-pack: start;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.c4 {
|
||||
-webkit-box-pack: justify;
|
||||
-webkit-justify-content: space-between;
|
||||
-ms-flex-pack: justify;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.c2 {
|
||||
.c5 {
|
||||
color: #222222;
|
||||
}
|
||||
|
||||
@ -389,31 +383,43 @@ exports[`SwapModalFooter.tsx renders a preview trade while disabling submission
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.c7 {
|
||||
.c10 {
|
||||
-webkit-animation: fAQEyV 1.5s infinite;
|
||||
animation: fAQEyV 1.5s infinite;
|
||||
-webkit-animation-fill-mode: both;
|
||||
animation-fill-mode: both;
|
||||
background: linear-gradient( to left, #FFFFFF 25%, #22222212 50%, #FFFFFF 75% );
|
||||
background-size: 400%;
|
||||
will-change: background-position;
|
||||
border-radius: 12px;
|
||||
height: 15px;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.c9 {
|
||||
display: inline-block;
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
.c5 {
|
||||
.c7 {
|
||||
text-align: right;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.c6 {
|
||||
cursor: auto;
|
||||
color: #7D7D7D;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.c8 {
|
||||
cursor: help;
|
||||
color: #7D7D7D;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.c6 {
|
||||
text-align: right;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
@media (max-width:960px) {
|
||||
|
||||
}
|
||||
@ -422,102 +428,91 @@ exports[`SwapModalFooter.tsx renders a preview trade while disabling submission
|
||||
class="c0 c1"
|
||||
>
|
||||
<div
|
||||
class="c2 css-142zc9n"
|
||||
class="c2 c3 c4"
|
||||
>
|
||||
<div
|
||||
class="c3 c4"
|
||||
class="c5 c6 css-142zc9n"
|
||||
data-testid="swap-li-label"
|
||||
>
|
||||
<div
|
||||
class="c2 c5 css-142zc9n"
|
||||
>
|
||||
Exchange rate
|
||||
</div>
|
||||
<div
|
||||
class="c2 c6 css-142zc9n"
|
||||
>
|
||||
1 DEF = 1.00 ABC
|
||||
</div>
|
||||
Exchange rate
|
||||
</div>
|
||||
<div
|
||||
class="c5 c7 css-142zc9n"
|
||||
>
|
||||
1 DEF = 1.00 ABC
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c2 css-142zc9n"
|
||||
class="c2 c3 c4"
|
||||
>
|
||||
<div
|
||||
class="c3 c4"
|
||||
class="c5 c8 css-142zc9n"
|
||||
data-testid="swap-li-label"
|
||||
>
|
||||
<div
|
||||
class="c7"
|
||||
>
|
||||
<div>
|
||||
Network fee
|
||||
</div>
|
||||
<div
|
||||
class="c9"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c5 c7 css-142zc9n"
|
||||
>
|
||||
<div
|
||||
class="c2 c8 css-142zc9n"
|
||||
cursor="help"
|
||||
>
|
||||
Network fees
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c7"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c2 c6 css-142zc9n"
|
||||
>
|
||||
-
|
||||
</div>
|
||||
class="c10"
|
||||
data-testid="loading-row"
|
||||
height="15"
|
||||
width="50"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c2 css-142zc9n"
|
||||
class="c2 c3 c4"
|
||||
>
|
||||
<div
|
||||
class="c3 c4"
|
||||
class="c5 c8 css-142zc9n"
|
||||
data-testid="swap-li-label"
|
||||
>
|
||||
<div
|
||||
class="c7"
|
||||
>
|
||||
<div>
|
||||
Price impact
|
||||
</div>
|
||||
<div
|
||||
class="c9"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c5 c7 css-142zc9n"
|
||||
>
|
||||
<div
|
||||
class="c2 c8 css-142zc9n"
|
||||
cursor="help"
|
||||
>
|
||||
Price impact
|
||||
</div>
|
||||
class="c10"
|
||||
data-testid="loading-row"
|
||||
height="15"
|
||||
width="50"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c2 c6 css-142zc9n"
|
||||
>
|
||||
-
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c2 css-142zc9n"
|
||||
class="c2 c3 c4"
|
||||
>
|
||||
<div
|
||||
class="c3 c4"
|
||||
class="c5 c8 css-142zc9n"
|
||||
data-testid="swap-li-label"
|
||||
>
|
||||
<div
|
||||
class="c7"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c2 c8 css-142zc9n"
|
||||
cursor="help"
|
||||
>
|
||||
Minimum received
|
||||
</div>
|
||||
Minimum output
|
||||
</div>
|
||||
<div
|
||||
class="c9"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c5 c7 css-142zc9n"
|
||||
>
|
||||
0.00000000000000098 DEF
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c2 c6 css-142zc9n"
|
||||
>
|
||||
0.00000000000000098 DEF
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
8
src/hooks/useHoverProps.ts
Normal file
8
src/hooks/useHoverProps.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { useState } from 'react'
|
||||
|
||||
export default function useHoverProps(): [boolean, { onMouseEnter: () => void; onMouseLeave: () => void }] {
|
||||
const [hover, setHover] = useState(false)
|
||||
const hoverProps = { onMouseEnter: () => setHover(true), onMouseLeave: () => setHover(false) }
|
||||
|
||||
return [hover, hoverProps]
|
||||
}
|
@ -3,6 +3,7 @@ import { ChainId, Currency, CurrencyAmount, Fraction, Percent, Price, Token, Tra
|
||||
import { DutchOrderInfo, DutchOrderInfoJSON, DutchOrderTrade as IDutchOrderTrade } from '@uniswap/uniswapx-sdk'
|
||||
import { Route as V2Route } from '@uniswap/v2-sdk'
|
||||
import { Route as V3Route } from '@uniswap/v3-sdk'
|
||||
import { ZERO_PERCENT } from 'constants/misc'
|
||||
|
||||
export enum TradeState {
|
||||
LOADING = 'loading',
|
||||
@ -280,6 +281,9 @@ export class DutchOrderTrade extends IDutchOrderTrade<Currency, Currency, TradeT
|
||||
deadlineBufferSecs: number
|
||||
slippageTolerance: Percent
|
||||
|
||||
inputTax = ZERO_PERCENT
|
||||
outputTax = ZERO_PERCENT
|
||||
|
||||
constructor({
|
||||
currencyIn,
|
||||
currenciesOut,
|
||||
|
@ -46,6 +46,9 @@ export const ThemedText = {
|
||||
LabelMicro(props: TextProps) {
|
||||
return <TextWrapper fontWeight={485} fontSize={12} color="neutral2" {...props} />
|
||||
},
|
||||
Caption(props: TextProps) {
|
||||
return <TextWrapper fontWeight={485} fontSize={12} lineHeight="16px" color="neutral1" {...props} />
|
||||
},
|
||||
Link(props: TextProps) {
|
||||
return <TextWrapper fontWeight={485} fontSize={14} color="accent1" {...props} />
|
||||
},
|
||||
|
@ -119,7 +119,7 @@ const SIX_SIG_FIGS_TWO_DECIMALS: NumberFormatOptions = {
|
||||
minimumFractionDigits: 2,
|
||||
}
|
||||
|
||||
export const SIX_SIG_FIGS_NO_COMMAS: NumberFormatOptions = {
|
||||
const SIX_SIG_FIGS_NO_COMMAS: NumberFormatOptions = {
|
||||
notation: 'standard',
|
||||
maximumSignificantDigits: 6,
|
||||
useGrouping: false,
|
||||
@ -178,7 +178,7 @@ type FormatterBaseRule = { formatterOptions: NumberFormatOptions }
|
||||
type FormatterExactRule = { upperBound?: undefined; exact: number } & FormatterBaseRule
|
||||
type FormatterUpperBoundRule = { upperBound: number; exact?: undefined } & FormatterBaseRule
|
||||
|
||||
export type FormatterRule = (FormatterExactRule | FormatterUpperBoundRule) & { hardCodedInput?: HardCodedInputFormat }
|
||||
type FormatterRule = (FormatterExactRule | FormatterUpperBoundRule) & { hardCodedInput?: HardCodedInputFormat }
|
||||
|
||||
// these formatter objects dictate which formatter rule to use based on the interval that
|
||||
// the number falls into. for example, based on the rule set below, if your number
|
||||
@ -215,6 +215,8 @@ const swapTradeAmountFormatter: FormatterRule[] = [
|
||||
{ upperBound: Infinity, formatterOptions: SIX_SIG_FIGS_TWO_DECIMALS_NO_COMMAS },
|
||||
]
|
||||
|
||||
const swapDetailsAmountFormatter: FormatterRule[] = [{ upperBound: Infinity, formatterOptions: SIX_SIG_FIGS_NO_COMMAS }]
|
||||
|
||||
const swapPriceFormatter: FormatterRule[] = [
|
||||
{ exact: 0, formatterOptions: NO_DECIMALS },
|
||||
{
|
||||
@ -322,6 +324,8 @@ export enum NumberType {
|
||||
// in the text input boxes. Output amounts on review screen should use the above TokenTx formatter
|
||||
SwapTradeAmount = 'swap-trade-amount',
|
||||
|
||||
SwapDetailsAmount = 'swap-details-amount',
|
||||
|
||||
// fiat prices in any component that belongs in the Token Details flow (except for token stats)
|
||||
FiatTokenDetails = 'fiat-token-details',
|
||||
|
||||
@ -356,6 +360,7 @@ const TYPE_TO_FORMATTER_RULES = {
|
||||
[NumberType.TokenTx]: tokenTxFormatter,
|
||||
[NumberType.SwapPrice]: swapPriceFormatter,
|
||||
[NumberType.SwapTradeAmount]: swapTradeAmountFormatter,
|
||||
[NumberType.SwapDetailsAmount]: swapDetailsAmountFormatter,
|
||||
[NumberType.FiatTokenQuantity]: fiatTokenQuantityFormatter,
|
||||
[NumberType.FiatTokenDetails]: fiatTokenDetailsFormatter,
|
||||
[NumberType.FiatTokenPrice]: fiatTokenPricesFormatter,
|
||||
|
@ -109,7 +109,7 @@ export function getPriceImpactWarning(priceImpact: Percent): 'warning' | 'error'
|
||||
export function getPriceImpactColor(priceImpact: Percent): keyof DefaultTheme | undefined {
|
||||
switch (getPriceImpactWarning(priceImpact)) {
|
||||
case 'error':
|
||||
return 'deprecated_accentFailureSoft'
|
||||
return 'critical'
|
||||
case 'warning':
|
||||
return 'deprecated_accentWarning'
|
||||
default:
|
||||
|
Loading…
Reference in New Issue
Block a user