Commit 0050b1e1 authored by Mike Grabowski's avatar Mike Grabowski Committed by GitHub

feat: new routing diagram (#6510)

* chore: initial commit

* chore: add todo to refactor and simplify if conditional in the future

* chore: update layout

* chore: ui tweaks

* chore: add todo

* chore: change todo

* chore: update UI

* chore: tmp

* feat: rename router preference

* chore: update type

* fix error

* fix one more issue

* finish UI work

* chore: remove unecessary components

* chore: update non-snapshot unit tests

* chore: fix lint

* chore: fix ts

* chore: one more time

* fix

* chore: update snapshots

* chore: add jira tickets

* chore: fix mobile popovers

* chore: add analytics event

* chore: fix padding and send event

* chore: fix loading state

* oops

* chore: address review

* chore: comment

* chore
parent 5bf33ab0
...@@ -95,7 +95,7 @@ export const ChainSelector = ({ leftAlign }: ChainSelectorProps) => { ...@@ -95,7 +95,7 @@ export const ChainSelector = ({ leftAlign }: ChainSelectorProps) => {
return ( return (
<Box position="relative" ref={ref}> <Box position="relative" ref={ref}>
<MouseoverTooltip text={t`Your wallet's current network is unsupported.`} disableHover={isSupported}> <MouseoverTooltip text={t`Your wallet's current network is unsupported.`} disabled={isSupported}>
<Row <Row
as="button" as="button"
gap="8" gap="8"
......
...@@ -99,8 +99,8 @@ export default function Popover({ ...@@ -99,8 +99,8 @@ export default function Popover({
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null) const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null)
const [arrowElement, setArrowElement] = useState<HTMLDivElement | null>(null) const [arrowElement, setArrowElement] = useState<HTMLDivElement | null>(null)
const options = useMemo( const options: Options = useMemo(
(): Options => ({ () => ({
placement, placement,
strategy: 'fixed', strategy: 'fixed',
modifiers: [ modifiers: [
...@@ -109,7 +109,7 @@ export default function Popover({ ...@@ -109,7 +109,7 @@ export default function Popover({
{ name: 'preventOverflow', options: { padding: 8 } }, { name: 'preventOverflow', options: { padding: 8 } },
], ],
}), }),
[arrowElement, offsetX, offsetY, placement] [placement, offsetX, offsetY, arrowElement]
) )
const { styles, update, attributes } = usePopper(referenceElement, popperElement, options) const { styles, update, attributes } = usePopper(referenceElement, popperElement, options)
......
import { Protocol } from '@uniswap/router-sdk' import { Protocol } from '@uniswap/router-sdk'
import { Currency, Percent } from '@uniswap/sdk-core' import { Currency, Percent } from '@uniswap/sdk-core'
import { FeeAmount } from '@uniswap/v3-sdk' import { FeeAmount } from '@uniswap/v3-sdk'
import { RoutingDiagramEntry } from 'components/swap/SwapRoute'
import { DAI, USDC_MAINNET, WBTC } from 'constants/tokens' import { DAI, USDC_MAINNET, WBTC } from 'constants/tokens'
import { render } from 'test-utils/render' import { render } from 'test-utils/render'
import { RoutingDiagramEntry } from 'utils/getRoutingDiagramEntries'
import RoutingDiagram from './RoutingDiagram' import RoutingDiagram from './RoutingDiagram'
......
...@@ -6,12 +6,12 @@ import Badge from 'components/Badge' ...@@ -6,12 +6,12 @@ import Badge from 'components/Badge'
import DoubleCurrencyLogo from 'components/DoubleLogo' import DoubleCurrencyLogo from 'components/DoubleLogo'
import CurrencyLogo from 'components/Logo/CurrencyLogo' import CurrencyLogo from 'components/Logo/CurrencyLogo'
import Row, { AutoRow } from 'components/Row' import Row, { AutoRow } from 'components/Row'
import { RoutingDiagramEntry } from 'components/swap/SwapRoute'
import { useTokenInfoFromActiveList } from 'hooks/useTokenInfoFromActiveList' import { useTokenInfoFromActiveList } from 'hooks/useTokenInfoFromActiveList'
import { Box } from 'rebass' import { Box } from 'rebass'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import { ThemedText } from 'theme' import { ThemedText } from 'theme'
import { Z_INDEX } from 'theme/zIndex' import { Z_INDEX } from 'theme/zIndex'
import { RoutingDiagramEntry } from 'utils/getRoutingDiagramEntries'
import { ReactComponent as DotLine } from '../../assets/svg/dot_line.svg' import { ReactComponent as DotLine } from '../../assets/svg/dot_line.svg'
import { MouseoverTooltip } from '../Tooltip' import { MouseoverTooltip } from '../Tooltip'
......
import { transparentize } from 'polished' import { transparentize } from 'polished'
import { ReactNode, useEffect, useState } from 'react' import { PropsWithChildren, ReactNode, useEffect, useState } from 'react'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import noop from 'utils/noop' import noop from 'utils/noop'
import Popover, { PopoverProps } from '../Popover' import Popover, { PopoverProps } from '../Popover'
export const TooltipContainer = styled.div` export enum TooltipSize {
max-width: 256px; Small = '256px',
Large = '400px',
}
const getPaddingForSize = (size: TooltipSize) => {
switch (size) {
case TooltipSize.Small:
return '12px'
case TooltipSize.Large:
return '16px 20px'
}
}
const TooltipContainer = styled.div<{ size: TooltipSize }>`
max-width: ${({ size }) => size};
width: calc(100vw - 16px);
cursor: default; cursor: default;
padding: 0.6rem 1rem; padding: ${({ size }) => getPaddingForSize(size)};
pointer-events: auto; pointer-events: auto;
color: ${({ theme }) => theme.textPrimary}; color: ${({ theme }) => theme.textPrimary};
...@@ -23,30 +38,23 @@ export const TooltipContainer = styled.div` ...@@ -23,30 +38,23 @@ export const TooltipContainer = styled.div`
box-shadow: 0 4px 8px 0 ${({ theme }) => transparentize(0.9, theme.shadow1)}; box-shadow: 0 4px 8px 0 ${({ theme }) => transparentize(0.9, theme.shadow1)};
` `
interface TooltipProps extends Omit<PopoverProps, 'content'> { type TooltipProps = Omit<PopoverProps, 'content'> & {
text: ReactNode text: ReactNode
open?: () => void open?: () => void
close?: () => void close?: () => void
disableHover?: boolean // disable the hover and content display size?: TooltipSize
disabled?: boolean
timeout?: number timeout?: number
} }
interface TooltipContentProps extends Omit<PopoverProps, 'content'> { // TODO(WEB-3305)
content: ReactNode // Migrate to MouseoverTooltip and move this component inline to MouseoverTooltip
onOpen?: () => void export default function Tooltip({ text, open, close, disabled, size = TooltipSize.Small, ...rest }: TooltipProps) {
open?: () => void
close?: () => void
// whether to wrap the content in a `TooltipContainer`
wrap?: boolean
disableHover?: boolean // disable the hover and content display
}
export default function Tooltip({ text, open, close, disableHover, ...rest }: TooltipProps) {
return ( return (
<Popover <Popover
content={ content={
text && ( text && (
<TooltipContainer onMouseEnter={disableHover ? noop : open} onMouseLeave={disableHover ? noop : close}> <TooltipContainer size={size} onMouseEnter={disabled ? noop : open} onMouseLeave={disabled ? noop : close}>
{text} {text}
</TooltipContainer> </TooltipContainer>
) )
...@@ -56,27 +64,24 @@ export default function Tooltip({ text, open, close, disableHover, ...rest }: To ...@@ -56,27 +64,24 @@ export default function Tooltip({ text, open, close, disableHover, ...rest }: To
) )
} }
function TooltipContent({ content, wrap = false, open, close, disableHover, ...rest }: TooltipContentProps) { // TODO(WEB-3305)
return ( // Do not pass through PopoverProps. Prefer higher-level interface to control MouseoverTooltip.
<Popover type MouseoverTooltipProps = Omit<PopoverProps, 'content' | 'show'> &
content={ PropsWithChildren<{
wrap ? ( text: ReactNode
<TooltipContainer onMouseEnter={disableHover ? noop : open} onMouseLeave={disableHover ? noop : close}> size?: TooltipSize
{content} disabled?: boolean
</TooltipContainer> timeout?: number
) : ( placement?: PopoverProps['placement']
content onOpen?: () => void
) }>
}
{...rest}
/>
)
}
/** Standard text tooltip. */ export function MouseoverTooltip({ text, disabled, children, onOpen, timeout, ...rest }: MouseoverTooltipProps) {
export function MouseoverTooltip({ text, disableHover, children, timeout, ...rest }: Omit<TooltipProps, 'show'>) {
const [show, setShow] = useState(false) const [show, setShow] = useState(false)
const open = () => text && setShow(true) const open = () => {
setShow(true)
onOpen?.()
}
const close = () => setShow(false) const close = () => setShow(false)
useEffect(() => { useEffect(() => {
...@@ -93,49 +98,10 @@ export function MouseoverTooltip({ text, disableHover, children, timeout, ...res ...@@ -93,49 +98,10 @@ export function MouseoverTooltip({ text, disableHover, children, timeout, ...res
}, [timeout, show]) }, [timeout, show])
return ( return (
<Tooltip <Tooltip {...rest} open={open} close={close} disabled={disabled} show={show} text={disabled ? null : text}>
{...rest} <div onMouseEnter={disabled ? noop : open} onMouseLeave={disabled || timeout ? noop : close}>
open={open}
close={close}
disableHover={disableHover}
show={show}
text={disableHover ? null : text}
>
<div onMouseEnter={disableHover ? noop : open} onMouseLeave={disableHover || timeout ? noop : close}>
{children} {children}
</div> </div>
</Tooltip> </Tooltip>
) )
} }
/** Tooltip that displays custom content. */
export function MouseoverTooltipContent({
content,
children,
onOpen: openCallback = undefined,
disableHover,
...rest
}: Omit<TooltipContentProps, 'show'>) {
const [show, setShow] = useState(false)
const open = () => {
setShow(true)
openCallback?.()
}
const close = () => {
setShow(false)
}
return (
<TooltipContent
{...rest}
open={open}
close={close}
show={!disableHover && show}
content={disableHover ? null : content}
>
<div onMouseEnter={open} onMouseLeave={close}>
{children}
</div>
</TooltipContent>
)
}
...@@ -20,20 +20,18 @@ describe('AdvancedSwapDetails.tsx', () => { ...@@ -20,20 +20,18 @@ describe('AdvancedSwapDetails.tsx', () => {
it('renders correct copy on mouseover', async () => { it('renders correct copy on mouseover', async () => {
render(<AdvancedSwapDetails trade={TEST_TRADE_EXACT_INPUT} allowedSlippage={TEST_ALLOWED_SLIPPAGE} />) render(<AdvancedSwapDetails trade={TEST_TRADE_EXACT_INPUT} allowedSlippage={TEST_ALLOWED_SLIPPAGE} />)
await act(() => userEvent.hover(screen.getByText('Price Impact'))) await act(() => userEvent.hover(screen.getByText('Expected output')))
expect(await screen.getByText(/The impact your trade has on the market price of this pool./i)).toBeVisible()
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() expect(await screen.getByText(/The amount you expect to receive at the current market price./i)).toBeVisible()
await act(() => userEvent.hover(screen.getByText(/Minimum received/i))) await act(() => userEvent.hover(screen.getByText(/Minimum output/i)))
expect(await screen.getByText(/The minimum amount you are guaranteed to receive./i)).toBeVisible() 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 () => { it('renders correct tooltips for test trade with exact output and gas use estimate USD', async () => {
TEST_TRADE_EXACT_OUTPUT.gasUseEstimateUSD = toCurrencyAmount(TEST_TOKEN_1, 1) TEST_TRADE_EXACT_OUTPUT.gasUseEstimateUSD = toCurrencyAmount(TEST_TOKEN_1, 1)
render(<AdvancedSwapDetails trade={TEST_TRADE_EXACT_OUTPUT} allowedSlippage={TEST_ALLOWED_SLIPPAGE} />) render(<AdvancedSwapDetails trade={TEST_TRADE_EXACT_OUTPUT} allowedSlippage={TEST_ALLOWED_SLIPPAGE} />)
await act(() => userEvent.hover(screen.getByText(/Maximum sent/i))) await act(() => userEvent.hover(screen.getByText(/Minimum output/i)))
expect(await screen.getByText(/The minimum amount you are guaranteed to receive./i)).toBeVisible() expect(await screen.getByText(/The minimum amount you are guaranteed to receive./i)).toBeVisible()
await act(() => userEvent.hover(screen.getByText('Network Fee'))) await act(() => userEvent.hover(screen.getByText('Network fee')))
expect(await screen.getByText(/The fee paid to miners who process your transaction./i)).toBeVisible() expect(await screen.getByText(/The fee paid to miners who process your transaction./i)).toBeVisible()
}) })
......
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import { sendAnalyticsEvent } from '@uniswap/analytics'
import { InterfaceElementName, SwapEventName } from '@uniswap/analytics-events'
import { Currency, Percent, TradeType } from '@uniswap/sdk-core' import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
import Card from 'components/Card'
import { LoadingRows } from 'components/Loader/styled' import { LoadingRows } from 'components/Loader/styled'
import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains' import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains'
import useNativeCurrency from 'lib/hooks/useNativeCurrency' import useNativeCurrency from 'lib/hooks/useNativeCurrency'
import { useMemo } from 'react'
import { InterfaceTrade } from 'state/routing/types' import { InterfaceTrade } from 'state/routing/types'
import styled, { useTheme } from 'styled-components/macro'
import { Separator, ThemedText } from '../../theme' import { Separator, ThemedText } from '../../theme'
import { computeRealizedPriceImpact } from '../../utils/prices' import Column from '../Column'
import { AutoColumn } from '../Column'
import { RowBetween, RowFixed } from '../Row' import { RowBetween, RowFixed } from '../Row'
import { MouseoverTooltip } from '../Tooltip' import { MouseoverTooltip, TooltipSize } from '../Tooltip'
import FormattedPriceImpact from './FormattedPriceImpact' import RouterLabel from './RouterLabel'
import SwapRoute from './SwapRoute'
const StyledCard = styled(Card)`
padding: 0;
`
interface AdvancedSwapDetailsProps { interface AdvancedSwapDetailsProps {
trade?: InterfaceTrade<Currency, Currency, TradeType> trade: InterfaceTrade<Currency, Currency, TradeType>
allowedSlippage: Percent allowedSlippage: Percent
syncing?: boolean syncing?: boolean
hideInfoTooltips?: boolean
} }
function TextWithLoadingPlaceholder({ function TextWithLoadingPlaceholder({
...@@ -45,119 +39,92 @@ function TextWithLoadingPlaceholder({ ...@@ -45,119 +39,92 @@ function TextWithLoadingPlaceholder({
) )
} }
export function AdvancedSwapDetails({ export function AdvancedSwapDetails({ trade, allowedSlippage, syncing = false }: AdvancedSwapDetailsProps) {
trade,
allowedSlippage,
syncing = false,
hideInfoTooltips = false,
}: AdvancedSwapDetailsProps) {
const theme = useTheme()
const { chainId } = useWeb3React() const { chainId } = useWeb3React()
const nativeCurrency = useNativeCurrency(chainId) const nativeCurrency = useNativeCurrency(chainId)
const { expectedOutputAmount, priceImpact } = useMemo(() => { return (
return { <Column gap="md">
expectedOutputAmount: trade?.outputAmount, <Separator />
priceImpact: trade ? computeRealizedPriceImpact(trade) : undefined, {!trade.gasUseEstimateUSD || !chainId || !SUPPORTED_GAS_ESTIMATE_CHAIN_IDS.includes(chainId) ? null : (
}
}, [trade])
return !trade ? null : (
<StyledCard>
<AutoColumn gap="sm">
<RowBetween> <RowBetween>
<RowFixed>
<MouseoverTooltip <MouseoverTooltip
text={ text={
<Trans> <Trans>
The amount you expect to receive at the current market price. You may receive less or more if the The fee paid to miners who process your transaction. This must be paid in {nativeCurrency.symbol}.
market price changes while your transaction is pending.
</Trans> </Trans>
} }
disableHover={hideInfoTooltips}
>
<ThemedText.DeprecatedSubHeader color={theme.textPrimary}>
<Trans>Expected Output</Trans>
</ThemedText.DeprecatedSubHeader>
</MouseoverTooltip>
</RowFixed>
<TextWithLoadingPlaceholder syncing={syncing} width={65}>
<ThemedText.DeprecatedBlack textAlign="right" fontSize={14}>
{expectedOutputAmount
? `${expectedOutputAmount.toSignificant(6)} ${expectedOutputAmount.currency.symbol}`
: '-'}
</ThemedText.DeprecatedBlack>
</TextWithLoadingPlaceholder>
</RowBetween>
<RowBetween>
<RowFixed>
<MouseoverTooltip
text={<Trans>The impact your trade has on the market price of this pool.</Trans>}
disableHover={hideInfoTooltips}
> >
<ThemedText.DeprecatedSubHeader color={theme.textPrimary}> <ThemedText.BodySmall color="textSecondary">
<Trans>Price Impact</Trans> <Trans>Network fee</Trans>
</ThemedText.DeprecatedSubHeader> </ThemedText.BodySmall>
</MouseoverTooltip> </MouseoverTooltip>
</RowFixed>
<TextWithLoadingPlaceholder syncing={syncing} width={50}> <TextWithLoadingPlaceholder syncing={syncing} width={50}>
<ThemedText.DeprecatedBlack textAlign="right" fontSize={14}> <ThemedText.BodySmall>~${trade.gasUseEstimateUSD.toFixed(2)}</ThemedText.BodySmall>
<FormattedPriceImpact priceImpact={priceImpact} />
</ThemedText.DeprecatedBlack>
</TextWithLoadingPlaceholder> </TextWithLoadingPlaceholder>
</RowBetween> </RowBetween>
<Separator /> )}
<RowBetween> <RowBetween>
<RowFixed style={{ marginRight: '20px' }}> <RowFixed>
<MouseoverTooltip <MouseoverTooltip
text={ text={
<Trans> <Trans>
The minimum amount you are guaranteed to receive. If the price slips any further, your transaction The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will
will revert. revert.
</Trans> </Trans>
} }
disableHover={hideInfoTooltips}
> >
<ThemedText.DeprecatedSubHeader color={theme.textTertiary}> <ThemedText.BodySmall color="textSecondary">
{trade.tradeType === TradeType.EXACT_INPUT ? ( <Trans>Minimum output</Trans>
<Trans>Minimum received</Trans> </ThemedText.BodySmall>
) : (
<Trans>Maximum sent</Trans>
)}{' '}
<Trans>after slippage</Trans> ({allowedSlippage.toFixed(2)}%)
</ThemedText.DeprecatedSubHeader>
</MouseoverTooltip> </MouseoverTooltip>
</RowFixed> </RowFixed>
<TextWithLoadingPlaceholder syncing={syncing} width={70}> <TextWithLoadingPlaceholder syncing={syncing} width={70}>
<ThemedText.DeprecatedBlack textAlign="right" fontSize={14} color={theme.textTertiary}> <ThemedText.BodySmall>
{trade.tradeType === TradeType.EXACT_INPUT {trade.tradeType === TradeType.EXACT_INPUT
? `${trade.minimumAmountOut(allowedSlippage).toSignificant(6)} ${trade.outputAmount.currency.symbol}` ? `${trade.minimumAmountOut(allowedSlippage).toSignificant(6)} ${trade.outputAmount.currency.symbol}`
: `${trade.maximumAmountIn(allowedSlippage).toSignificant(6)} ${trade.inputAmount.currency.symbol}`} : `${trade.maximumAmountIn(allowedSlippage).toSignificant(6)} ${trade.inputAmount.currency.symbol}`}
</ThemedText.DeprecatedBlack> </ThemedText.BodySmall>
</TextWithLoadingPlaceholder> </TextWithLoadingPlaceholder>
</RowBetween> </RowBetween>
{!trade?.gasUseEstimateUSD || !chainId || !SUPPORTED_GAS_ESTIMATE_CHAIN_IDS.includes(chainId) ? null : (
<RowBetween> <RowBetween>
<RowFixed>
<MouseoverTooltip <MouseoverTooltip
text={ text={
<Trans> <Trans>
The fee paid to miners who process your transaction. This must be paid in {nativeCurrency.symbol}. 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> </Trans>
} }
disableHover={hideInfoTooltips}
> >
<ThemedText.DeprecatedSubHeader color={theme.textTertiary}> <ThemedText.BodySmall color="textSecondary">
<Trans>Network Fee</Trans> <Trans>Expected output</Trans>
</ThemedText.DeprecatedSubHeader> </ThemedText.BodySmall>
</MouseoverTooltip> </MouseoverTooltip>
<TextWithLoadingPlaceholder syncing={syncing} width={50}> </RowFixed>
<ThemedText.DeprecatedBlack textAlign="right" fontSize={14} color={theme.textTertiary}> <TextWithLoadingPlaceholder syncing={syncing} width={65}>
~${trade.gasUseEstimateUSD.toFixed(2)} <ThemedText.BodySmall>
</ThemedText.DeprecatedBlack> {`${trade.outputAmount.toSignificant(6)} ${trade.outputAmount.currency.symbol}`}
</ThemedText.BodySmall>
</TextWithLoadingPlaceholder> </TextWithLoadingPlaceholder>
</RowBetween> </RowBetween>
)} <Separator />
</AutoColumn> <RowBetween>
</StyledCard> <ThemedText.BodySmall color="textSecondary">
<Trans>Order routing</Trans>
</ThemedText.BodySmall>
<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 />
</MouseoverTooltip>
</RowBetween>
</Column>
) )
} }
import { useRef } from 'react'
let uniqueId = 0
const getUniqueId = () => uniqueId++
export default function AutoRouterIcon({ className, id }: { className?: string; id?: string }) {
const componentIdRef = useRef(id ?? getUniqueId())
const componentId = `AutoRouterIconGradient${componentIdRef.current}`
return (
<svg
width="23"
height="20"
viewBox="0 0 23 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<defs>
<linearGradient id={componentId} x1="0" y1="0" x2="1" y2="0" gradientTransform="rotate(95)">
<stop id="stop1" offset="0" stopColor="#2274E2" />
<stop id="stop1" offset="0.5" stopColor="#2274E2" />
<stop id="stop2" offset="1" stopColor="#3FB672" />
</linearGradient>
</defs>
<path
d="M16 16C10 16 9 10 5 10M16 16C16 17.6569 17.3431 19 19 19C20.6569 19 22 17.6569 22 16C22 14.3431 20.6569 13 19 13C17.3431 13 16 14.3431 16 16ZM5 10C9 10 10 4 16 4M5 10H1.5M16 4C16 5.65685 17.3431 7 19 7C20.6569 7 22 5.65685 22 4C22 2.34315 20.6569 1 19 1C17.3431 1 16 2.34315 16 4Z"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
stroke={`url(#${componentId})`}
/>
</svg>
)
}
import { Percent } from '@uniswap/sdk-core'
import { warningSeverity } from '../../utils/prices'
import { ErrorText } from './styleds'
export const formatPriceImpact = (priceImpact: Percent) => `${priceImpact.multiply(-1).toFixed(2)}%`
/**
* Formatted version of price impact text with warning colors
*/
export default function FormattedPriceImpact({ priceImpact }: { priceImpact?: Percent }) {
return (
<ErrorText fontWeight={500} fontSize={14} severity={warningSeverity(priceImpact)}>
{priceImpact ? formatPriceImpact(priceImpact) : '-'}
</ErrorText>
)
}
import { Trans } from '@lingui/macro'
import { Currency, TradeType } from '@uniswap/sdk-core'
import { sendEvent } from 'components/analytics'
import { AutoColumn } from 'components/Column'
import { LoadingOpacityContainer } from 'components/Loader/styled'
import { RowFixed } from 'components/Row'
import { MouseoverTooltipContent } from 'components/Tooltip'
import { InterfaceTrade } from 'state/routing/types'
import styled from 'styled-components/macro'
import { ThemedText } from 'theme'
import { ReactComponent as GasIcon } from '../../assets/images/gas-icon.svg'
import { ResponsiveTooltipContainer } from './styleds'
import SwapRoute from './SwapRoute'
const GasWrapper = styled(RowFixed)`
border-radius: 8px;
padding: 4px 6px;
height: 24px;
color: ${({ theme }) => theme.textTertiary};
background-color: ${({ theme }) => theme.deprecated_bg1};
font-size: 14px;
font-weight: 500;
user-select: none;
`
const StyledGasIcon = styled(GasIcon)`
margin-right: 4px;
height: 14px;
& > * {
stroke: ${({ theme }) => theme.textTertiary};
}
`
export default function GasEstimateBadge({
trade,
loading,
showRoute,
disableHover,
}: {
trade: InterfaceTrade<Currency, Currency, TradeType> | undefined | null // dollar amount in active chain's stablecoin
loading: boolean
showRoute?: boolean // show route instead of gas estimation summary
disableHover?: boolean
}) {
const formattedGasPriceString = trade?.gasUseEstimateUSD
? trade.gasUseEstimateUSD.toFixed(2) === '0.00'
? '<$0.01'
: '$' + trade.gasUseEstimateUSD.toFixed(2)
: undefined
return (
<MouseoverTooltipContent
wrap={false}
disableHover={disableHover}
content={
loading ? null : (
<ResponsiveTooltipContainer
origin="top right"
style={{
padding: showRoute ? '0' : '12px',
border: 'none',
borderRadius: showRoute ? '16px' : '12px',
maxWidth: '400px',
}}
>
{showRoute ? (
trade ? (
<SwapRoute trade={trade} syncing={loading} fixedOpen={showRoute} />
) : null
) : (
<AutoColumn gap="4px" justify="center">
<ThemedText.DeprecatedMain fontSize="12px" textAlign="center">
<Trans>Estimated network fee</Trans>
</ThemedText.DeprecatedMain>
<ThemedText.DeprecatedBody textAlign="center" fontWeight={500} style={{ userSelect: 'none' }}>
<Trans>${trade?.gasUseEstimateUSD?.toFixed(2)}</Trans>
</ThemedText.DeprecatedBody>
<ThemedText.DeprecatedMain fontSize="10px" textAlign="center" maxWidth="140px" color="text3">
<Trans>Estimate may differ due to your wallet gas settings</Trans>
</ThemedText.DeprecatedMain>
</AutoColumn>
)}
</ResponsiveTooltipContainer>
)
}
placement="bottom"
onOpen={() =>
sendEvent({
category: 'Gas',
action: 'Gas Details Tooltip Open',
})
}
>
<LoadingOpacityContainer $loading={loading}>
<GasWrapper>
<StyledGasIcon />
{formattedGasPriceString ?? null}
</GasWrapper>
</LoadingOpacityContainer>
</MouseoverTooltipContent>
)
}
import { sendAnalyticsEvent } from '@uniswap/analytics'
import { InterfaceElementName, SwapEventName } from '@uniswap/analytics-events'
import { Currency, TradeType } from '@uniswap/sdk-core'
import { LoadingOpacityContainer } from 'components/Loader/styled'
import { RowFixed } from 'components/Row'
import { MouseoverTooltip, TooltipSize } from 'components/Tooltip'
import { InterfaceTrade } from 'state/routing/types'
import styled from 'styled-components/macro'
import { ThemedText } from 'theme'
import { ReactComponent as GasIcon } from '../../assets/images/gas-icon.svg'
import SwapRoute from './SwapRoute'
const StyledGasIcon = styled(GasIcon)`
margin-right: 4px;
height: 18px;
// We apply the following to all children of the SVG in order to override the default color
& > * {
stroke: ${({ theme }) => theme.textTertiary};
}
`
export default function GasEstimateTooltip({
trade,
loading,
disabled,
}: {
trade: InterfaceTrade<Currency, Currency, TradeType> // dollar amount in active chain's stablecoin
loading: boolean
disabled?: boolean
}) {
const formattedGasPriceString = trade?.gasUseEstimateUSD
? trade.gasUseEstimateUSD.toFixed(2) === '0.00'
? '<$0.01'
: '$' + trade.gasUseEstimateUSD.toFixed(2)
: undefined
return (
<MouseoverTooltip
disabled={disabled}
size={TooltipSize.Large}
// TODO(WEB-3304)
// Most of Swap-related components accept either `syncing`, `loading` or both props at the same time.
// We are often using them interchangeably, or pass both values as one of them (`syncing={loading || syncing}`).
// This is confusing and can lead to unpredicted UI behavior. We should refactor and unify this.
text={<SwapRoute trade={trade} syncing={loading} />}
onOpen={() => {
sendAnalyticsEvent(SwapEventName.SWAP_AUTOROUTER_VISUALIZATION_EXPANDED, {
element: InterfaceElementName.AUTOROUTER_VISUALIZATION_ROW,
})
}}
placement="bottom"
>
<LoadingOpacityContainer $loading={loading}>
<RowFixed>
<StyledGasIcon />
<ThemedText.BodySmall color="textSecondary">{formattedGasPriceString}</ThemedText.BodySmall>
</RowFixed>
</LoadingOpacityContainer>
</MouseoverTooltip>
)
}
...@@ -8,7 +8,6 @@ import { ThemedText } from '../../theme' ...@@ -8,7 +8,6 @@ import { ThemedText } from '../../theme'
import { AutoColumn } from '../Column' import { AutoColumn } from '../Column'
import { RowBetween, RowFixed } from '../Row' import { RowBetween, RowFixed } from '../Row'
import { MouseoverTooltip } from '../Tooltip' import { MouseoverTooltip } from '../Tooltip'
import { formatPriceImpact } from './FormattedPriceImpact'
const StyledCard = styled(OutlineCard)` const StyledCard = styled(OutlineCard)`
padding: 12px; padding: 12px;
...@@ -19,6 +18,8 @@ interface PriceImpactWarningProps { ...@@ -19,6 +18,8 @@ interface PriceImpactWarningProps {
priceImpact: Percent priceImpact: Percent
} }
const formatPriceImpact = (priceImpact: Percent) => `${priceImpact.multiply(-1).toFixed(2)}%`
export default function PriceImpactWarning({ priceImpact }: PriceImpactWarningProps) { export default function PriceImpactWarning({ priceImpact }: PriceImpactWarningProps) {
const theme = useTheme() const theme = useTheme()
......
import { Trans } from '@lingui/macro' import { RouterPreference } from 'state/routing/slice'
import useAutoRouterSupported from 'hooks/useAutoRouterSupported' import { useRouterPreference } from 'state/user/hooks'
import styled from 'styled-components/macro'
import { ThemedText } from 'theme' import { ThemedText } from 'theme'
import { ReactComponent as StaticRouterIcon } from '../../assets/svg/static_route.svg' export default function RouterLabel() {
import AutoRouterIcon from './AutoRouterIcon' const [routerPreference] = useRouterPreference()
const StyledAutoRouterIcon = styled(AutoRouterIcon)` switch (routerPreference) {
height: 16px; case RouterPreference.AUTO:
width: 16px; case RouterPreference.API:
return <ThemedText.BodySmall>Uniswap API</ThemedText.BodySmall>
:hover { case RouterPreference.CLIENT:
filter: brightness(1.3); return <ThemedText.BodySmall>Uniswap Client</ThemedText.BodySmall>
}
`
const StyledStaticRouterIcon = styled(StaticRouterIcon)`
height: 16px;
width: 16px;
fill: ${({ theme }) => theme.textTertiary};
:hover {
filter: brightness(1.3);
}
`
const StyledAutoRouterLabel = styled(ThemedText.DeprecatedBlack)`
line-height: 1rem;
/* fallback color */
color: ${({ theme }) => theme.accentSuccess};
@supports (-webkit-background-clip: text) and (-webkit-text-fill-color: transparent) {
background-image: linear-gradient(90deg, #2172e5 0%, #54e521 163.16%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
} }
`
export function AutoRouterLogo() {
const autoRouterSupported = useAutoRouterSupported()
return autoRouterSupported ? <StyledAutoRouterIcon /> : <StyledStaticRouterIcon />
}
export function AutoRouterLabel() {
const autoRouterSupported = useAutoRouterSupported()
return autoRouterSupported ? (
<StyledAutoRouterLabel fontSize={14}>Auto Router</StyledAutoRouterLabel>
) : (
<ThemedText.DeprecatedBlack fontSize={14}>
<Trans>Trade Route</Trans>
</ThemedText.DeprecatedBlack>
)
} }
...@@ -4,7 +4,7 @@ import { BrowserEvent, InterfaceElementName, SharedEventName } from '@uniswap/an ...@@ -4,7 +4,7 @@ import { BrowserEvent, InterfaceElementName, SharedEventName } from '@uniswap/an
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
import { useAccountDrawer } from 'components/AccountDrawer' import { useAccountDrawer } from 'components/AccountDrawer'
import { ButtonText } from 'components/Button' import { ButtonText } from 'components/Button'
import { MouseoverTooltipContent } from 'components/Tooltip' import { MouseoverTooltip } from 'components/Tooltip'
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
import { useBuyFiatFlowCompleted } from 'state/user/hooks' import { useBuyFiatFlowCompleted } from 'state/user/hooks'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
...@@ -109,9 +109,8 @@ export default function SwapBuyFiatButton() { ...@@ -109,9 +109,8 @@ export default function SwapBuyFiatButton() {
!fiatOnrampAvailabilityChecked || (fiatOnrampAvailabilityChecked && fiatOnrampAvailable) !fiatOnrampAvailabilityChecked || (fiatOnrampAvailabilityChecked && fiatOnrampAvailable)
return ( return (
<MouseoverTooltipContent <MouseoverTooltip
wrap text={
content={
<div data-testid="fiat-on-ramp-unavailable-tooltip"> <div data-testid="fiat-on-ramp-unavailable-tooltip">
<Trans>Crypto purchases are not available in your region. </Trans> <Trans>Crypto purchases are not available in your region. </Trans>
<TraceEvent <TraceEvent
...@@ -126,7 +125,7 @@ export default function SwapBuyFiatButton() { ...@@ -126,7 +125,7 @@ export default function SwapBuyFiatButton() {
</div> </div>
} }
placement="bottom" placement="bottom"
disableHover={fiatOnRampsUnavailableTooltipDisabled} disabled={fiatOnRampsUnavailableTooltipDisabled}
> >
<TraceEvent <TraceEvent
events={[BrowserEvent.onClick]} events={[BrowserEvent.onClick]}
...@@ -139,6 +138,6 @@ export default function SwapBuyFiatButton() { ...@@ -139,6 +138,6 @@ export default function SwapBuyFiatButton() {
{!buyFiatFlowCompleted && <Dot data-testid="buy-fiat-flow-incomplete-indicator" />} {!buyFiatFlowCompleted && <Dot data-testid="buy-fiat-flow-incomplete-indicator" />}
</StyledTextButton> </StyledTextButton>
</TraceEvent> </TraceEvent>
</MouseoverTooltipContent> </MouseoverTooltip>
) )
} }
...@@ -38,6 +38,5 @@ describe('SwapDetailsDropdown.tsx', () => { ...@@ -38,6 +38,5 @@ describe('SwapDetailsDropdown.tsx', () => {
expect(screen.getByTestId('trade-price-container')).toBeInTheDocument() expect(screen.getByTestId('trade-price-container')).toBeInTheDocument()
await act(() => userEvent.click(screen.getByTestId('swap-details-header-row'))) await act(() => userEvent.click(screen.getByTestId('swap-details-header-row')))
expect(screen.getByTestId('advanced-swap-details')).toBeInTheDocument() expect(screen.getByTestId('advanced-swap-details')).toBeInTheDocument()
expect(screen.getByTestId('swap-route-info')).toBeInTheDocument()
}) })
}) })
...@@ -4,10 +4,9 @@ import { BrowserEvent, InterfaceElementName, SwapEventName } from '@uniswap/anal ...@@ -4,10 +4,9 @@ import { BrowserEvent, InterfaceElementName, SwapEventName } from '@uniswap/anal
import { Currency, Percent, TradeType } from '@uniswap/sdk-core' import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
import AnimatedDropdown from 'components/AnimatedDropdown' import AnimatedDropdown from 'components/AnimatedDropdown'
import { OutlineCard } from 'components/Card' import Column from 'components/Column'
import { AutoColumn } from 'components/Column'
import { LoadingOpacityContainer } from 'components/Loader/styled' import { LoadingOpacityContainer } from 'components/Loader/styled'
import Row, { RowBetween, RowFixed } from 'components/Row' import { RowBetween, RowFixed } from 'components/Row'
import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains' import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains'
import { useState } from 'react' import { useState } from 'react'
import { ChevronDown } from 'react-feather' import { ChevronDown } from 'react-feather'
...@@ -16,24 +15,9 @@ import styled, { keyframes, useTheme } from 'styled-components/macro' ...@@ -16,24 +15,9 @@ import styled, { keyframes, useTheme } from 'styled-components/macro'
import { ThemedText } from 'theme' import { ThemedText } from 'theme'
import { AdvancedSwapDetails } from './AdvancedSwapDetails' import { AdvancedSwapDetails } from './AdvancedSwapDetails'
import GasEstimateBadge from './GasEstimateBadge' import GasEstimateTooltip from './GasEstimateTooltip'
import SwapRoute from './SwapRoute'
import TradePrice from './TradePrice' import TradePrice from './TradePrice'
const Wrapper = styled(Row)`
width: 100%;
justify-content: center;
border-radius: inherit;
padding: 8px 12px;
margin-top: 0;
min-height: 32px;
`
const StyledCard = styled(OutlineCard)`
padding: 12px;
border: 1px solid ${({ theme }) => theme.backgroundOutline};
`
const StyledHeaderRow = styled(RowBetween)<{ disabled: boolean; open: boolean }>` const StyledHeaderRow = styled(RowBetween)<{ disabled: boolean; open: boolean }>`
padding: 0; padding: 0;
align-items: center; align-items: center;
...@@ -97,6 +81,16 @@ const Spinner = styled.div` ...@@ -97,6 +81,16 @@ const Spinner = styled.div`
top: -3px; top: -3px;
` `
const SwapDetailsWrapper = styled.div`
padding-top: ${({ theme }) => theme.grids.md};
`
const Wrapper = styled(Column)`
border: 1px solid ${({ theme }) => theme.backgroundOutline};
border-radius: 16px;
padding: 12px 16px;
`
interface SwapDetailsInlineProps { interface SwapDetailsInlineProps {
trade: InterfaceTrade<Currency, Currency, TradeType> | undefined trade: InterfaceTrade<Currency, Currency, TradeType> | undefined
syncing: boolean syncing: boolean
...@@ -110,8 +104,7 @@ export default function SwapDetailsDropdown({ trade, syncing, loading, allowedSl ...@@ -110,8 +104,7 @@ export default function SwapDetailsDropdown({ trade, syncing, loading, allowedSl
const [showDetails, setShowDetails] = useState(false) const [showDetails, setShowDetails] = useState(false)
return ( return (
<Wrapper style={{ marginTop: '0' }}> <Wrapper>
<AutoColumn gap="sm" style={{ width: '100%', marginBottom: '-8px' }}>
<TraceEvent <TraceEvent
events={[BrowserEvent.onClick]} events={[BrowserEvent.onClick]}
name={SwapEventName.SWAP_DETAILS_EXPANDED} name={SwapEventName.SWAP_DETAILS_EXPANDED}
...@@ -124,7 +117,7 @@ export default function SwapDetailsDropdown({ trade, syncing, loading, allowedSl ...@@ -124,7 +117,7 @@ export default function SwapDetailsDropdown({ trade, syncing, loading, allowedSl
disabled={!trade} disabled={!trade}
open={showDetails} open={showDetails}
> >
<RowFixed style={{ position: 'relative' }} align="center"> <RowFixed>
{Boolean(loading || syncing) && ( {Boolean(loading || syncing) && (
<StyledPolling> <StyledPolling>
<StyledPollingDot> <StyledPollingDot>
...@@ -147,12 +140,7 @@ export default function SwapDetailsDropdown({ trade, syncing, loading, allowedSl ...@@ -147,12 +140,7 @@ export default function SwapDetailsDropdown({ trade, syncing, loading, allowedSl
showDetails || showDetails ||
!chainId || !chainId ||
!SUPPORTED_GAS_ESTIMATE_CHAIN_IDS.includes(chainId) ? null : ( !SUPPORTED_GAS_ESTIMATE_CHAIN_IDS.includes(chainId) ? null : (
<GasEstimateBadge <GasEstimateTooltip trade={trade} loading={syncing || loading} disabled={showDetails} />
trade={trade}
loading={syncing || loading}
showRoute={!showDetails}
disableHover={showDetails}
/>
)} )}
<RotatingArrow <RotatingArrow
stroke={trade ? theme.textTertiary : theme.deprecated_bg3} stroke={trade ? theme.textTertiary : theme.deprecated_bg3}
...@@ -161,17 +149,13 @@ export default function SwapDetailsDropdown({ trade, syncing, loading, allowedSl ...@@ -161,17 +149,13 @@ export default function SwapDetailsDropdown({ trade, syncing, loading, allowedSl
</RowFixed> </RowFixed>
</StyledHeaderRow> </StyledHeaderRow>
</TraceEvent> </TraceEvent>
{trade && (
<AnimatedDropdown open={showDetails}> <AnimatedDropdown open={showDetails}>
<AutoColumn gap="sm" style={{ padding: '0', paddingBottom: '8px' }}> <SwapDetailsWrapper data-testid="advanced-swap-details">
{trade ? (
<StyledCard data-testid="advanced-swap-details">
<AdvancedSwapDetails trade={trade} allowedSlippage={allowedSlippage} syncing={syncing} /> <AdvancedSwapDetails trade={trade} allowedSlippage={allowedSlippage} syncing={syncing} />
</StyledCard> </SwapDetailsWrapper>
) : null}
{trade ? <SwapRoute data-testid="swap-route-info" trade={trade} syncing={syncing} /> : null}
</AutoColumn>
</AnimatedDropdown> </AnimatedDropdown>
</AutoColumn> )}
</Wrapper> </Wrapper>
) )
} }
...@@ -16,12 +16,12 @@ import { Text } from 'rebass' ...@@ -16,12 +16,12 @@ import { Text } from 'rebass'
import { RouterPreference } from 'state/routing/slice' import { RouterPreference } from 'state/routing/slice'
import { InterfaceTrade } from 'state/routing/types' import { InterfaceTrade } from 'state/routing/types'
import { useRouterPreference, useUserSlippageTolerance } from 'state/user/hooks' import { useRouterPreference, useUserSlippageTolerance } from 'state/user/hooks'
import getRoutingDiagramEntries, { RoutingDiagramEntry } from 'utils/getRoutingDiagramEntries'
import { computeRealizedPriceImpact } from 'utils/prices' import { computeRealizedPriceImpact } from 'utils/prices'
import { ButtonError } from '../Button' import { ButtonError } from '../Button'
import { AutoRow } from '../Row' import { AutoRow } from '../Row'
import { SwapCallbackError } from './styleds' import { SwapCallbackError } from './styleds'
import { getTokenPath, RoutingDiagramEntry } from './SwapRoute'
interface AnalyticsEventProps { interface AnalyticsEventProps {
trade: InterfaceTrade<Currency, Currency, TradeType> trade: InterfaceTrade<Currency, Currency, TradeType>
...@@ -125,7 +125,7 @@ export default function SwapModalFooter({ ...@@ -125,7 +125,7 @@ export default function SwapModalFooter({
const transactionDeadlineSecondsSinceEpoch = useTransactionDeadline()?.toNumber() // in seconds since epoch const transactionDeadlineSecondsSinceEpoch = useTransactionDeadline()?.toNumber() // in seconds since epoch
const isAutoSlippage = useUserSlippageTolerance()[0] === 'auto' const isAutoSlippage = useUserSlippageTolerance()[0] === 'auto'
const [routerPreference] = useRouterPreference() const [routerPreference] = useRouterPreference()
const routes = getTokenPath(trade) const routes = getRoutingDiagramEntries(trade)
return ( return (
<> <>
......
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import { TraceEvent } from '@uniswap/analytics' import { Currency, TradeType } from '@uniswap/sdk-core'
import { BrowserEvent, InterfaceElementName, SwapEventName } from '@uniswap/analytics-events'
import { Protocol } from '@uniswap/router-sdk'
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
import { Pair } from '@uniswap/v2-sdk'
import { FeeAmount } from '@uniswap/v3-sdk'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
import AnimatedDropdown from 'components/AnimatedDropdown' import Column from 'components/Column'
import { AutoColumn } from 'components/Column'
import { LoadingRows } from 'components/Loader/styled' import { LoadingRows } from 'components/Loader/styled'
import RoutingDiagram from 'components/RoutingDiagram/RoutingDiagram' import RoutingDiagram from 'components/RoutingDiagram/RoutingDiagram'
import { AutoRow, RowBetween } from 'components/Row'
import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains' import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains'
import useAutoRouterSupported from 'hooks/useAutoRouterSupported' import useAutoRouterSupported from 'hooks/useAutoRouterSupported'
import { memo, useState } from 'react'
import { Plus } from 'react-feather'
import { InterfaceTrade } from 'state/routing/types' import { InterfaceTrade } from 'state/routing/types'
import styled from 'styled-components/macro'
import { Separator, ThemedText } from 'theme' import { Separator, ThemedText } from 'theme'
import { useDarkModeManager } from 'theme/components/ThemeToggle' import getRoutingDiagramEntries from 'utils/getRoutingDiagramEntries'
import { AutoRouterLabel, AutoRouterLogo } from './RouterLabel' import RouterLabel from './RouterLabel'
const Wrapper = styled(AutoColumn)<{ darkMode?: boolean; fixedOpen?: boolean }>` export default function SwapRoute({
padding: ${({ fixedOpen }) => (fixedOpen ? '12px' : '12px 8px 12px 12px')}; trade,
border-radius: 16px; syncing,
border: 1px solid ${({ theme, fixedOpen }) => (fixedOpen ? 'transparent' : theme.backgroundOutline)}; }: {
cursor: pointer;
`
const OpenCloseIcon = styled(Plus)<{ open?: boolean }>`
margin-left: 8px;
height: 20px;
stroke-width: 2px;
transition: transform 0.1s;
transform: ${({ open }) => (open ? 'rotate(45deg)' : 'none')};
stroke: ${({ theme }) => theme.textTertiary};
cursor: pointer;
:hover {
opacity: 0.8;
}
`
interface SwapRouteProps extends React.HTMLAttributes<HTMLDivElement> {
trade: InterfaceTrade<Currency, Currency, TradeType> trade: InterfaceTrade<Currency, Currency, TradeType>
syncing: boolean syncing: boolean
fixedOpen?: boolean // fixed in open state, hide open/close icon }) {
}
export default memo(function SwapRoute({ trade, syncing, fixedOpen = false, ...rest }: SwapRouteProps) {
const autoRouterSupported = useAutoRouterSupported()
const routes = getTokenPath(trade)
const [open, setOpen] = useState(false)
const { chainId } = useWeb3React() const { chainId } = useWeb3React()
const autoRouterSupported = useAutoRouterSupported()
const [darkMode] = useDarkModeManager() const routes = getRoutingDiagramEntries(trade)
const formattedGasPriceString = trade?.gasUseEstimateUSD const gasPrice =
// TODO(WEB-3303)
// Can `trade.gasUseEstimateUSD` be defined when `chainId` is not in `SUPPORTED_GAS_ESTIMATE_CHAIN_IDS`?
trade.gasUseEstimateUSD && chainId && SUPPORTED_GAS_ESTIMATE_CHAIN_IDS.includes(chainId)
? trade.gasUseEstimateUSD.toFixed(2) === '0.00' ? trade.gasUseEstimateUSD.toFixed(2) === '0.00'
? '<$0.01' ? '<$0.01'
: '$' + trade.gasUseEstimateUSD.toFixed(2) : '$' + trade.gasUseEstimateUSD.toFixed(2)
: undefined : undefined
return ( return (
<Wrapper {...rest} darkMode={darkMode} fixedOpen={fixedOpen}> <Column gap="md">
<TraceEvent <RouterLabel />
events={[BrowserEvent.onClick]} <Separator />
name={SwapEventName.SWAP_AUTOROUTER_VISUALIZATION_EXPANDED}
element={InterfaceElementName.AUTOROUTER_VISUALIZATION_ROW}
shouldLogImpression={!open}
>
<RowBetween onClick={() => setOpen(!open)}>
<AutoRow gap="4px" width="auto">
<AutoRouterLogo />
<AutoRouterLabel />
</AutoRow>
{fixedOpen ? null : <OpenCloseIcon open={open} />}
</RowBetween>
</TraceEvent>
<AnimatedDropdown open={open || fixedOpen}>
<AutoRow gap="4px" width="auto" style={{ paddingTop: '12px', margin: 0 }}>
{syncing ? ( {syncing ? (
<LoadingRows> <LoadingRows>
<div style={{ width: '400px', height: '30px' }} /> <div style={{ width: '100%', height: '30px' }} />
</LoadingRows> </LoadingRows>
) : ( ) : (
<RoutingDiagram <RoutingDiagram
...@@ -91,67 +48,24 @@ export default memo(function SwapRoute({ trade, syncing, fixedOpen = false, ...r ...@@ -91,67 +48,24 @@ export default memo(function SwapRoute({ trade, syncing, fixedOpen = false, ...r
routes={routes} routes={routes}
/> />
)} )}
{autoRouterSupported && ( {autoRouterSupported && (
<> <>
<Separator /> <Separator />
{syncing ? ( {syncing ? (
<LoadingRows> <LoadingRows>
<div style={{ width: '250px', height: '15px' }} /> <div style={{ width: '100%', height: '15px' }} />
</LoadingRows> </LoadingRows>
) : ( ) : (
<ThemedText.DeprecatedMain fontSize={12} width={400} margin={0}> <ThemedText.Caption color="textSecondary">
{trade?.gasUseEstimateUSD && chainId && SUPPORTED_GAS_ESTIMATE_CHAIN_IDS.includes(chainId) ? ( {gasPrice ? <Trans>Best price route costs ~{gasPrice} in gas.</Trans> : null}{' '}
<Trans>Best price route costs ~{formattedGasPriceString} in gas. </Trans>
) : null}{' '}
<Trans> <Trans>
This route optimizes your total output by considering split routes, multiple hops, and the gas cost This route optimizes your total output by considering split routes, multiple hops, and the gas cost of
of each step. each step.
</Trans> </Trans>
</ThemedText.DeprecatedMain> </ThemedText.Caption>
)} )}
</> </>
)} )}
</AutoRow> </Column>
</AnimatedDropdown>
</Wrapper>
) )
})
export interface RoutingDiagramEntry {
percent: Percent
path: [Currency, Currency, FeeAmount][]
protocol: Protocol
}
const V2_DEFAULT_FEE_TIER = 3000
/**
* Loops through all routes on a trade and returns an array of diagram entries.
*/
export function getTokenPath(trade: InterfaceTrade<Currency, Currency, TradeType>): RoutingDiagramEntry[] {
return trade.swaps.map(({ route: { path: tokenPath, pools, protocol }, inputAmount, outputAmount }) => {
const portion =
trade.tradeType === TradeType.EXACT_INPUT
? inputAmount.divide(trade.inputAmount)
: outputAmount.divide(trade.outputAmount)
const percent = new Percent(portion.numerator, portion.denominator)
const path: RoutingDiagramEntry['path'] = []
for (let i = 0; i < pools.length; i++) {
const nextPool = pools[i]
const tokenIn = tokenPath[i]
const tokenOut = tokenPath[i + 1]
const entry: RoutingDiagramEntry['path'][0] = [
tokenIn,
tokenOut,
nextPool instanceof Pair ? V2_DEFAULT_FEE_TIER : nextPool.fee,
]
path.push(entry)
}
return {
percent,
path,
protocol,
}
})
} }
...@@ -25,7 +25,6 @@ const StyledPriceContainer = styled.button` ...@@ -25,7 +25,6 @@ const StyledPriceContainer = styled.button`
flex-direction: row; flex-direction: row;
text-align: left; text-align: left;
flex-wrap: wrap; flex-wrap: wrap;
padding: 8px 0;
user-select: text; user-select: text;
` `
...@@ -60,9 +59,9 @@ export default function TradePrice({ price }: TradePriceProps) { ...@@ -60,9 +59,9 @@ export default function TradePrice({ price }: TradePriceProps) {
> >
<ThemedText.BodySmall>{text}</ThemedText.BodySmall>{' '} <ThemedText.BodySmall>{text}</ThemedText.BodySmall>{' '}
{usdPrice && ( {usdPrice && (
<ThemedText.DeprecatedDarkGray> <ThemedText.BodySmall color="textSecondary">
<Trans>({formatNumber(usdPrice, NumberType.FiatTokenPrice)})</Trans> <Trans>({formatNumber(usdPrice, NumberType.FiatTokenPrice)})</Trans>
</ThemedText.DeprecatedDarkGray> </ThemedText.BodySmall>
)} )}
</StyledPriceContainer> </StyledPriceContainer>
) )
......
...@@ -2,13 +2,13 @@ ...@@ -2,13 +2,13 @@
exports[`AdvancedSwapDetails.tsx matches base snapshot 1`] = ` exports[`AdvancedSwapDetails.tsx matches base snapshot 1`] = `
<DocumentFragment> <DocumentFragment>
.c0 { .c2 {
box-sizing: border-box; box-sizing: border-box;
margin: 0; margin: 0;
min-width: 0; min-width: 0;
} }
.c4 { .c3 {
width: 100%; width: 100%;
display: -webkit-box; display: -webkit-box;
display: -webkit-flex; display: -webkit-flex;
...@@ -25,140 +25,129 @@ exports[`AdvancedSwapDetails.tsx matches base snapshot 1`] = ` ...@@ -25,140 +25,129 @@ exports[`AdvancedSwapDetails.tsx matches base snapshot 1`] = `
justify-content: flex-start; justify-content: flex-start;
} }
.c5 { .c4 {
-webkit-box-pack: justify; -webkit-box-pack: justify;
-webkit-justify-content: space-between; -webkit-justify-content: space-between;
-ms-flex-pack: justify; -ms-flex-pack: justify;
justify-content: space-between; justify-content: space-between;
} }
.c6 { .c5 {
width: -webkit-fit-content; width: -webkit-fit-content;
width: -moz-fit-content; width: -moz-fit-content;
width: fit-content; width: fit-content;
} }
.c7 {
color: #7780A0;
}
.c8 { .c8 {
color: #0D111C; color: #0D111C;
} }
.c10 { .c1 {
width: 100%; width: 100%;
height: 1px; height: 1px;
background-color: #D2D9EE; background-color: #D2D9EE;
} }
.c1 { .c0 {
width: 100%; display: -webkit-box;
padding: 1rem; display: -webkit-flex;
border-radius: 16px; display: -ms-flexbox;
} display: flex;
-webkit-flex-direction: column;
.c3 { -ms-flex-direction: column;
display: grid; flex-direction: column;
grid-auto-rows: auto; -webkit-box-pack: start;
grid-row-gap: 8px; -webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
gap: 12px;
} }
.c7 { .c6 {
display: inline-block; display: inline-block;
height: inherit; height: inherit;
} }
.c9 {
color: #7780A0;
}
.c2 {
padding: 0;
}
<div <div
class="c0 c1 c2" class="c0"
> >
<div <div
class="c3" class="c1"
> />
<div <div
class="c0 c4 c5" class="c2 c3 c4"
> >
<div <div
class="c0 c4 c6" class="c2 c3 c5"
> >
<div <div
class="c7" class="c6"
> >
<div> <div>
<div <div
class="css-zhpkf8" class="c7 css-zhpkf8"
> >
Expected Output Minimum output
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div <div
class="c8 css-q4yjm0" class="c8 css-zhpkf8"
> >
0.000000000000001 DEF 0.00000000000000098 DEF
</div> </div>
</div> </div>
<div <div
class="c0 c4 c5" class="c2 c3 c4"
> >
<div <div
class="c0 c4 c6" class="c2 c3 c5"
> >
<div <div
class="c7" class="c6"
> >
<div> <div>
<div <div
class="css-zhpkf8" class="c7 css-zhpkf8"
> >
Price Impact Expected output
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div <div
class="c8 css-q4yjm0" class="c8 css-zhpkf8"
> >
<div 0.000000000000001 DEF
class="c9 css-1aekuku"
>
105567.37%
</div>
</div> </div>
</div> </div>
<div <div
class="c10" class="c1"
/> />
<div <div
class="c0 c4 c5" class="c2 c3 c4"
> >
<div <div
class="c0 c4 c6" class="c7 css-zhpkf8"
style="margin-right: 20px;"
> >
Order routing
</div>
<div <div
class="c7" class="c6"
> >
<div> <div>
<div <div
class="css-zhpkf8" class="c8 css-zhpkf8"
> >
Minimum received after slippage (2.00%) Uniswap API
</div>
</div> </div>
</div> </div>
</div> </div>
<div
class="css-q4yjm0"
>
0.00000000000000098 DEF
</div>
</div>
</div> </div>
</div> </div>
</DocumentFragment> </DocumentFragment>
......
...@@ -2,62 +2,13 @@ ...@@ -2,62 +2,13 @@
exports[`SwapDetailsDropdown.tsx renders a trade 1`] = ` exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
<DocumentFragment> <DocumentFragment>
.c0 { .c2 {
box-sizing: border-box; box-sizing: border-box;
margin: 0; margin: 0;
min-width: 0; min-width: 0;
} }
.c20 { .c3 {
box-sizing: border-box;
margin: 0;
min-width: 0;
width: auto;
}
.c37 {
box-sizing: border-box;
margin: 0;
min-width: 0;
width: 100%;
}
.c1 {
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;
}
.c21 {
width: auto;
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;
gap: 4px;
}
.c38 {
width: 100%; width: 100%;
display: -webkit-box; display: -webkit-box;
display: -webkit-flex; display: -webkit-flex;
...@@ -72,7 +23,6 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = ` ...@@ -72,7 +23,6 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
-webkit-justify-content: flex-start; -webkit-justify-content: flex-start;
-ms-flex-pack: start; -ms-flex-pack: start;
justify-content: flex-start; justify-content: flex-start;
gap: 1px;
} }
.c4 { .c4 {
...@@ -82,28 +32,6 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = ` ...@@ -82,28 +32,6 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
justify-content: space-between; justify-content: space-between;
} }
.c22 {
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
margin: -4px;
}
.c22 > * {
margin: 4px !important;
}
.c39 {
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
margin: -1px;
}
.c39 > * {
margin: 1px !important;
}
.c6 { .c6 {
width: -webkit-fit-content; width: -webkit-fit-content;
width: -moz-fit-content; width: -moz-fit-content;
...@@ -114,238 +42,56 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = ` ...@@ -114,238 +42,56 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
color: #0D111C; color: #0D111C;
} }
.c44 { .c15 {
color: #7780A0; color: #7780A0;
} }
.c17 { .c13 {
width: 100%; width: 100%;
height: 1px; height: 1px;
background-color: #D2D9EE; background-color: #D2D9EE;
} }
.c11 { .c0 {
width: 100%;
padding: 1rem;
border-radius: 16px;
}
.c12 {
border: 1px solid #B8C0DC;
}
.c3 {
display: grid;
grid-auto-rows: auto;
grid-row-gap: 8px;
}
.c18 {
display: grid;
grid-auto-rows: auto;
}
.c7 {
-webkit-filter: none;
filter: none;
opacity: 1;
-webkit-transition: opacity 0.2s ease-in-out;
transition: opacity 0.2s ease-in-out;
}
.c15 {
display: inline-block;
height: inherit;
}
.c16 {
color: #7780A0;
}
.c14 {
padding: 0;
}
.c33 {
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
background: #E8ECFB;
border: unset;
border-radius: 0.5rem;
color: #000;
display: -webkit-inline-box;
display: -webkit-inline-flex;
display: -ms-inline-flexbox;
display: inline-flex;
padding: 4px 6px;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
font-weight: 500;
}
.c29 {
width: 20px;
height: 20px;
border-radius: 50%;
background: radial-gradient(white 60%,#ffffff00 calc(70% + 1px));
box-shadow: 0 0 1px white;
}
.c28 {
position: relative;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}
.c41 {
position: relative;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
}
.c42 {
z-index: 1;
}
.c43 {
position: absolute;
left: -10px !important;
}
.c26 {
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
width: 100%;
}
.c27 {
display: grid;
grid-template-columns: 24px 1fr 24px;
}
.c30 {
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
display: -webkit-box; display: -webkit-box;
display: -webkit-flex; display: -webkit-flex;
display: -ms-flexbox; display: -ms-flexbox;
display: flex; display: flex;
-webkit-box-pack: center; -webkit-flex-direction: column;
-webkit-justify-content: center; -ms-flex-direction: column;
-ms-flex-pack: center; flex-direction: column;
justify-content: center; -webkit-box-pack: start;
padding: 0.1rem 0.5rem; -webkit-justify-content: flex-start;
position: relative; -ms-flex-pack: start;
} justify-content: flex-start;
.c40 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 4px 4px;
} }
.c31 { .c12 {
display: -webkit-box; display: -webkit-box;
display: -webkit-flex; display: -webkit-flex;
display: -ms-flexbox; display: -ms-flexbox;
display: flex; display: flex;
-webkit-align-items: center; -webkit-flex-direction: column;
-webkit-box-align: center; -ms-flex-direction: column;
-ms-flex-align: center; flex-direction: column;
align-items: center;
position: absolute;
width: calc(100%);
z-index: 1;
opacity: 0.5;
}
.c32 path {
stroke: #98A1C0;
}
.c34 {
background-color: #E8ECFB;
border-radius: 8px;
display: grid;
font-size: 12px;
grid-gap: 4px;
grid-auto-flow: column;
-webkit-box-pack: start; -webkit-box-pack: start;
-webkit-justify-content: start; -webkit-justify-content: flex-start;
-ms-flex-pack: start; -ms-flex-pack: start;
justify-content: start; justify-content: flex-start;
padding: 4px 6px 4px 4px; gap: 12px;
z-index: 1020;
}
.c35 {
background-color: #B8C0DC;
border-radius: 4px;
color: #7780A0;
font-size: 10px;
padding: 2px 4px;
z-index: 1021;
}
.c36 {
word-break: normal;
}
.c23 {
height: 16px;
width: 16px;
}
.c23:hover {
-webkit-filter: brightness(1.3);
filter: brightness(1.3);
}
.c24 {
line-height: 1rem;
color: #40B66B;
}
.c19 {
padding: 12px 8px 12px 12px;
border-radius: 16px;
border: 1px solid #D2D9EE;
cursor: pointer;
} }
.c25 { .c7 {
margin-left: 8px; -webkit-filter: none;
height: 20px; filter: none;
stroke-width: 2px; opacity: 1;
-webkit-transition: -webkit-transform 0.1s; -webkit-transition: opacity 0.2s ease-in-out;
-webkit-transition: transform 0.1s; transition: opacity 0.2s ease-in-out;
transition: transform 0.1s;
-webkit-transform: none;
-ms-transform: none;
transform: none;
stroke: #98A1C0;
cursor: pointer;
} }
.c25:hover { .c14 {
opacity: 0.8; display: inline-block;
height: inherit;
} }
.c8 { .c8 {
...@@ -374,30 +120,12 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = ` ...@@ -374,30 +120,12 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
-webkit-flex-wrap: wrap; -webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap; -ms-flex-wrap: wrap;
flex-wrap: wrap; flex-wrap: wrap;
padding: 8px 0;
-webkit-user-select: text; -webkit-user-select: text;
-moz-user-select: text; -moz-user-select: text;
-ms-user-select: text; -ms-user-select: text;
user-select: text; user-select: text;
} }
.c2 {
width: 100%;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
border-radius: inherit;
padding: 8px 12px;
margin-top: 0;
min-height: 32px;
}
.c13 {
padding: 12px;
border: 1px solid #D2D9EE;
}
.c5 { .c5 {
padding: 0; padding: 0;
-webkit-align-items: center; -webkit-align-items: center;
...@@ -416,29 +144,25 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = ` ...@@ -416,29 +144,25 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
transition: transform 0.1s linear; transition: transform 0.1s linear;
} }
@supports (-webkit-background-clip:text) and (-webkit-text-fill-color:transparent) { .c11 {
.c24 { padding-top: 12px;
background-image: linear-gradient(90deg,#2172e5 0%,#54e521 163.16%); }
-webkit-background-clip: text;
-webkit-text-fill-color: transparent; .c1 {
} border: 1px solid #D2D9EE;
border-radius: 16px;
padding: 12px 16px;
} }
<div <div
class="c0 c1 c2" class="c0 c1"
style="margin-top: 0px;"
> >
<div <div
class="c3" class="c2 c3 c4 c5"
style="width: 100%; margin-bottom: -8px;"
>
<div
class="c0 c1 c4 c5"
data-testid="swap-details-header-row" data-testid="swap-details-header-row"
> >
<div <div
class="c0 c1 c6" class="c2 c3 c6"
style="position: relative;"
> >
<div <div
class="c7" class="c7"
...@@ -458,7 +182,7 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = ` ...@@ -458,7 +182,7 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
</div> </div>
</div> </div>
<div <div
class="c0 c1 c6" class="c2 c3 c6"
> >
<svg <svg
class="c10" class="c10"
...@@ -483,317 +207,82 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = ` ...@@ -483,317 +207,82 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
> >
<div> <div>
<div <div
class="c3" class="c11"
style="padding: 0px 0px 8px 0px;"
>
<div
class="c0 c11 c12 c13"
data-testid="advanced-swap-details" data-testid="advanced-swap-details"
> >
<div <div
class="c0 c11 c14" class="c12"
>
<div
class="c3"
>
<div
class="c0 c1 c4"
>
<div
class="c0 c1 c6"
>
<div
class="c15"
>
<div>
<div
class="css-zhpkf8"
>
Expected Output
</div>
</div>
</div>
</div>
<div
class="c9 css-q4yjm0"
>
0.000000000000001 DEF
</div>
</div>
<div
class="c0 c1 c4"
>
<div
class="c0 c1 c6"
>
<div
class="c15"
>
<div>
<div
class="css-zhpkf8"
>
Price Impact
</div>
</div>
</div>
</div>
<div
class="c9 css-q4yjm0"
>
<div
class="c16 css-1aekuku"
> >
105567.37%
</div>
</div>
</div>
<div <div
class="c17" class="c13"
/> />
<div <div
class="c0 c1 c4" class="c2 c3 c4"
> >
<div <div
class="c0 c1 c6" class="c2 c3 c6"
style="margin-right: 20px;"
> >
<div <div
class="c15" class="c14"
> >
<div> <div>
<div <div
class="css-zhpkf8" class="c15 css-zhpkf8"
> >
Minimum received after slippage (2.00%) Minimum output
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div <div
class="css-q4yjm0" class="c9 css-zhpkf8"
> >
0.00000000000000098 DEF 0.00000000000000098 DEF
</div> </div>
</div> </div>
</div>
</div>
</div>
<div
class="c18 c19"
data-testid="swap-route-info"
>
<div
class="c0 c1 c4"
>
<div <div
class="c20 c21 c22" class="c2 c3 c4"
width="auto"
>
<svg
class="c23"
fill="none"
height="20"
viewBox="0 0 23 20"
width="23"
xmlns="http://www.w3.org/2000/svg"
>
<defs>
<lineargradient
gradientTransform="rotate(95)"
id="AutoRouterIconGradient0"
x1="0"
x2="1"
y1="0"
y2="0"
> >
<stop
id="stop1"
offset="0"
stop-color="#2274E2"
/>
<stop
id="stop1"
offset="0.5"
stop-color="#2274E2"
/>
<stop
id="stop2"
offset="1"
stop-color="#3FB672"
/>
</lineargradient>
</defs>
<path
d="M16 16C10 16 9 10 5 10M16 16C16 17.6569 17.3431 19 19 19C20.6569 19 22 17.6569 22 16C22 14.3431 20.6569 13 19 13C17.3431 13 16 14.3431 16 16ZM5 10C9 10 10 4 16 4M5 10H1.5M16 4C16 5.65685 17.3431 7 19 7C20.6569 7 22 5.65685 22 4C22 2.34315 20.6569 1 19 1C17.3431 1 16 2.34315 16 4Z"
stroke="url(#AutoRouterIconGradient0)"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
/>
</svg>
<div <div
class="c9 c24 css-1aekuku" class="c2 c3 c6"
> >
Auto Router
</div>
</div>
<svg
class="c25"
fill="none"
height="24"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<line
x1="12"
x2="12"
y1="5"
y2="19"
/>
<line
x1="5"
x2="19"
y1="12"
y2="12"
/>
</svg>
</div>
<div <div
style="height: 0px; overflow: hidden; width: 100%; will-change: height;" class="c14"
> >
<div> <div>
<div <div
class="c20 c21 c22" class="c15 css-zhpkf8"
style="padding-top: 12px; margin: 0px;"
width="auto"
>
<div
class="c26 css-vurnku"
> >
<div Expected output
class="c0 c1 c27"
>
<div
class="c28"
>
<img
alt="ABC logo"
class="c29"
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x0000000000000000000000000000000000000001/logo.png"
/>
</div> </div>
<div
class="c0 c1 c30"
>
<div
class="c31"
>
<svg
class="c32"
>
dot_line.svg
</svg>
</div> </div>
<div
class="c33 c34"
>
<div
class="c33 c35"
>
<div
class="c36 css-15li2d9"
>
V3
</div> </div>
</div> </div>
<div <div
class="c36 css-1aekuku" class="c9 css-zhpkf8"
style="min-width: auto;"
> >
100% 0.000000000000001 DEF
</div> </div>
</div> </div>
<div <div
class="c37 c38 c39" class="c13"
style="justify-content: space-evenly; z-index: 2;"
width="100%"
>
<div
class="c15"
>
<div>
<div
class="c33 c40"
>
<div
class="css-mbnpt3"
>
<div
class="c41"
>
<div
class="c42"
>
<div
class="c28"
>
<img
alt="DEF logo"
class="c29"
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x0000000000000000000000000000000000000002/logo.png"
/> />
</div>
</div>
<div <div
class="c43" class="c2 c3 c4"
> >
<div <div
class="c28" class="c15 css-zhpkf8"
> >
<img Order routing
alt="ABC logo"
class="c29"
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x0000000000000000000000000000000000000001/logo.png"
/>
</div>
</div>
</div>
</div> </div>
<div <div
class="css-1aekuku" class="c14"
> >
1% <div>
</div>
</div>
</div>
</div>
</div>
</div>
<div
class="c28"
>
<img
alt="DEF logo"
class="c29"
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x0000000000000000000000000000000000000002/logo.png"
/>
</div>
</div>
</div>
<div
class="c17"
/>
<div <div
class="c44 css-65u4ng" class="c9 css-zhpkf8"
> >
This route optimizes your total output by considering split routes, multiple hops, and the gas cost of each step. Uniswap API
</div>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -81,7 +81,7 @@ exports[`SwapModalHeader.tsx matches base snapshot for test trade with exact inp ...@@ -81,7 +81,7 @@ exports[`SwapModalHeader.tsx matches base snapshot for test trade with exact inp
margin: -0px; margin: -0px;
} }
.c21 { .c22 {
width: -webkit-fit-content; width: -webkit-fit-content;
width: -moz-fit-content; width: -moz-fit-content;
width: fit-content; width: fit-content;
...@@ -91,11 +91,11 @@ exports[`SwapModalHeader.tsx matches base snapshot for test trade with exact inp ...@@ -91,11 +91,11 @@ exports[`SwapModalHeader.tsx matches base snapshot for test trade with exact inp
color: #0D111C; color: #0D111C;
} }
.c26 { .c24 {
color: #7780A0; color: #7780A0;
} }
.c24 { .c21 {
width: 100%; width: 100%;
height: 1px; height: 1px;
background-color: #D2D9EE; background-color: #D2D9EE;
...@@ -118,6 +118,21 @@ exports[`SwapModalHeader.tsx matches base snapshot for test trade with exact inp ...@@ -118,6 +118,21 @@ exports[`SwapModalHeader.tsx matches base snapshot for test trade with exact inp
background-color: #F5F6FC; background-color: #F5F6FC;
} }
.c20 {
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;
}
.c0 { .c0 {
display: grid; display: grid;
grid-auto-rows: auto; grid-auto-rows: auto;
...@@ -152,7 +167,7 @@ exports[`SwapModalHeader.tsx matches base snapshot for test trade with exact inp ...@@ -152,7 +167,7 @@ exports[`SwapModalHeader.tsx matches base snapshot for test trade with exact inp
background-size: 400%; background-size: 400%;
} }
.c22 { .c23 {
display: inline-block; display: inline-block;
height: inherit; height: inherit;
} }
...@@ -205,17 +220,12 @@ exports[`SwapModalHeader.tsx matches base snapshot for test trade with exact inp ...@@ -205,17 +220,12 @@ exports[`SwapModalHeader.tsx matches base snapshot for test trade with exact inp
-webkit-flex-wrap: wrap; -webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap; -ms-flex-wrap: wrap;
flex-wrap: wrap; flex-wrap: wrap;
padding: 8px 0;
-webkit-user-select: text; -webkit-user-select: text;
-moz-user-select: text; -moz-user-select: text;
-ms-user-select: text; -ms-user-select: text;
user-select: text; user-select: text;
} }
.c23 {
color: #7780A0;
}
.c10 { .c10 {
text-overflow: ellipsis; text-overflow: ellipsis;
max-width: 220px; max-width: 220px;
...@@ -223,10 +233,6 @@ exports[`SwapModalHeader.tsx matches base snapshot for test trade with exact inp ...@@ -223,10 +233,6 @@ exports[`SwapModalHeader.tsx matches base snapshot for test trade with exact inp
text-align: right; text-align: right;
} }
.c20 {
padding: 0;
}
.c15 { .c15 {
padding: 4px; padding: 4px;
border-radius: 12px; border-radius: 12px;
...@@ -415,92 +421,82 @@ exports[`SwapModalHeader.tsx matches base snapshot for test trade with exact inp ...@@ -415,92 +421,82 @@ exports[`SwapModalHeader.tsx matches base snapshot for test trade with exact inp
style="padding: .75rem; margin-top: 0.5rem;" style="padding: .75rem; margin-top: 0.5rem;"
> >
<div <div
class="c5 c19 c20" class="c20"
> >
<div <div
class="c4" class="c21"
> />
<div <div
class="c5 c6 c7" class="c5 c6 c7"
> >
<div <div
class="c5 c6 c21" class="c5 c6 c22"
> >
<div <div
class="c22" class="c23"
> >
<div> <div>
<div <div
class="css-zhpkf8" class="c24 css-zhpkf8"
> >
Expected Output Minimum output
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div <div
class="c18 css-q4yjm0" class="c18 css-zhpkf8"
> >
0.000000000000001 DEF 0.00000000000000098 DEF
</div> </div>
</div> </div>
<div <div
class="c5 c6 c7" class="c5 c6 c7"
> >
<div <div
class="c5 c6 c21" class="c5 c6 c22"
> >
<div <div
class="c22" class="c23"
> >
<div> <div>
<div <div
class="css-zhpkf8" class="c24 css-zhpkf8"
> >
Price Impact Expected output
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div <div
class="c18 css-q4yjm0" class="c18 css-zhpkf8"
>
<div
class="c23 css-1aekuku"
> >
105567.37% 0.000000000000001 DEF
</div>
</div> </div>
</div> </div>
<div <div
class="c24" class="c21"
/> />
<div <div
class="c5 c6 c7" class="c5 c6 c7"
> >
<div <div
class="c5 c6 c21" class="c24 css-zhpkf8"
style="margin-right: 20px;"
> >
Order routing
</div>
<div <div
class="c22" class="c23"
> >
<div> <div>
<div <div
class="css-zhpkf8" class="c18 css-zhpkf8"
> >
Minimum received after slippage (2.00%) Uniswap API
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div
class="css-q4yjm0"
>
0.00000000000000098 DEF
</div>
</div>
</div>
</div> </div>
</div> </div>
<div <div
...@@ -508,7 +504,7 @@ exports[`SwapModalHeader.tsx matches base snapshot for test trade with exact inp ...@@ -508,7 +504,7 @@ exports[`SwapModalHeader.tsx matches base snapshot for test trade with exact inp
style="padding: .75rem 1rem;" style="padding: .75rem 1rem;"
> >
<div <div
class="c26 css-k51stg" class="c24 css-k51stg"
style="width: 100%;" style="width: 100%;"
> >
Output is estimated. You will receive at least Output is estimated. You will receive at least
...@@ -524,7 +520,7 @@ exports[`SwapModalHeader.tsx matches base snapshot for test trade with exact inp ...@@ -524,7 +520,7 @@ exports[`SwapModalHeader.tsx matches base snapshot for test trade with exact inp
style="padding: 12px 0px 0px 0px;" style="padding: 12px 0px 0px 0px;"
> >
<div <div
class="c26 css-8mokm4" class="c24 css-8mokm4"
> >
Output will be sent to Output will be sent to
<b <b
......
import { TooltipContainer } from 'components/Tooltip'
import { SupportedChainId } from 'constants/chains' import { SupportedChainId } from 'constants/chains'
import { transparentize } from 'polished' import { transparentize } from 'polished'
import { ReactNode } from 'react' import { ReactNode } from 'react'
...@@ -64,17 +63,6 @@ export const ArrowWrapper = styled.div<{ clickable: boolean }>` ...@@ -64,17 +63,6 @@ export const ArrowWrapper = styled.div<{ clickable: boolean }>`
: null} : null}
` `
export const ErrorText = styled(Text)<{ severity?: 0 | 1 | 2 | 3 | 4 }>`
color: ${({ theme, severity }) =>
severity === 3 || severity === 4
? theme.accentFailure
: severity === 2
? theme.deprecated_yellow2
: severity === 1
? theme.textPrimary
: theme.textSecondary};
`
export const TruncatedText = styled(Text)` export const TruncatedText = styled(Text)`
text-overflow: ellipsis; text-overflow: ellipsis;
max-width: 220px; max-width: 220px;
...@@ -151,15 +139,3 @@ export const SwapShowAcceptChanges = styled(AutoColumn)` ...@@ -151,15 +139,3 @@ export const SwapShowAcceptChanges = styled(AutoColumn)`
border-radius: 12px; border-radius: 12px;
margin-top: 8px; margin-top: 8px;
` `
export const ResponsiveTooltipContainer = styled(TooltipContainer)<{ origin?: string; width?: string }>`
background-color: ${({ theme }) => theme.backgroundSurface};
border: 1px solid ${({ theme }) => theme.backgroundInteractive};
padding: 1rem;
width: ${({ width }) => width ?? 'auto'};
${({ theme, origin }) => theme.deprecated_mediaWidth.deprecated_upToExtraSmall`
transform: scale(0.8);
transform-origin: ${origin ?? 'top left'};
`}
`
...@@ -3,7 +3,7 @@ import { useWeb3React } from '@web3-react/core' ...@@ -3,7 +3,7 @@ import { useWeb3React } from '@web3-react/core'
import { SupportedChainId } from 'constants/chains' import { SupportedChainId } from 'constants/chains'
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount' import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
import { useMemo, useRef } from 'react' import { useMemo, useRef } from 'react'
import { RouterPreference } from 'state/routing/slice' import { INTERNAL_ROUTER_PREFERENCE_PRICE } from 'state/routing/slice'
import { useRoutingAPITrade } from 'state/routing/useRoutingAPITrade' import { useRoutingAPITrade } from 'state/routing/useRoutingAPITrade'
import { CUSD_CELO, DAI_OPTIMISM, USDC_ARBITRUM, USDC_MAINNET, USDC_POLYGON, USDT_BSC } from '../constants/tokens' import { CUSD_CELO, DAI_OPTIMISM, USDC_ARBITRUM, USDC_MAINNET, USDC_POLYGON, USDT_BSC } from '../constants/tokens'
...@@ -28,7 +28,7 @@ export default function useStablecoinPrice(currency?: Currency): Price<Currency, ...@@ -28,7 +28,7 @@ export default function useStablecoinPrice(currency?: Currency): Price<Currency,
const amountOut = chainId ? STABLECOIN_AMOUNT_OUT[chainId] : undefined const amountOut = chainId ? STABLECOIN_AMOUNT_OUT[chainId] : undefined
const stablecoin = amountOut?.currency const stablecoin = amountOut?.currency
const { trade } = useRoutingAPITrade(TradeType.EXACT_OUTPUT, amountOut, currency, RouterPreference.PRICE) const { trade } = useRoutingAPITrade(TradeType.EXACT_OUTPUT, amountOut, currency, INTERNAL_ROUTER_PREFERENCE_PRICE)
const price = useMemo(() => { const price = useMemo(() => {
if (!currency || !stablecoin) { if (!currency || !stablecoin) {
return undefined return undefined
......
...@@ -3,7 +3,7 @@ import { Currency, CurrencyAmount, Price, SupportedChainId, TradeType } from '@u ...@@ -3,7 +3,7 @@ import { Currency, CurrencyAmount, Price, SupportedChainId, TradeType } from '@u
import { nativeOnChain } from 'constants/tokens' import { nativeOnChain } from 'constants/tokens'
import { Chain, useTokenSpotPriceQuery } from 'graphql/data/__generated__/types-and-hooks' import { Chain, useTokenSpotPriceQuery } from 'graphql/data/__generated__/types-and-hooks'
import { chainIdToBackendName, isGqlSupportedChain, PollingInterval } from 'graphql/data/util' import { chainIdToBackendName, isGqlSupportedChain, PollingInterval } from 'graphql/data/util'
import { RouterPreference } from 'state/routing/slice' import { INTERNAL_ROUTER_PREFERENCE_PRICE } from 'state/routing/slice'
import { TradeState } from 'state/routing/types' import { TradeState } from 'state/routing/types'
import { useRoutingAPITrade } from 'state/routing/useRoutingAPITrade' import { useRoutingAPITrade } from 'state/routing/useRoutingAPITrade'
import { getNativeTokenDBAddress } from 'utils/nativeTokens' import { getNativeTokenDBAddress } from 'utils/nativeTokens'
...@@ -30,7 +30,7 @@ function useETHValue(currencyAmount?: CurrencyAmount<Currency>): { ...@@ -30,7 +30,7 @@ function useETHValue(currencyAmount?: CurrencyAmount<Currency>): {
TradeType.EXACT_OUTPUT, TradeType.EXACT_OUTPUT,
amountOut, amountOut,
currencyAmount?.currency, currencyAmount?.currency,
RouterPreference.PRICE INTERNAL_ROUTER_PREFERENCE_PRICE
) )
// Get ETH value of ETH or WETH // Get ETH value of ETH or WETH
......
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core' import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
import { useMemo } from 'react' import { useMemo } from 'react'
import { RouterPreference } from 'state/routing/slice' import { INTERNAL_ROUTER_PREFERENCE_PRICE, RouterPreference } from 'state/routing/slice'
/** /**
* Returns query arguments for the Routing API query or undefined if the * Returns query arguments for the Routing API query or undefined if the
...@@ -18,7 +18,7 @@ export function useRoutingAPIArguments({ ...@@ -18,7 +18,7 @@ export function useRoutingAPIArguments({
tokenOut: Currency | undefined tokenOut: Currency | undefined
amount: CurrencyAmount<Currency> | undefined amount: CurrencyAmount<Currency> | undefined
tradeType: TradeType tradeType: TradeType
routerPreference: RouterPreference routerPreference: RouterPreference | typeof INTERNAL_ROUTER_PREFERENCE_PRICE
}) { }) {
return useMemo( return useMemo(
() => () =>
......
...@@ -110,16 +110,8 @@ const SwapSection = styled.div` ...@@ -110,16 +110,8 @@ const SwapSection = styled.div`
} }
` `
const OutputSwapSection = styled(SwapSection)<{ showDetailsDropdown: boolean }>` const OutputSwapSection = styled(SwapSection)`
border-bottom: ${({ theme }) => `1px solid ${theme.backgroundSurface}`}; border-bottom: ${({ theme }) => `1px solid ${theme.backgroundSurface}`};
border-bottom-left-radius: ${({ showDetailsDropdown }) => showDetailsDropdown && '0'};
border-bottom-right-radius: ${({ showDetailsDropdown }) => showDetailsDropdown && '0'};
`
const DetailsSwapSection = styled(SwapSection)`
padding: 0;
border-top-left-radius: 0;
border-top-right-radius: 0;
` `
function getIsValidSwapQuote( function getIsValidSwapQuote(
...@@ -641,9 +633,9 @@ export function Swap({ ...@@ -641,9 +633,9 @@ export function Swap({
</TraceEvent> </TraceEvent>
</ArrowWrapper> </ArrowWrapper>
</div> </div>
<AutoColumn gap="md"> <AutoColumn gap="xs">
<div> <div>
<OutputSwapSection showDetailsDropdown={showDetailsDropdown}> <OutputSwapSection>
<Trace section={InterfaceSectionName.CURRENCY_OUTPUT_PANEL}> <Trace section={InterfaceSectionName.CURRENCY_OUTPUT_PANEL}>
<SwapCurrencyInputPanel <SwapCurrencyInputPanel
value={formattedAmounts[Field.OUTPUT]} value={formattedAmounts[Field.OUTPUT]}
...@@ -676,17 +668,15 @@ export function Swap({ ...@@ -676,17 +668,15 @@ export function Swap({
</> </>
) : null} ) : null}
</OutputSwapSection> </OutputSwapSection>
</div>
{showDetailsDropdown && ( {showDetailsDropdown && (
<DetailsSwapSection>
<SwapDetailsDropdown <SwapDetailsDropdown
trade={trade} trade={trade}
syncing={routeIsSyncing} syncing={routeIsSyncing}
loading={routeIsLoading} loading={routeIsLoading}
allowedSlippage={allowedSlippage} allowedSlippage={allowedSlippage}
/> />
</DetailsSwapSection>
)} )}
</div>
{showPriceImpactWarning && <PriceImpactWarning priceImpact={largerPriceImpact} />} {showPriceImpactWarning && <PriceImpactWarning priceImpact={largerPriceImpact} />}
<div> <div>
{swapIsUnsupported ? ( {swapIsUnsupported ? (
......
...@@ -13,11 +13,12 @@ export enum RouterPreference { ...@@ -13,11 +13,12 @@ export enum RouterPreference {
AUTO = 'auto', AUTO = 'auto',
API = 'api', API = 'api',
CLIENT = 'client', CLIENT = 'client',
// Used internally for token -> USDC trades to get a USD value.
PRICE = 'price',
} }
// This is excluded from `RouterPreference` enum because it's only used
// internally for token -> USDC trades to get a USD value.
export const INTERNAL_ROUTER_PREFERENCE_PRICE = 'price' as const
const routers = new Map<ChainId, AlphaRouter>() const routers = new Map<ChainId, AlphaRouter>()
function getRouter(chainId: ChainId): AlphaRouter { function getRouter(chainId: ChainId): AlphaRouter {
const router = routers.get(chainId) const router = routers.get(chainId)
...@@ -78,7 +79,7 @@ interface GetQuoteArgs { ...@@ -78,7 +79,7 @@ interface GetQuoteArgs {
tokenOutDecimals: number tokenOutDecimals: number
tokenOutSymbol?: string tokenOutSymbol?: string
amount: string amount: string
routerPreference: RouterPreference routerPreference: RouterPreference | typeof INTERNAL_ROUTER_PREFERENCE_PRICE
type: 'exactIn' | 'exactOut' type: 'exactIn' | 'exactOut'
} }
...@@ -110,7 +111,7 @@ export const routingApi = createApi({ ...@@ -110,7 +111,7 @@ export const routingApi = createApi({
{ {
data: { data: {
...args, ...args,
isPrice: args.routerPreference === RouterPreference.PRICE, isPrice: args.routerPreference === INTERNAL_ROUTER_PREFERENCE_PRICE,
isAutoRouter: isAutoRouter:
args.routerPreference === RouterPreference.AUTO || args.routerPreference === RouterPreference.API, args.routerPreference === RouterPreference.AUTO || args.routerPreference === RouterPreference.API,
}, },
......
...@@ -7,7 +7,7 @@ import { useStablecoinAmountFromFiatValue } from 'hooks/useStablecoinPrice' ...@@ -7,7 +7,7 @@ import { useStablecoinAmountFromFiatValue } from 'hooks/useStablecoinPrice'
import { useRoutingAPIArguments } from 'lib/hooks/routing/useRoutingAPIArguments' import { useRoutingAPIArguments } from 'lib/hooks/routing/useRoutingAPIArguments'
import ms from 'ms.macro' import ms from 'ms.macro'
import { useMemo } from 'react' import { useMemo } from 'react'
import { RouterPreference, useGetQuoteQuery } from 'state/routing/slice' import { INTERNAL_ROUTER_PREFERENCE_PRICE, RouterPreference, useGetQuoteQuery } from 'state/routing/slice'
import { InterfaceTrade, TradeState } from './types' import { InterfaceTrade, TradeState } from './types'
import { computeRoutes, transformRoutesToTrade } from './utils' import { computeRoutes, transformRoutesToTrade } from './utils'
...@@ -22,7 +22,7 @@ export function useRoutingAPITrade<TTradeType extends TradeType>( ...@@ -22,7 +22,7 @@ export function useRoutingAPITrade<TTradeType extends TradeType>(
tradeType: TTradeType, tradeType: TTradeType,
amountSpecified: CurrencyAmount<Currency> | undefined, amountSpecified: CurrencyAmount<Currency> | undefined,
otherCurrency: Currency | undefined, otherCurrency: Currency | undefined,
routerPreference: RouterPreference routerPreference: RouterPreference | typeof INTERNAL_ROUTER_PREFERENCE_PRICE
): { ): {
state: TradeState state: TradeState
trade: InterfaceTrade<Currency, Currency, TTradeType> | undefined trade: InterfaceTrade<Currency, Currency, TTradeType> | undefined
...@@ -50,7 +50,7 @@ export function useRoutingAPITrade<TTradeType extends TradeType>( ...@@ -50,7 +50,7 @@ export function useRoutingAPITrade<TTradeType extends TradeType>(
currentData, currentData,
} = useGetQuoteQuery(queryArgs ?? skipToken, { } = useGetQuoteQuery(queryArgs ?? skipToken, {
// Price-fetching is informational and costly, so it's done less frequently. // Price-fetching is informational and costly, so it's done less frequently.
pollingInterval: routerPreference === RouterPreference.PRICE ? ms`1m` : AVERAGE_L1_BLOCK_TIME, pollingInterval: routerPreference === INTERNAL_ROUTER_PREFERENCE_PRICE ? ms`1m` : AVERAGE_L1_BLOCK_TIME,
// If latest quote from cache was fetched > 2m ago, instantly repoll for another instead of waiting for next poll period // If latest quote from cache was fetched > 2m ago, instantly repoll for another instead of waiting for next poll period
refetchOnMountOrArgChange: 2 * 60, refetchOnMountOrArgChange: 2 * 60,
}) })
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`getRoutingDiagramEntries returns entries for a trade 1`] = `
Array [
Object {
"path": Array [
Array [
Token {
"address": "0x0000000000000000000000000000000000000001",
"chainId": 1,
"decimals": 18,
"isNative": false,
"isToken": true,
"name": "Abc",
"symbol": "ABC",
},
Token {
"address": "0x0000000000000000000000000000000000000002",
"chainId": 1,
"decimals": 18,
"isNative": false,
"isToken": true,
"name": "Def",
"symbol": "DEF",
},
10000,
],
],
"percent": Percent {
"denominator": JSBI [
1000,
],
"isPercent": true,
"numerator": JSBI [
1000,
],
},
"protocol": "V3",
},
]
`;
import { TEST_TRADE_EXACT_INPUT } from 'test-utils/constants'
import getRoutingDiagramEntries from './getRoutingDiagramEntries'
describe('getRoutingDiagramEntries', () => {
it('returns entries for a trade', () => {
expect(getRoutingDiagramEntries(TEST_TRADE_EXACT_INPUT)).toMatchSnapshot()
})
})
import { Protocol } from '@uniswap/router-sdk'
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
import { Pair } from '@uniswap/v2-sdk'
import { FeeAmount } from '@uniswap/v3-sdk'
import { InterfaceTrade } from 'state/routing/types'
export interface RoutingDiagramEntry {
percent: Percent
path: [Currency, Currency, FeeAmount][]
protocol: Protocol
}
const V2_DEFAULT_FEE_TIER = 3000
/**
* Loops through all routes on a trade and returns an array of diagram entries.
*/
export default function getRoutingDiagramEntries(
trade: InterfaceTrade<Currency, Currency, TradeType>
): RoutingDiagramEntry[] {
return trade.swaps.map(({ route: { path: tokenPath, pools, protocol }, inputAmount, outputAmount }) => {
const portion =
trade.tradeType === TradeType.EXACT_INPUT
? inputAmount.divide(trade.inputAmount)
: outputAmount.divide(trade.outputAmount)
const percent = new Percent(portion.numerator, portion.denominator)
const path: RoutingDiagramEntry['path'] = []
for (let i = 0; i < pools.length; i++) {
const nextPool = pools[i]
const tokenIn = tokenPath[i]
const tokenOut = tokenPath[i + 1]
const entry: RoutingDiagramEntry['path'][0] = [
tokenIn,
tokenOut,
nextPool instanceof Pair ? V2_DEFAULT_FEE_TIER : nextPool.fee,
]
path.push(entry)
}
return {
percent,
path,
protocol,
}
})
}
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