chore: Refactor swap request flow (#6499)
* Refactor swap quote flow with widget logic * remove console logging * add ignore path for serialization check and pass in native currencies for client side routing * apply stashed changes * revert node version change * remove TODO comment because maybe no longer relevant * update unit tests * wip: add snapshot test * add snapshot test for gas estimate badge * address PR comments: rename variables, fix client side router initialization * update Trade type * add TODO comment about isExactInput util * change | undefined convention to ? * PR comments * update type * remove client side initialization logic and replace with TODO * use routing-api for price fetching trades too * remove QuoteType.Initialized
This commit is contained in:
parent
fd1aded517
commit
8431ad9161
@ -1,11 +1,5 @@
|
|||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
import {
|
import { TEST_ALLOWED_SLIPPAGE, TEST_TRADE_EXACT_INPUT, TEST_TRADE_EXACT_OUTPUT } from 'test-utils/constants'
|
||||||
TEST_ALLOWED_SLIPPAGE,
|
|
||||||
TEST_TOKEN_1,
|
|
||||||
TEST_TRADE_EXACT_INPUT,
|
|
||||||
TEST_TRADE_EXACT_OUTPUT,
|
|
||||||
toCurrencyAmount,
|
|
||||||
} from 'test-utils/constants'
|
|
||||||
import { act, render, screen } from 'test-utils/render'
|
import { act, render, screen } from 'test-utils/render'
|
||||||
|
|
||||||
import { AdvancedSwapDetails } from './AdvancedSwapDetails'
|
import { AdvancedSwapDetails } from './AdvancedSwapDetails'
|
||||||
@ -27,7 +21,7 @@ describe('AdvancedSwapDetails.tsx', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
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 = '1.00'
|
||||||
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 input/i)))
|
await act(() => userEvent.hover(screen.getByText(/Maximum input/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()
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Trans } from '@lingui/macro'
|
import { Trans } from '@lingui/macro'
|
||||||
import { sendAnalyticsEvent } from '@uniswap/analytics'
|
import { sendAnalyticsEvent } from '@uniswap/analytics'
|
||||||
import { InterfaceElementName, SwapEventName } from '@uniswap/analytics-events'
|
import { InterfaceElementName, SwapEventName } from '@uniswap/analytics-events'
|
||||||
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
|
import { Percent, TradeType } from '@uniswap/sdk-core'
|
||||||
import { useWeb3React } from '@web3-react/core'
|
import { useWeb3React } from '@web3-react/core'
|
||||||
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'
|
||||||
@ -16,7 +16,7 @@ import RouterLabel from './RouterLabel'
|
|||||||
import SwapRoute from './SwapRoute'
|
import SwapRoute from './SwapRoute'
|
||||||
|
|
||||||
interface AdvancedSwapDetailsProps {
|
interface AdvancedSwapDetailsProps {
|
||||||
trade: InterfaceTrade<Currency, Currency, TradeType>
|
trade: InterfaceTrade
|
||||||
allowedSlippage: Percent
|
allowedSlippage: Percent
|
||||||
syncing?: boolean
|
syncing?: boolean
|
||||||
}
|
}
|
||||||
@ -60,7 +60,7 @@ export function AdvancedSwapDetails({ trade, allowedSlippage, syncing = false }:
|
|||||||
</ThemedText.BodySmall>
|
</ThemedText.BodySmall>
|
||||||
</MouseoverTooltip>
|
</MouseoverTooltip>
|
||||||
<TextWithLoadingPlaceholder syncing={syncing} width={50}>
|
<TextWithLoadingPlaceholder syncing={syncing} width={50}>
|
||||||
<ThemedText.BodySmall>~${trade.gasUseEstimateUSD.toFixed(2)}</ThemedText.BodySmall>
|
<ThemedText.BodySmall>~${trade.gasUseEstimateUSD}</ThemedText.BodySmall>
|
||||||
</TextWithLoadingPlaceholder>
|
</TextWithLoadingPlaceholder>
|
||||||
</RowBetween>
|
</RowBetween>
|
||||||
)}
|
)}
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { Trans } from '@lingui/macro'
|
import { Trans } from '@lingui/macro'
|
||||||
import { Trace } from '@uniswap/analytics'
|
import { Trace } from '@uniswap/analytics'
|
||||||
import { InterfaceModalName } from '@uniswap/analytics-events'
|
import { InterfaceModalName } from '@uniswap/analytics-events'
|
||||||
import { Trade } from '@uniswap/router-sdk'
|
import { Percent } from '@uniswap/sdk-core'
|
||||||
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
|
|
||||||
import { ReactNode, useCallback, useMemo, useState } from 'react'
|
import { ReactNode, useCallback, useMemo, useState } from 'react'
|
||||||
import { InterfaceTrade } from 'state/routing/types'
|
import { InterfaceTrade } from 'state/routing/types'
|
||||||
import { tradeMeaningfullyDiffers } from 'utils/tradeMeaningFullyDiffer'
|
import { tradeMeaningfullyDiffers } from 'utils/tradeMeaningFullyDiffer'
|
||||||
@ -31,8 +30,8 @@ export default function ConfirmSwapModal({
|
|||||||
fiatValueOutput,
|
fiatValueOutput,
|
||||||
}: {
|
}: {
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
trade: InterfaceTrade<Currency, Currency, TradeType> | undefined
|
trade: InterfaceTrade | undefined
|
||||||
originalTrade: Trade<Currency, Currency, TradeType> | undefined
|
originalTrade: InterfaceTrade | undefined
|
||||||
attemptingTxn: boolean
|
attemptingTxn: boolean
|
||||||
txHash: string | undefined
|
txHash: string | undefined
|
||||||
recipient: string | null
|
recipient: string | null
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { sendAnalyticsEvent } from '@uniswap/analytics'
|
import { sendAnalyticsEvent } from '@uniswap/analytics'
|
||||||
import { InterfaceElementName, SwapEventName } from '@uniswap/analytics-events'
|
import { InterfaceElementName, SwapEventName } from '@uniswap/analytics-events'
|
||||||
import { Currency, TradeType } from '@uniswap/sdk-core'
|
|
||||||
import { LoadingOpacityContainer } from 'components/Loader/styled'
|
import { LoadingOpacityContainer } from 'components/Loader/styled'
|
||||||
import { RowFixed } from 'components/Row'
|
import { RowFixed } from 'components/Row'
|
||||||
import { MouseoverTooltip, TooltipSize } from 'components/Tooltip'
|
import { MouseoverTooltip, TooltipSize } from 'components/Tooltip'
|
||||||
@ -26,14 +25,14 @@ export default function GasEstimateTooltip({
|
|||||||
loading,
|
loading,
|
||||||
disabled,
|
disabled,
|
||||||
}: {
|
}: {
|
||||||
trade: InterfaceTrade<Currency, Currency, TradeType> // dollar amount in active chain's stablecoin
|
trade: InterfaceTrade // dollar amount in active chain's stablecoin
|
||||||
loading: boolean
|
loading: boolean
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
}) {
|
}) {
|
||||||
const formattedGasPriceString = trade?.gasUseEstimateUSD
|
const formattedGasPriceString = trade?.gasUseEstimateUSD
|
||||||
? trade.gasUseEstimateUSD.toFixed(2) === '0.00'
|
? trade.gasUseEstimateUSD === '0.00'
|
||||||
? '<$0.01'
|
? '<$0.01'
|
||||||
: '$' + trade.gasUseEstimateUSD.toFixed(2)
|
: '$' + trade.gasUseEstimateUSD
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
import { TEST_ALLOWED_SLIPPAGE, TEST_TOKEN_1, TEST_TRADE_EXACT_INPUT, toCurrencyAmount } from 'test-utils/constants'
|
import { TEST_ALLOWED_SLIPPAGE, TEST_TRADE_EXACT_INPUT } from 'test-utils/constants'
|
||||||
import { act, render, screen } from 'test-utils/render'
|
import { act, render, screen } from 'test-utils/render'
|
||||||
|
|
||||||
import SwapDetailsDropdown from './SwapDetailsDropdown'
|
import SwapDetailsDropdown from './SwapDetailsDropdown'
|
||||||
@ -25,7 +25,7 @@ describe('SwapDetailsDropdown.tsx', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('is interactive once loaded', async () => {
|
it('is interactive once loaded', async () => {
|
||||||
TEST_TRADE_EXACT_INPUT.gasUseEstimateUSD = toCurrencyAmount(TEST_TOKEN_1, 1)
|
TEST_TRADE_EXACT_INPUT.gasUseEstimateUSD = '1.00'
|
||||||
render(
|
render(
|
||||||
<SwapDetailsDropdown
|
<SwapDetailsDropdown
|
||||||
trade={TEST_TRADE_EXACT_INPUT}
|
trade={TEST_TRADE_EXACT_INPUT}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Trans } from '@lingui/macro'
|
import { Trans } from '@lingui/macro'
|
||||||
import { TraceEvent } from '@uniswap/analytics'
|
import { TraceEvent } from '@uniswap/analytics'
|
||||||
import { BrowserEvent, InterfaceElementName, SwapEventName } from '@uniswap/analytics-events'
|
import { BrowserEvent, InterfaceElementName, SwapEventName } from '@uniswap/analytics-events'
|
||||||
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
|
import { Percent } 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 Column from 'components/Column'
|
import Column from 'components/Column'
|
||||||
@ -92,7 +92,7 @@ const Wrapper = styled(Column)`
|
|||||||
`
|
`
|
||||||
|
|
||||||
interface SwapDetailsInlineProps {
|
interface SwapDetailsInlineProps {
|
||||||
trade: InterfaceTrade<Currency, Currency, TradeType> | undefined
|
trade: InterfaceTrade | undefined
|
||||||
syncing: boolean
|
syncing: boolean
|
||||||
loading: boolean
|
loading: boolean
|
||||||
allowedSlippage: Percent
|
allowedSlippage: Percent
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Trans } from '@lingui/macro'
|
import { Trans } from '@lingui/macro'
|
||||||
import { TraceEvent } from '@uniswap/analytics'
|
import { TraceEvent } from '@uniswap/analytics'
|
||||||
import { BrowserEvent, InterfaceElementName, SwapEventName } from '@uniswap/analytics-events'
|
import { BrowserEvent, InterfaceElementName, SwapEventName } from '@uniswap/analytics-events'
|
||||||
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
|
import { Percent } from '@uniswap/sdk-core'
|
||||||
import useTransactionDeadline from 'hooks/useTransactionDeadline'
|
import useTransactionDeadline from 'hooks/useTransactionDeadline'
|
||||||
import {
|
import {
|
||||||
formatPercentInBasisPointsNumber,
|
formatPercentInBasisPointsNumber,
|
||||||
@ -24,7 +24,7 @@ import { AutoRow } from '../Row'
|
|||||||
import { SwapCallbackError } from './styleds'
|
import { SwapCallbackError } from './styleds'
|
||||||
|
|
||||||
interface AnalyticsEventProps {
|
interface AnalyticsEventProps {
|
||||||
trade: InterfaceTrade<Currency, Currency, TradeType>
|
trade: InterfaceTrade
|
||||||
hash: string | undefined
|
hash: string | undefined
|
||||||
allowedSlippage: Percent
|
allowedSlippage: Percent
|
||||||
transactionDeadlineSecondsSinceEpoch: number | undefined
|
transactionDeadlineSecondsSinceEpoch: number | undefined
|
||||||
@ -75,7 +75,7 @@ const formatAnalyticsEventProperties = ({
|
|||||||
fiatValueInput,
|
fiatValueInput,
|
||||||
fiatValueOutput,
|
fiatValueOutput,
|
||||||
}: AnalyticsEventProps) => ({
|
}: AnalyticsEventProps) => ({
|
||||||
estimated_network_fee_usd: trade.gasUseEstimateUSD ? formatToDecimal(trade.gasUseEstimateUSD, 2) : undefined,
|
estimated_network_fee_usd: trade.gasUseEstimateUSD ?? undefined,
|
||||||
transaction_hash: hash,
|
transaction_hash: hash,
|
||||||
transaction_deadline_seconds: getDurationUntilTimestampSeconds(transactionDeadlineSecondsSinceEpoch),
|
transaction_deadline_seconds: getDurationUntilTimestampSeconds(transactionDeadlineSecondsSinceEpoch),
|
||||||
token_in_address: getTokenAddress(trade.inputAmount.currency),
|
token_in_address: getTokenAddress(trade.inputAmount.currency),
|
||||||
@ -112,7 +112,7 @@ export default function SwapModalFooter({
|
|||||||
fiatValueInput,
|
fiatValueInput,
|
||||||
fiatValueOutput,
|
fiatValueOutput,
|
||||||
}: {
|
}: {
|
||||||
trade: InterfaceTrade<Currency, Currency, TradeType>
|
trade: InterfaceTrade
|
||||||
hash: string | undefined
|
hash: string | undefined
|
||||||
allowedSlippage: Percent
|
allowedSlippage: Percent
|
||||||
onConfirm: () => void
|
onConfirm: () => void
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Trans } from '@lingui/macro'
|
import { Trans } from '@lingui/macro'
|
||||||
import { sendAnalyticsEvent } from '@uniswap/analytics'
|
import { sendAnalyticsEvent } from '@uniswap/analytics'
|
||||||
import { SwapEventName, SwapPriceUpdateUserResponse } from '@uniswap/analytics-events'
|
import { SwapEventName, SwapPriceUpdateUserResponse } from '@uniswap/analytics-events'
|
||||||
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
|
import { Percent, TradeType } from '@uniswap/sdk-core'
|
||||||
import { useUSDPrice } from 'hooks/useUSDPrice'
|
import { useUSDPrice } from 'hooks/useUSDPrice'
|
||||||
import { getPriceUpdateBasisPoints } from 'lib/utils/analytics'
|
import { getPriceUpdateBasisPoints } from 'lib/utils/analytics'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
@ -42,7 +42,7 @@ const ArrowWrapper = styled.div`
|
|||||||
`
|
`
|
||||||
|
|
||||||
const formatAnalyticsEventProperties = (
|
const formatAnalyticsEventProperties = (
|
||||||
trade: InterfaceTrade<Currency, Currency, TradeType>,
|
trade: InterfaceTrade,
|
||||||
priceUpdate: number | undefined,
|
priceUpdate: number | undefined,
|
||||||
response: SwapPriceUpdateUserResponse
|
response: SwapPriceUpdateUserResponse
|
||||||
) => ({
|
) => ({
|
||||||
@ -65,7 +65,7 @@ export default function SwapModalHeader({
|
|||||||
showAcceptChanges,
|
showAcceptChanges,
|
||||||
onAcceptChanges,
|
onAcceptChanges,
|
||||||
}: {
|
}: {
|
||||||
trade: InterfaceTrade<Currency, Currency, TradeType>
|
trade: InterfaceTrade
|
||||||
shouldLogModalCloseEvent: boolean
|
shouldLogModalCloseEvent: boolean
|
||||||
setShouldLogModalCloseEvent: (shouldLog: boolean) => void
|
setShouldLogModalCloseEvent: (shouldLog: boolean) => void
|
||||||
allowedSlippage: Percent
|
allowedSlippage: Percent
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { Trans } from '@lingui/macro'
|
import { Trans } from '@lingui/macro'
|
||||||
import { Currency, TradeType } from '@uniswap/sdk-core'
|
|
||||||
import { useWeb3React } from '@web3-react/core'
|
import { useWeb3React } from '@web3-react/core'
|
||||||
import Column from 'components/Column'
|
import Column from 'components/Column'
|
||||||
import { LoadingRows } from 'components/Loader/styled'
|
import { LoadingRows } from 'components/Loader/styled'
|
||||||
@ -12,13 +11,7 @@ import getRoutingDiagramEntries from 'utils/getRoutingDiagramEntries'
|
|||||||
|
|
||||||
import RouterLabel from './RouterLabel'
|
import RouterLabel from './RouterLabel'
|
||||||
|
|
||||||
export default function SwapRoute({
|
export default function SwapRoute({ trade, syncing }: { trade: InterfaceTrade; syncing: boolean }) {
|
||||||
trade,
|
|
||||||
syncing,
|
|
||||||
}: {
|
|
||||||
trade: InterfaceTrade<Currency, Currency, TradeType>
|
|
||||||
syncing: boolean
|
|
||||||
}) {
|
|
||||||
const { chainId } = useWeb3React()
|
const { chainId } = useWeb3React()
|
||||||
const autoRouterSupported = useAutoRouterSupported()
|
const autoRouterSupported = useAutoRouterSupported()
|
||||||
|
|
||||||
@ -28,9 +21,9 @@ export default function SwapRoute({
|
|||||||
// TODO(WEB-3303)
|
// TODO(WEB-3303)
|
||||||
// Can `trade.gasUseEstimateUSD` be defined when `chainId` is not in `SUPPORTED_GAS_ESTIMATE_CHAIN_IDS`?
|
// 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 && chainId && SUPPORTED_GAS_ESTIMATE_CHAIN_IDS.includes(chainId)
|
||||||
? trade.gasUseEstimateUSD.toFixed(2) === '0.00'
|
? trade.gasUseEstimateUSD === '0.00'
|
||||||
? '<$0.01'
|
? '<$0.01'
|
||||||
: '$' + trade.gasUseEstimateUSD.toFixed(2)
|
: '$' + trade.gasUseEstimateUSD
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -32,17 +32,17 @@ exports[`AdvancedSwapDetails.tsx matches base snapshot 1`] = `
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c5 {
|
.c8 {
|
||||||
width: -webkit-fit-content;
|
width: -webkit-fit-content;
|
||||||
width: -moz-fit-content;
|
width: -moz-fit-content;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c7 {
|
.c6 {
|
||||||
color: #7780A0;
|
color: #7780A0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c8 {
|
.c7 {
|
||||||
color: #0D111C;
|
color: #0D111C;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ exports[`AdvancedSwapDetails.tsx matches base snapshot 1`] = `
|
|||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c6 {
|
.c5 {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: inherit;
|
height: inherit;
|
||||||
}
|
}
|
||||||
@ -82,14 +82,34 @@ exports[`AdvancedSwapDetails.tsx matches base snapshot 1`] = `
|
|||||||
class="c2 c3 c4"
|
class="c2 c3 c4"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="c2 c3 c5"
|
class="c5"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="c6 css-zhpkf8"
|
||||||
|
>
|
||||||
|
Network fee
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="c7 css-zhpkf8"
|
||||||
|
>
|
||||||
|
~$1.00
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="c2 c3 c4"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="c2 c3 c8"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="c6"
|
class="c5"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="c7 css-zhpkf8"
|
class="c6 css-zhpkf8"
|
||||||
>
|
>
|
||||||
Minimum output
|
Minimum output
|
||||||
</div>
|
</div>
|
||||||
@ -97,7 +117,7 @@ exports[`AdvancedSwapDetails.tsx matches base snapshot 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="c8 css-zhpkf8"
|
class="c7 css-zhpkf8"
|
||||||
>
|
>
|
||||||
0.00000000000000098 DEF
|
0.00000000000000098 DEF
|
||||||
</div>
|
</div>
|
||||||
@ -106,14 +126,14 @@ exports[`AdvancedSwapDetails.tsx matches base snapshot 1`] = `
|
|||||||
class="c2 c3 c4"
|
class="c2 c3 c4"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="c2 c3 c5"
|
class="c2 c3 c8"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="c6"
|
class="c5"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="c7 css-zhpkf8"
|
class="c6 css-zhpkf8"
|
||||||
>
|
>
|
||||||
Expected output
|
Expected output
|
||||||
</div>
|
</div>
|
||||||
@ -121,7 +141,7 @@ exports[`AdvancedSwapDetails.tsx matches base snapshot 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="c8 css-zhpkf8"
|
class="c7 css-zhpkf8"
|
||||||
>
|
>
|
||||||
0.000000000000001 DEF
|
0.000000000000001 DEF
|
||||||
</div>
|
</div>
|
||||||
@ -133,16 +153,16 @@ exports[`AdvancedSwapDetails.tsx matches base snapshot 1`] = `
|
|||||||
class="c2 c3 c4"
|
class="c2 c3 c4"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="c7 css-zhpkf8"
|
class="c6 css-zhpkf8"
|
||||||
>
|
>
|
||||||
Order routing
|
Order routing
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="c6"
|
class="c5"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="c8 css-zhpkf8"
|
class="c7 css-zhpkf8"
|
||||||
>
|
>
|
||||||
Uniswap API
|
Uniswap API
|
||||||
</div>
|
</div>
|
||||||
|
@ -42,11 +42,11 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
|
|||||||
color: #0D111C;
|
color: #0D111C;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c15 {
|
.c12 {
|
||||||
color: #7780A0;
|
color: #7780A0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c13 {
|
.c16 {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
background-color: #D2D9EE;
|
background-color: #D2D9EE;
|
||||||
@ -66,7 +66,7 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
|
|||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c12 {
|
.c15 {
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
display: -webkit-flex;
|
display: -webkit-flex;
|
||||||
display: -ms-flexbox;
|
display: -ms-flexbox;
|
||||||
@ -89,11 +89,20 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
|
|||||||
transition: opacity 0.2s ease-in-out;
|
transition: opacity 0.2s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c14 {
|
.c10 {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: inherit;
|
height: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.c11 {
|
||||||
|
margin-right: 4px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c11 > * {
|
||||||
|
stroke: #98A1C0;
|
||||||
|
}
|
||||||
|
|
||||||
.c8 {
|
.c8 {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
@ -135,7 +144,7 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c10 {
|
.c13 {
|
||||||
-webkit-transform: none;
|
-webkit-transform: none;
|
||||||
-ms-transform: none;
|
-ms-transform: none;
|
||||||
transform: none;
|
transform: none;
|
||||||
@ -144,7 +153,7 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
|
|||||||
transition: transform 0.1s linear;
|
transition: transform 0.1s linear;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c11 {
|
.c14 {
|
||||||
padding-top: 12px;
|
padding-top: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,8 +193,32 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
|
|||||||
<div
|
<div
|
||||||
class="c2 c3 c6"
|
class="c2 c3 c6"
|
||||||
>
|
>
|
||||||
<svg
|
<div
|
||||||
class="c10"
|
class="c10"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="c7"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="c2 c3 c6"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="c11"
|
||||||
|
>
|
||||||
|
gas-icon.svg
|
||||||
|
</svg>
|
||||||
|
<div
|
||||||
|
class="c12 css-zhpkf8"
|
||||||
|
>
|
||||||
|
$1.00
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<svg
|
||||||
|
class="c13"
|
||||||
fill="none"
|
fill="none"
|
||||||
height="24"
|
height="24"
|
||||||
stroke="#98A1C0"
|
stroke="#98A1C0"
|
||||||
@ -207,15 +240,35 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
|
|||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="c11"
|
class="c14"
|
||||||
data-testid="advanced-swap-details"
|
data-testid="advanced-swap-details"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="c12"
|
class="c15"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="c13"
|
class="c16"
|
||||||
/>
|
/>
|
||||||
|
<div
|
||||||
|
class="c2 c3 c4"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="c10"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="c12 css-zhpkf8"
|
||||||
|
>
|
||||||
|
Network fee
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="c9 css-zhpkf8"
|
||||||
|
>
|
||||||
|
~$1.00
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
class="c2 c3 c4"
|
class="c2 c3 c4"
|
||||||
>
|
>
|
||||||
@ -223,11 +276,11 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
|
|||||||
class="c2 c3 c6"
|
class="c2 c3 c6"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="c14"
|
class="c10"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="c15 css-zhpkf8"
|
class="c12 css-zhpkf8"
|
||||||
>
|
>
|
||||||
Minimum output
|
Minimum output
|
||||||
</div>
|
</div>
|
||||||
@ -247,11 +300,11 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
|
|||||||
class="c2 c3 c6"
|
class="c2 c3 c6"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="c14"
|
class="c10"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="c15 css-zhpkf8"
|
class="c12 css-zhpkf8"
|
||||||
>
|
>
|
||||||
Expected output
|
Expected output
|
||||||
</div>
|
</div>
|
||||||
@ -265,18 +318,18 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="c13"
|
class="c16"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="c2 c3 c4"
|
class="c2 c3 c4"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="c15 css-zhpkf8"
|
class="c12 css-zhpkf8"
|
||||||
>
|
>
|
||||||
Order routing
|
Order routing
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="c14"
|
class="c10"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
|
@ -81,7 +81,7 @@ exports[`SwapModalHeader.tsx matches base snapshot for test trade with exact inp
|
|||||||
margin: -0px;
|
margin: -0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c22 {
|
.c24 {
|
||||||
width: -webkit-fit-content;
|
width: -webkit-fit-content;
|
||||||
width: -moz-fit-content;
|
width: -moz-fit-content;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
@ -91,7 +91,7 @@ exports[`SwapModalHeader.tsx matches base snapshot for test trade with exact inp
|
|||||||
color: #0D111C;
|
color: #0D111C;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c24 {
|
.c23 {
|
||||||
color: #7780A0;
|
color: #7780A0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,7 +167,7 @@ exports[`SwapModalHeader.tsx matches base snapshot for test trade with exact inp
|
|||||||
background-size: 400%;
|
background-size: 400%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c23 {
|
.c22 {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: inherit;
|
height: inherit;
|
||||||
}
|
}
|
||||||
@ -430,14 +430,34 @@ exports[`SwapModalHeader.tsx matches base snapshot for test trade with exact inp
|
|||||||
class="c5 c6 c7"
|
class="c5 c6 c7"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="c5 c6 c22"
|
class="c22"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="c23 css-zhpkf8"
|
||||||
|
>
|
||||||
|
Network fee
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="c18 css-zhpkf8"
|
||||||
|
>
|
||||||
|
~$1.00
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="c5 c6 c7"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="c5 c6 c24"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="c23"
|
class="c22"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="c24 css-zhpkf8"
|
class="c23 css-zhpkf8"
|
||||||
>
|
>
|
||||||
Minimum output
|
Minimum output
|
||||||
</div>
|
</div>
|
||||||
@ -454,14 +474,14 @@ exports[`SwapModalHeader.tsx matches base snapshot for test trade with exact inp
|
|||||||
class="c5 c6 c7"
|
class="c5 c6 c7"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="c5 c6 c22"
|
class="c5 c6 c24"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="c23"
|
class="c22"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="c24 css-zhpkf8"
|
class="c23 css-zhpkf8"
|
||||||
>
|
>
|
||||||
Expected output
|
Expected output
|
||||||
</div>
|
</div>
|
||||||
@ -481,12 +501,12 @@ exports[`SwapModalHeader.tsx matches base snapshot for test trade with exact inp
|
|||||||
class="c5 c6 c7"
|
class="c5 c6 c7"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="c24 css-zhpkf8"
|
class="c23 css-zhpkf8"
|
||||||
>
|
>
|
||||||
Order routing
|
Order routing
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="c23"
|
class="c22"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
@ -504,7 +524,7 @@ exports[`SwapModalHeader.tsx matches base snapshot for test trade with exact inp
|
|||||||
style="padding: .75rem 1rem;"
|
style="padding: .75rem 1rem;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="c24 css-k51stg"
|
class="c23 css-k51stg"
|
||||||
style="width: 100%;"
|
style="width: 100%;"
|
||||||
>
|
>
|
||||||
Output is estimated. You will receive at least
|
Output is estimated. You will receive at least
|
||||||
@ -520,7 +540,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="c24 css-8mokm4"
|
class="c23 css-8mokm4"
|
||||||
>
|
>
|
||||||
Output will be sent to
|
Output will be sent to
|
||||||
<b
|
<b
|
||||||
|
@ -113,3 +113,7 @@ export const L2_CHAIN_IDS = [
|
|||||||
] as const
|
] as const
|
||||||
|
|
||||||
export type SupportedL2ChainId = typeof L2_CHAIN_IDS[number]
|
export type SupportedL2ChainId = typeof L2_CHAIN_IDS[number]
|
||||||
|
|
||||||
|
export function isPolygonChain(chainId: number): chainId is SupportedChainId.POLYGON | SupportedChainId.POLYGON_MUMBAI {
|
||||||
|
return chainId === SupportedChainId.POLYGON || chainId === SupportedChainId.POLYGON_MUMBAI
|
||||||
|
}
|
||||||
|
@ -11,7 +11,7 @@ import { useMemo } from 'react'
|
|||||||
import { InterfaceTrade } from 'state/routing/types'
|
import { InterfaceTrade } from 'state/routing/types'
|
||||||
|
|
||||||
import useGasPrice from './useGasPrice'
|
import useGasPrice from './useGasPrice'
|
||||||
import useStablecoinPrice, { useStablecoinValue } from './useStablecoinPrice'
|
import useStablecoinPrice, { useStablecoinAmountFromFiatValue, useStablecoinValue } from './useStablecoinPrice'
|
||||||
|
|
||||||
const DEFAULT_AUTO_SLIPPAGE = new Percent(1, 1000) // .10%
|
const DEFAULT_AUTO_SLIPPAGE = new Percent(1, 1000) // .10%
|
||||||
|
|
||||||
@ -72,15 +72,14 @@ const MAX_AUTO_SLIPPAGE_TOLERANCE = new Percent(5, 100) // 5%
|
|||||||
/**
|
/**
|
||||||
* Returns slippage tolerance based on values from current trade, gas estimates from api, and active network.
|
* Returns slippage tolerance based on values from current trade, gas estimates from api, and active network.
|
||||||
*/
|
*/
|
||||||
export default function useAutoSlippageTolerance(
|
export default function useAutoSlippageTolerance(trade?: InterfaceTrade): Percent {
|
||||||
trade: InterfaceTrade<Currency, Currency, TradeType> | undefined
|
|
||||||
): Percent {
|
|
||||||
const { chainId } = useWeb3React()
|
const { chainId } = useWeb3React()
|
||||||
const onL2 = chainId && L2_CHAIN_IDS.includes(chainId)
|
const onL2 = chainId && L2_CHAIN_IDS.includes(chainId)
|
||||||
const outputDollarValue = useStablecoinValue(trade?.outputAmount)
|
const outputDollarValue = useStablecoinValue(trade?.outputAmount)
|
||||||
const nativeGasPrice = useGasPrice()
|
const nativeGasPrice = useGasPrice()
|
||||||
|
|
||||||
const gasEstimate = guesstimateGas(trade)
|
const gasEstimate = guesstimateGas(trade)
|
||||||
|
const gasEstimateUSD = useStablecoinAmountFromFiatValue(trade?.gasUseEstimateUSD) ?? null
|
||||||
const nativeCurrency = useNativeCurrency(chainId)
|
const nativeCurrency = useNativeCurrency(chainId)
|
||||||
const nativeCurrencyPrice = useStablecoinPrice((trade && nativeCurrency) ?? undefined)
|
const nativeCurrencyPrice = useStablecoinPrice((trade && nativeCurrency) ?? undefined)
|
||||||
|
|
||||||
@ -100,9 +99,7 @@ export default function useAutoSlippageTolerance(
|
|||||||
// NOTE - dont use gas estimate for L2s yet - need to verify accuracy
|
// NOTE - dont use gas estimate for L2s yet - need to verify accuracy
|
||||||
// if not, use local heuristic
|
// if not, use local heuristic
|
||||||
const dollarCostToUse =
|
const dollarCostToUse =
|
||||||
chainId && SUPPORTED_GAS_ESTIMATE_CHAIN_IDS.includes(chainId) && trade?.gasUseEstimateUSD
|
chainId && SUPPORTED_GAS_ESTIMATE_CHAIN_IDS.includes(chainId) && gasEstimateUSD ? gasEstimateUSD : dollarGasCost
|
||||||
? trade.gasUseEstimateUSD
|
|
||||||
: dollarGasCost
|
|
||||||
|
|
||||||
if (outputDollarValue && dollarCostToUse) {
|
if (outputDollarValue && dollarCostToUse) {
|
||||||
// optimize for highest possible slippage without getting MEV'd
|
// optimize for highest possible slippage without getting MEV'd
|
||||||
@ -121,5 +118,15 @@ export default function useAutoSlippageTolerance(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return DEFAULT_AUTO_SLIPPAGE
|
return DEFAULT_AUTO_SLIPPAGE
|
||||||
}, [trade, onL2, nativeGasPrice, gasEstimate, nativeCurrency, nativeCurrencyPrice, chainId, outputDollarValue])
|
}, [
|
||||||
|
trade,
|
||||||
|
onL2,
|
||||||
|
nativeGasPrice,
|
||||||
|
gasEstimate,
|
||||||
|
nativeCurrency,
|
||||||
|
nativeCurrencyPrice,
|
||||||
|
chainId,
|
||||||
|
gasEstimateUSD,
|
||||||
|
outputDollarValue,
|
||||||
|
])
|
||||||
}
|
}
|
||||||
|
@ -83,15 +83,6 @@ describe('#useBestV3Trade ExactIn', () => {
|
|||||||
expect(useClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_INPUT, undefined, undefined)
|
expect(useClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_INPUT, undefined, undefined)
|
||||||
expect(result.current).toEqual({ state: TradeState.VALID, trade: undefined })
|
expect(result.current).toEqual({ state: TradeState.VALID, trade: undefined })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('does not compute client side v3 trade if routing api is SYNCING', () => {
|
|
||||||
expectRouterMock(TradeState.SYNCING)
|
|
||||||
|
|
||||||
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_INPUT, USDCAmount, DAI))
|
|
||||||
|
|
||||||
expect(useClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_INPUT, undefined, undefined)
|
|
||||||
expect(result.current).toEqual({ state: TradeState.SYNCING, trade: undefined })
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('when routing api is in error state', () => {
|
describe('when routing api is in error state', () => {
|
||||||
@ -167,15 +158,6 @@ describe('#useBestV3Trade ExactOut', () => {
|
|||||||
expect(useClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, undefined)
|
expect(useClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, undefined)
|
||||||
expect(result.current).toEqual({ state: TradeState.VALID, trade: undefined })
|
expect(result.current).toEqual({ state: TradeState.VALID, trade: undefined })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('does not compute client side v3 trade if routing api is SYNCING', () => {
|
|
||||||
expectRouterMock(TradeState.SYNCING)
|
|
||||||
|
|
||||||
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET))
|
|
||||||
|
|
||||||
expect(useClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, undefined)
|
|
||||||
expect(result.current).toEqual({ state: TradeState.SYNCING, trade: undefined })
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('when routing api is in error state', () => {
|
describe('when routing api is in error state', () => {
|
||||||
|
@ -23,7 +23,7 @@ export function useBestTrade(
|
|||||||
otherCurrency?: Currency
|
otherCurrency?: Currency
|
||||||
): {
|
): {
|
||||||
state: TradeState
|
state: TradeState
|
||||||
trade: InterfaceTrade<Currency, Currency, TradeType> | undefined
|
trade?: InterfaceTrade
|
||||||
} {
|
} {
|
||||||
const { chainId } = useWeb3React()
|
const { chainId } = useWeb3React()
|
||||||
const autoRouterSupported = useAutoRouterSupported()
|
const autoRouterSupported = useAutoRouterSupported()
|
||||||
|
@ -5,7 +5,7 @@ import { SupportedChainId } from 'constants/chains'
|
|||||||
import JSBI from 'jsbi'
|
import JSBI from 'jsbi'
|
||||||
import { useSingleContractWithCallData } from 'lib/hooks/multicall'
|
import { useSingleContractWithCallData } from 'lib/hooks/multicall'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { InterfaceTrade, TradeState } from 'state/routing/types'
|
import { ClassicTrade, InterfaceTrade, TradeState } from 'state/routing/types'
|
||||||
|
|
||||||
import { isCelo } from '../constants/tokens'
|
import { isCelo } from '../constants/tokens'
|
||||||
import { useAllV3Routes } from './useAllV3Routes'
|
import { useAllV3Routes } from './useAllV3Routes'
|
||||||
@ -33,7 +33,7 @@ export function useClientSideV3Trade<TTradeType extends TradeType>(
|
|||||||
tradeType: TTradeType,
|
tradeType: TTradeType,
|
||||||
amountSpecified?: CurrencyAmount<Currency>,
|
amountSpecified?: CurrencyAmount<Currency>,
|
||||||
otherCurrency?: Currency
|
otherCurrency?: Currency
|
||||||
): { state: TradeState; trade: InterfaceTrade<Currency, Currency, TTradeType> | undefined } {
|
): { state: TradeState; trade: InterfaceTrade | undefined } {
|
||||||
const [currencyIn, currencyOut] =
|
const [currencyIn, currencyOut] =
|
||||||
tradeType === TradeType.EXACT_INPUT
|
tradeType === TradeType.EXACT_INPUT
|
||||||
? [amountSpecified?.currency, otherCurrency]
|
? [amountSpecified?.currency, otherCurrency]
|
||||||
@ -135,7 +135,7 @@ export function useClientSideV3Trade<TTradeType extends TradeType>(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
state: TradeState.VALID,
|
state: TradeState.VALID,
|
||||||
trade: new InterfaceTrade({
|
trade: new ClassicTrade({
|
||||||
v2Routes: [],
|
v2Routes: [],
|
||||||
v3Routes: [
|
v3Routes: [
|
||||||
{
|
{
|
||||||
|
@ -41,8 +41,8 @@ function useETHValue(currencyAmount?: CurrencyAmount<Currency>): {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!trade || !currencyAmount?.currency || !isGqlSupportedChain(chainId)) {
|
if (!trade || state === TradeState.LOADING || !currencyAmount?.currency || !isGqlSupportedChain(chainId)) {
|
||||||
return { data: undefined, isLoading: state === TradeState.LOADING || state === TradeState.SYNCING }
|
return { data: undefined, isLoading: state === TradeState.LOADING }
|
||||||
}
|
}
|
||||||
|
|
||||||
const { numerator, denominator } = trade.routes[0].midPrice
|
const { numerator, denominator } = trade.routes[0].midPrice
|
||||||
|
@ -3,8 +3,10 @@ import { BigintIsh, CurrencyAmount, Token, TradeType } from '@uniswap/sdk-core'
|
|||||||
// eslint-disable-next-line no-restricted-imports
|
// eslint-disable-next-line no-restricted-imports
|
||||||
import { AlphaRouter, AlphaRouterConfig, ChainId } from '@uniswap/smart-order-router'
|
import { AlphaRouter, AlphaRouterConfig, ChainId } from '@uniswap/smart-order-router'
|
||||||
import { SupportedChainId } from 'constants/chains'
|
import { SupportedChainId } from 'constants/chains'
|
||||||
|
import { nativeOnChain } from 'constants/tokens'
|
||||||
import JSBI from 'jsbi'
|
import JSBI from 'jsbi'
|
||||||
import { GetQuoteResult } from 'state/routing/types'
|
import { GetQuoteArgs } from 'state/routing/slice'
|
||||||
|
import { QuoteResult, QuoteState, SwapRouterNativeAssets } from 'state/routing/types'
|
||||||
import { transformSwapRouteToGetQuoteResult } from 'utils/transformSwapRouteToGetQuoteResult'
|
import { transformSwapRouteToGetQuoteResult } from 'utils/transformSwapRouteToGetQuoteResult'
|
||||||
|
|
||||||
export function toSupportedChainId(chainId: ChainId): SupportedChainId | undefined {
|
export function toSupportedChainId(chainId: ChainId): SupportedChainId | undefined {
|
||||||
@ -19,50 +21,41 @@ export function isSupportedChainId(chainId: ChainId | undefined): boolean {
|
|||||||
|
|
||||||
async function getQuote(
|
async function getQuote(
|
||||||
{
|
{
|
||||||
type,
|
tradeType,
|
||||||
tokenIn,
|
tokenIn,
|
||||||
tokenOut,
|
tokenOut,
|
||||||
amount: amountRaw,
|
amount: amountRaw,
|
||||||
}: {
|
}: {
|
||||||
type: 'exactIn' | 'exactOut'
|
tradeType: TradeType
|
||||||
tokenIn: { address: string; chainId: number; decimals: number; symbol?: string }
|
tokenIn: { address: string; chainId: number; decimals: number; symbol?: string }
|
||||||
tokenOut: { address: string; chainId: number; decimals: number; symbol?: string }
|
tokenOut: { address: string; chainId: number; decimals: number; symbol?: string }
|
||||||
amount: BigintIsh
|
amount: BigintIsh
|
||||||
},
|
},
|
||||||
router: AlphaRouter,
|
router: AlphaRouter,
|
||||||
config: Partial<AlphaRouterConfig>
|
routerConfig: Partial<AlphaRouterConfig>
|
||||||
): Promise<{ data: GetQuoteResult; error?: unknown }> {
|
): Promise<QuoteResult> {
|
||||||
const currencyIn = new Token(tokenIn.chainId, tokenIn.address, tokenIn.decimals, tokenIn.symbol)
|
const tokenInIsNative = Object.values(SwapRouterNativeAssets).includes(tokenIn.address as SwapRouterNativeAssets)
|
||||||
const currencyOut = new Token(tokenOut.chainId, tokenOut.address, tokenOut.decimals, tokenOut.symbol)
|
const tokenOutIsNative = Object.values(SwapRouterNativeAssets).includes(tokenOut.address as SwapRouterNativeAssets)
|
||||||
|
|
||||||
|
const currencyIn = tokenInIsNative
|
||||||
|
? nativeOnChain(tokenIn.chainId)
|
||||||
|
: new Token(tokenIn.chainId, tokenIn.address, tokenIn.decimals, tokenIn.symbol)
|
||||||
|
const currencyOut = tokenOutIsNative
|
||||||
|
? nativeOnChain(tokenOut.chainId)
|
||||||
|
: new Token(tokenOut.chainId, tokenOut.address, tokenOut.decimals, tokenOut.symbol)
|
||||||
|
|
||||||
|
const baseCurrency = tradeType === TradeType.EXACT_INPUT ? currencyIn : currencyOut
|
||||||
|
const quoteCurrency = tradeType === TradeType.EXACT_INPUT ? currencyOut : currencyIn
|
||||||
|
|
||||||
const baseCurrency = type === 'exactIn' ? currencyIn : currencyOut
|
|
||||||
const quoteCurrency = type === 'exactIn' ? currencyOut : currencyIn
|
|
||||||
const amount = CurrencyAmount.fromRawAmount(baseCurrency, JSBI.BigInt(amountRaw))
|
const amount = CurrencyAmount.fromRawAmount(baseCurrency, JSBI.BigInt(amountRaw))
|
||||||
|
// TODO (WEB-2055): explore initializing client side routing on first load (when amountRaw is null) if there are enough users using client-side router preference.
|
||||||
|
const swapRoute = await router.route(amount, quoteCurrency, tradeType, /*swapConfig=*/ undefined, routerConfig)
|
||||||
|
|
||||||
const swapRoute = await router.route(
|
if (!swapRoute) {
|
||||||
amount,
|
return { state: QuoteState.NOT_FOUND }
|
||||||
quoteCurrency,
|
}
|
||||||
type === 'exactIn' ? TradeType.EXACT_INPUT : TradeType.EXACT_OUTPUT,
|
|
||||||
/*swapConfig=*/ undefined,
|
|
||||||
config
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!swapRoute) throw new Error('Failed to generate client side quote')
|
return transformSwapRouteToGetQuoteResult(tradeType, amount, swapRoute)
|
||||||
|
|
||||||
return { data: transformSwapRouteToGetQuoteResult(type, amount, swapRoute) }
|
|
||||||
}
|
|
||||||
|
|
||||||
interface QuoteArguments {
|
|
||||||
tokenInAddress: string
|
|
||||||
tokenInChainId: ChainId
|
|
||||||
tokenInDecimals: number
|
|
||||||
tokenInSymbol?: string
|
|
||||||
tokenOutAddress: string
|
|
||||||
tokenOutChainId: ChainId
|
|
||||||
tokenOutDecimals: number
|
|
||||||
tokenOutSymbol?: string
|
|
||||||
amount: string
|
|
||||||
type: 'exactIn' | 'exactOut'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getClientSideQuote(
|
export async function getClientSideQuote(
|
||||||
@ -76,14 +69,14 @@ export async function getClientSideQuote(
|
|||||||
tokenOutDecimals,
|
tokenOutDecimals,
|
||||||
tokenOutSymbol,
|
tokenOutSymbol,
|
||||||
amount,
|
amount,
|
||||||
type,
|
tradeType,
|
||||||
}: QuoteArguments,
|
}: GetQuoteArgs,
|
||||||
router: AlphaRouter,
|
router: AlphaRouter,
|
||||||
config: Partial<AlphaRouterConfig>
|
config: Partial<AlphaRouterConfig>
|
||||||
) {
|
) {
|
||||||
return getQuote(
|
return getQuote(
|
||||||
{
|
{
|
||||||
type,
|
tradeType,
|
||||||
tokenIn: {
|
tokenIn: {
|
||||||
address: tokenInAddress,
|
address: tokenInAddress,
|
||||||
chainId: tokenInChainId,
|
chainId: tokenInChainId,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
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 { INTERNAL_ROUTER_PREFERENCE_PRICE, RouterPreference } from 'state/routing/slice'
|
import { INTERNAL_ROUTER_PREFERENCE_PRICE, RouterPreference } from 'state/routing/slice'
|
||||||
|
import { currencyAddressForSwapQuote } from 'state/routing/utils'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns query arguments for the Routing API query or undefined if the
|
* Returns query arguments for the Routing API query or undefined if the
|
||||||
@ -26,16 +27,16 @@ export function useRoutingAPIArguments({
|
|||||||
? undefined
|
? undefined
|
||||||
: {
|
: {
|
||||||
amount: amount.quotient.toString(),
|
amount: amount.quotient.toString(),
|
||||||
tokenInAddress: tokenIn.wrapped.address,
|
tokenInAddress: currencyAddressForSwapQuote(tokenIn),
|
||||||
tokenInChainId: tokenIn.wrapped.chainId,
|
tokenInChainId: tokenIn.wrapped.chainId,
|
||||||
tokenInDecimals: tokenIn.wrapped.decimals,
|
tokenInDecimals: tokenIn.wrapped.decimals,
|
||||||
tokenInSymbol: tokenIn.wrapped.symbol,
|
tokenInSymbol: tokenIn.wrapped.symbol,
|
||||||
tokenOutAddress: tokenOut.wrapped.address,
|
tokenOutAddress: currencyAddressForSwapQuote(tokenOut),
|
||||||
tokenOutChainId: tokenOut.wrapped.chainId,
|
tokenOutChainId: tokenOut.wrapped.chainId,
|
||||||
tokenOutDecimals: tokenOut.wrapped.decimals,
|
tokenOutDecimals: tokenOut.wrapped.decimals,
|
||||||
tokenOutSymbol: tokenOut.wrapped.symbol,
|
tokenOutSymbol: tokenOut.wrapped.symbol,
|
||||||
routerPreference,
|
routerPreference,
|
||||||
type: (tradeType === TradeType.EXACT_INPUT ? 'exactIn' : 'exactOut') as 'exactIn' | 'exactOut',
|
tradeType,
|
||||||
},
|
},
|
||||||
[amount, routerPreference, tokenIn, tokenOut, tradeType]
|
[amount, routerPreference, tokenIn, tokenOut, tradeType]
|
||||||
)
|
)
|
||||||
|
@ -39,7 +39,7 @@ export const formatSwapSignedAnalyticsEventProperties = ({
|
|||||||
fiatValues,
|
fiatValues,
|
||||||
txHash,
|
txHash,
|
||||||
}: {
|
}: {
|
||||||
trade: InterfaceTrade<Currency, Currency, TradeType> | Trade<Currency, Currency, TradeType>
|
trade: InterfaceTrade | Trade<Currency, Currency, TradeType>
|
||||||
fiatValues: { amountIn: number | undefined; amountOut: number | undefined }
|
fiatValues: { amountIn: number | undefined; amountOut: number | undefined }
|
||||||
txHash: string
|
txHash: string
|
||||||
}) => ({
|
}) => ({
|
||||||
@ -61,7 +61,7 @@ export const formatSwapSignedAnalyticsEventProperties = ({
|
|||||||
|
|
||||||
export const formatSwapQuoteReceivedEventProperties = (
|
export const formatSwapQuoteReceivedEventProperties = (
|
||||||
trade: Trade<Currency, Currency, TradeType>,
|
trade: Trade<Currency, Currency, TradeType>,
|
||||||
gasUseEstimateUSD?: CurrencyAmount<Token>,
|
gasUseEstimateUSD?: string,
|
||||||
fetchingSwapQuoteStartTime?: Date
|
fetchingSwapQuoteStartTime?: Date
|
||||||
) => {
|
) => {
|
||||||
return {
|
return {
|
||||||
@ -70,7 +70,7 @@ export const formatSwapQuoteReceivedEventProperties = (
|
|||||||
token_in_address: getTokenAddress(trade.inputAmount.currency),
|
token_in_address: getTokenAddress(trade.inputAmount.currency),
|
||||||
token_out_address: getTokenAddress(trade.outputAmount.currency),
|
token_out_address: getTokenAddress(trade.outputAmount.currency),
|
||||||
price_impact_basis_points: trade ? formatPercentInBasisPointsNumber(computeRealizedPriceImpact(trade)) : undefined,
|
price_impact_basis_points: trade ? formatPercentInBasisPointsNumber(computeRealizedPriceImpact(trade)) : undefined,
|
||||||
estimated_network_fee_usd: gasUseEstimateUSD ? formatToDecimal(gasUseEstimateUSD, 2) : undefined,
|
estimated_network_fee_usd: gasUseEstimateUSD,
|
||||||
chain_id:
|
chain_id:
|
||||||
trade.inputAmount.currency.chainId === trade.outputAmount.currency.chainId
|
trade.inputAmount.currency.chainId === trade.outputAmount.currency.chainId
|
||||||
? trade.inputAmount.currency.chainId
|
? trade.inputAmount.currency.chainId
|
||||||
|
@ -3,7 +3,7 @@ import { formatEther, parseEther } from '@ethersproject/units'
|
|||||||
import { t, Trans } from '@lingui/macro'
|
import { t, Trans } from '@lingui/macro'
|
||||||
import { sendAnalyticsEvent, TraceEvent } from '@uniswap/analytics'
|
import { sendAnalyticsEvent, TraceEvent } from '@uniswap/analytics'
|
||||||
import { BrowserEvent, InterfaceElementName, NFTEventName } from '@uniswap/analytics-events'
|
import { BrowserEvent, InterfaceElementName, NFTEventName } from '@uniswap/analytics-events'
|
||||||
import { Currency, CurrencyAmount, Token, TradeType } from '@uniswap/sdk-core'
|
import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||||
import { useWeb3React } from '@web3-react/core'
|
import { useWeb3React } from '@web3-react/core'
|
||||||
import { useToggleAccountDrawer } from 'components/AccountDrawer'
|
import { useToggleAccountDrawer } from 'components/AccountDrawer'
|
||||||
import Column from 'components/Column'
|
import Column from 'components/Column'
|
||||||
@ -208,7 +208,7 @@ const InputCurrencyValue = ({
|
|||||||
totalEthPrice: BigNumber
|
totalEthPrice: BigNumber
|
||||||
activeCurrency: Currency | undefined | null
|
activeCurrency: Currency | undefined | null
|
||||||
tradeState: TradeState
|
tradeState: TradeState
|
||||||
trade: InterfaceTrade<Currency, Currency, TradeType> | undefined
|
trade: InterfaceTrade | undefined
|
||||||
}) => {
|
}) => {
|
||||||
if (!usingPayWithAnyToken) {
|
if (!usingPayWithAnyToken) {
|
||||||
return (
|
return (
|
||||||
@ -219,7 +219,7 @@ const InputCurrencyValue = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tradeState === TradeState.LOADING) {
|
if (tradeState === TradeState.LOADING && !trade) {
|
||||||
return (
|
return (
|
||||||
<ThemedText.BodyPrimary color="textTertiary" lineHeight="20px" fontWeight="500">
|
<ThemedText.BodyPrimary color="textTertiary" lineHeight="20px" fontWeight="500">
|
||||||
<Trans>Fetching price...</Trans>
|
<Trans>Fetching price...</Trans>
|
||||||
@ -228,7 +228,7 @@ const InputCurrencyValue = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ValueText color={tradeState === TradeState.SYNCING ? 'textTertiary' : 'textPrimary'}>
|
<ValueText color={tradeState === TradeState.LOADING ? 'textTertiary' : 'textPrimary'}>
|
||||||
{ethNumberStandardFormatter(trade?.inputAmount.toExact())}
|
{ethNumberStandardFormatter(trade?.inputAmount.toExact())}
|
||||||
</ValueText>
|
</ValueText>
|
||||||
)
|
)
|
||||||
|
@ -9,7 +9,7 @@ export default function useDerivedPayWithAnyTokenSwapInfo(
|
|||||||
parsedOutputAmount?: CurrencyAmount<NativeCurrency | Token>
|
parsedOutputAmount?: CurrencyAmount<NativeCurrency | Token>
|
||||||
): {
|
): {
|
||||||
state: TradeState
|
state: TradeState
|
||||||
trade: InterfaceTrade<Currency, Currency, TradeType> | undefined
|
trade: InterfaceTrade | undefined
|
||||||
maximumAmountIn: CurrencyAmount<Token> | undefined
|
maximumAmountIn: CurrencyAmount<Token> | undefined
|
||||||
allowedSlippage: Percent
|
allowedSlippage: Percent
|
||||||
} {
|
} {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
|
import { Percent } from '@uniswap/sdk-core'
|
||||||
import { PermitInput, TokenTradeRoutesInput, TokenTradeType } from 'graphql/data/__generated__/types-and-hooks'
|
import { PermitInput, TokenTradeRoutesInput, TokenTradeType } from 'graphql/data/__generated__/types-and-hooks'
|
||||||
import { Allowance } from 'hooks/usePermit2Allowance'
|
import { Allowance } from 'hooks/usePermit2Allowance'
|
||||||
import { buildAllTradeRouteInputs } from 'nft/utils/tokenRoutes'
|
import { buildAllTradeRouteInputs } from 'nft/utils/tokenRoutes'
|
||||||
@ -8,7 +8,7 @@ import { InterfaceTrade } from 'state/routing/types'
|
|||||||
import { useTokenInput } from './useTokenInput'
|
import { useTokenInput } from './useTokenInput'
|
||||||
|
|
||||||
export default function usePayWithAnyTokenSwap(
|
export default function usePayWithAnyTokenSwap(
|
||||||
trade?: InterfaceTrade<Currency, Currency, TradeType> | undefined,
|
trade?: InterfaceTrade | undefined,
|
||||||
allowance?: Allowance,
|
allowance?: Allowance,
|
||||||
allowedSlippage?: Percent
|
allowedSlippage?: Percent
|
||||||
) {
|
) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
|
import { Percent } from '@uniswap/sdk-core'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { InterfaceTrade } from 'state/routing/types'
|
import { InterfaceTrade } from 'state/routing/types'
|
||||||
import { useTheme } from 'styled-components/macro'
|
import { useTheme } from 'styled-components/macro'
|
||||||
@ -14,7 +14,7 @@ interface PriceImpactSeverity {
|
|||||||
color: string
|
color: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function usePriceImpact(trade?: InterfaceTrade<Currency, Currency, TradeType>): PriceImpact | undefined {
|
export function usePriceImpact(trade?: InterfaceTrade): PriceImpact | undefined {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { IRoute, Protocol } from '@uniswap/router-sdk'
|
import { IRoute, Protocol } from '@uniswap/router-sdk'
|
||||||
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
|
import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
|
||||||
import { Pair } from '@uniswap/v2-sdk'
|
import { Pair } from '@uniswap/v2-sdk'
|
||||||
import { Pool } from '@uniswap/v3-sdk'
|
import { Pool } from '@uniswap/v3-sdk'
|
||||||
import { TokenAmountInput, TokenTradeRouteInput, TradePoolInput } from 'graphql/data/__generated__/types-and-hooks'
|
import { TokenAmountInput, TokenTradeRouteInput, TradePoolInput } from 'graphql/data/__generated__/types-and-hooks'
|
||||||
@ -108,7 +108,7 @@ function buildTradeRouteInput(swap: Swap): TokenTradeRouteInput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildAllTradeRouteInputs(trade: InterfaceTrade<Currency, Currency, TradeType>): {
|
export function buildAllTradeRouteInputs(trade: InterfaceTrade): {
|
||||||
mixedTokenTradeRouteInputs: TokenTradeRouteInput[] | undefined
|
mixedTokenTradeRouteInputs: TokenTradeRouteInput[] | undefined
|
||||||
v2TokenTradeRouteInputs: TokenTradeRouteInput[] | undefined
|
v2TokenTradeRouteInputs: TokenTradeRouteInput[] | undefined
|
||||||
v3TokenTradeRouteInputs: TokenTradeRouteInput[] | undefined
|
v3TokenTradeRouteInputs: TokenTradeRouteInput[] | undefined
|
||||||
|
@ -8,8 +8,7 @@ import {
|
|||||||
InterfaceSectionName,
|
InterfaceSectionName,
|
||||||
SwapEventName,
|
SwapEventName,
|
||||||
} from '@uniswap/analytics-events'
|
} from '@uniswap/analytics-events'
|
||||||
import { Trade } from '@uniswap/router-sdk'
|
import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core'
|
||||||
import { Currency, CurrencyAmount, Percent, Token, TradeType } from '@uniswap/sdk-core'
|
|
||||||
import { UNIVERSAL_ROUTER_ADDRESS } from '@uniswap/universal-router-sdk'
|
import { UNIVERSAL_ROUTER_ADDRESS } from '@uniswap/universal-router-sdk'
|
||||||
import { useWeb3React } from '@web3-react/core'
|
import { useWeb3React } from '@web3-react/core'
|
||||||
import { useToggleAccountDrawer } from 'components/AccountDrawer'
|
import { useToggleAccountDrawer } from 'components/AccountDrawer'
|
||||||
@ -115,11 +114,11 @@ const OutputSwapSection = styled(SwapSection)`
|
|||||||
`
|
`
|
||||||
|
|
||||||
function getIsValidSwapQuote(
|
function getIsValidSwapQuote(
|
||||||
trade: InterfaceTrade<Currency, Currency, TradeType> | undefined,
|
trade: InterfaceTrade | undefined,
|
||||||
tradeState: TradeState,
|
tradeState: TradeState,
|
||||||
swapInputError?: ReactNode
|
swapInputError?: ReactNode
|
||||||
): boolean {
|
): boolean {
|
||||||
return !!swapInputError && !!trade && (tradeState === TradeState.VALID || tradeState === TradeState.SYNCING)
|
return Boolean(swapInputError && trade && tradeState === TradeState.VALID)
|
||||||
}
|
}
|
||||||
|
|
||||||
function largerPercentValue(a?: Percent, b?: Percent) {
|
function largerPercentValue(a?: Percent, b?: Percent) {
|
||||||
@ -293,7 +292,7 @@ export function Swap({
|
|||||||
const fiatValueOutput = useUSDPrice(parsedAmounts[Field.OUTPUT])
|
const fiatValueOutput = useUSDPrice(parsedAmounts[Field.OUTPUT])
|
||||||
|
|
||||||
const [routeNotFound, routeIsLoading, routeIsSyncing] = useMemo(
|
const [routeNotFound, routeIsLoading, routeIsSyncing] = useMemo(
|
||||||
() => [!trade?.swaps, TradeState.LOADING === tradeState, TradeState.SYNCING === tradeState],
|
() => [!trade?.swaps, TradeState.LOADING === tradeState, TradeState.LOADING === tradeState && Boolean(trade)],
|
||||||
[trade, tradeState]
|
[trade, tradeState]
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -336,7 +335,7 @@ export function Swap({
|
|||||||
// modal and loading
|
// modal and loading
|
||||||
const [{ showConfirm, tradeToConfirm, swapErrorMessage, attemptingTxn, txHash }, setSwapState] = useState<{
|
const [{ showConfirm, tradeToConfirm, swapErrorMessage, attemptingTxn, txHash }, setSwapState] = useState<{
|
||||||
showConfirm: boolean
|
showConfirm: boolean
|
||||||
tradeToConfirm: Trade<Currency, Currency, TradeType> | undefined
|
tradeToConfirm: InterfaceTrade | undefined
|
||||||
attemptingTxn: boolean
|
attemptingTxn: boolean
|
||||||
swapErrorMessage: string | undefined
|
swapErrorMessage: string | undefined
|
||||||
txHash: string | undefined
|
txHash: string | undefined
|
||||||
|
@ -14,7 +14,15 @@ const store = configureStore({
|
|||||||
reducer,
|
reducer,
|
||||||
enhancers: (defaultEnhancers) => defaultEnhancers.concat(sentryEnhancer),
|
enhancers: (defaultEnhancers) => defaultEnhancers.concat(sentryEnhancer),
|
||||||
middleware: (getDefaultMiddleware) =>
|
middleware: (getDefaultMiddleware) =>
|
||||||
getDefaultMiddleware({ thunk: true })
|
getDefaultMiddleware({
|
||||||
|
thunk: true,
|
||||||
|
serializableCheck: {
|
||||||
|
// meta.arg and meta.baseQueryMeta are defaults. payload.trade is a nonserializable return value, but that's ok
|
||||||
|
// because we are not adding it into any persisted store that requires serialization (e.g. localStorage)
|
||||||
|
ignoredActionPaths: ['meta.arg', 'meta.baseQueryMeta', 'payload.trade'],
|
||||||
|
ignoredPaths: [routingApi.reducerPath],
|
||||||
|
},
|
||||||
|
})
|
||||||
.concat(routingApi.middleware)
|
.concat(routingApi.middleware)
|
||||||
.concat(save({ states: PERSISTED_KEYS, debounce: 1000 })),
|
.concat(save({ states: PERSISTED_KEYS, debounce: 1000 })),
|
||||||
preloadedState: load({ states: PERSISTED_KEYS, disableWarnings: isTestEnv() }),
|
preloadedState: load({ states: PERSISTED_KEYS, disableWarnings: isTestEnv() }),
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { createApi, fetchBaseQuery, FetchBaseQueryError } from '@reduxjs/toolkit/query/react'
|
import { createApi, fetchBaseQuery, FetchBaseQueryError } from '@reduxjs/toolkit/query/react'
|
||||||
import { Protocol } from '@uniswap/router-sdk'
|
import { Protocol } from '@uniswap/router-sdk'
|
||||||
|
import { TradeType } from '@uniswap/sdk-core'
|
||||||
import { AlphaRouter, ChainId } from '@uniswap/smart-order-router'
|
import { AlphaRouter, ChainId } from '@uniswap/smart-order-router'
|
||||||
import { RPC_PROVIDERS } from 'constants/providers'
|
import { RPC_PROVIDERS } from 'constants/providers'
|
||||||
import { getClientSideQuote, toSupportedChainId } from 'lib/hooks/routing/clientSideSmartOrderRouter'
|
import { getClientSideQuote, toSupportedChainId } from 'lib/hooks/routing/clientSideSmartOrderRouter'
|
||||||
@ -7,7 +8,8 @@ import ms from 'ms.macro'
|
|||||||
import qs from 'qs'
|
import qs from 'qs'
|
||||||
import { trace } from 'tracing/trace'
|
import { trace } from 'tracing/trace'
|
||||||
|
|
||||||
import { GetQuoteResult } from './types'
|
import { QuoteData, TradeResult } from './types'
|
||||||
|
import { isExactInput, transformRoutesToTrade } from './utils'
|
||||||
|
|
||||||
export enum RouterPreference {
|
export enum RouterPreference {
|
||||||
AUTO = 'auto',
|
AUTO = 'auto',
|
||||||
@ -69,7 +71,7 @@ const PRICE_PARAMS = {
|
|||||||
distributionPercent: 100,
|
distributionPercent: 100,
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GetQuoteArgs {
|
export interface GetQuoteArgs {
|
||||||
tokenInAddress: string
|
tokenInAddress: string
|
||||||
tokenInChainId: ChainId
|
tokenInChainId: ChainId
|
||||||
tokenInDecimals: number
|
tokenInDecimals: number
|
||||||
@ -80,7 +82,12 @@ interface GetQuoteArgs {
|
|||||||
tokenOutSymbol?: string
|
tokenOutSymbol?: string
|
||||||
amount: string
|
amount: string
|
||||||
routerPreference: RouterPreference | typeof INTERNAL_ROUTER_PREFERENCE_PRICE
|
routerPreference: RouterPreference | typeof INTERNAL_ROUTER_PREFERENCE_PRICE
|
||||||
type: 'exactIn' | 'exactOut'
|
tradeType: TradeType
|
||||||
|
}
|
||||||
|
|
||||||
|
enum QuoteState {
|
||||||
|
SUCCESS = 'Success',
|
||||||
|
NOT_FOUND = 'Not found',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const routingApi = createApi({
|
export const routingApi = createApi({
|
||||||
@ -89,7 +96,7 @@ export const routingApi = createApi({
|
|||||||
baseUrl: 'https://api.uniswap.org/v1/',
|
baseUrl: 'https://api.uniswap.org/v1/',
|
||||||
}),
|
}),
|
||||||
endpoints: (build) => ({
|
endpoints: (build) => ({
|
||||||
getQuote: build.query<GetQuoteResult, GetQuoteArgs>({
|
getQuote: build.query<TradeResult, GetQuoteArgs>({
|
||||||
async onQueryStarted(args: GetQuoteArgs, { queryFulfilled }) {
|
async onQueryStarted(args: GetQuoteArgs, { queryFulfilled }) {
|
||||||
trace(
|
trace(
|
||||||
'quote',
|
'quote',
|
||||||
@ -119,11 +126,14 @@ export const routingApi = createApi({
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
async queryFn(args, _api, _extraOptions, fetch) {
|
async queryFn(args, _api, _extraOptions, fetch) {
|
||||||
const { tokenInAddress, tokenInChainId, tokenOutAddress, tokenOutChainId, amount, routerPreference, type } =
|
if (
|
||||||
args
|
args.routerPreference === RouterPreference.API ||
|
||||||
|
args.routerPreference === RouterPreference.AUTO ||
|
||||||
try {
|
args.routerPreference === INTERNAL_ROUTER_PREFERENCE_PRICE
|
||||||
if (routerPreference === RouterPreference.API || routerPreference === RouterPreference.AUTO) {
|
) {
|
||||||
|
try {
|
||||||
|
const { tokenInAddress, tokenInChainId, tokenOutAddress, tokenOutChainId, amount, tradeType } = args
|
||||||
|
const type = isExactInput(tradeType) ? 'exactIn' : 'exactOut'
|
||||||
const query = qs.stringify({
|
const query = qs.stringify({
|
||||||
...API_QUERY_PARAMS,
|
...API_QUERY_PARAMS,
|
||||||
tokenInAddress,
|
tokenInAddress,
|
||||||
@ -133,21 +143,40 @@ export const routingApi = createApi({
|
|||||||
amount,
|
amount,
|
||||||
type,
|
type,
|
||||||
})
|
})
|
||||||
return (await fetch(`quote?${query}`)) as { data: GetQuoteResult } | { error: FetchBaseQueryError }
|
const response = await fetch(`quote?${query}`)
|
||||||
} else {
|
if (response.error) {
|
||||||
const router = getRouter(args.tokenInChainId)
|
try {
|
||||||
return await getClientSideQuote(
|
// cast as any here because we do a runtime check on it being an object before indexing into .errorCode
|
||||||
args,
|
const errorData = response.error.data as any
|
||||||
router,
|
// NO_ROUTE should be treated as a valid response to prevent retries.
|
||||||
// TODO(zzmp): Use PRICE_PARAMS for RouterPreference.PRICE.
|
if (typeof errorData === 'object' && errorData?.errorCode === 'NO_ROUTE') {
|
||||||
// This change is intentionally being deferred to first see what effect router caching has.
|
return { data: { state: QuoteState.NOT_FOUND } }
|
||||||
CLIENT_PARAMS
|
}
|
||||||
|
} catch {
|
||||||
|
throw response.error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const quoteData = response.data as QuoteData
|
||||||
|
const tradeResult = transformRoutesToTrade(args, quoteData)
|
||||||
|
return { data: tradeResult }
|
||||||
|
} catch (error: any) {
|
||||||
|
console.warn(
|
||||||
|
`GetQuote failed on routing API, falling back to client: ${error?.message ?? error?.detail ?? error}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
}
|
||||||
// TODO: fall back to client-side quoter when auto router fails.
|
try {
|
||||||
// deprecate 'legacy' v2/v3 routers first.
|
const router = getRouter(args.tokenInChainId)
|
||||||
return { error: { status: 'CUSTOM_ERROR', error: error.toString() } }
|
const quoteResult = await getClientSideQuote(args, router, CLIENT_PARAMS)
|
||||||
|
if (quoteResult.state === QuoteState.SUCCESS) {
|
||||||
|
return { data: transformRoutesToTrade(args, quoteResult.data) }
|
||||||
|
} else {
|
||||||
|
return { data: quoteResult }
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.warn(`GetQuote failed on client: ${error}`)
|
||||||
|
return { error: { status: 'CUSTOM_ERROR', error: error?.detail ?? error?.message ?? error } }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
keepUnusedDataFor: ms`10s`,
|
keepUnusedDataFor: ms`10s`,
|
||||||
|
@ -8,7 +8,6 @@ export enum TradeState {
|
|||||||
INVALID,
|
INVALID,
|
||||||
NO_ROUTE_FOUND,
|
NO_ROUTE_FOUND,
|
||||||
VALID,
|
VALID,
|
||||||
SYNCING,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// from https://github.com/Uniswap/routing-api/blob/main/lib/handlers/schema.ts
|
// from https://github.com/Uniswap/routing-api/blob/main/lib/handlers/schema.ts
|
||||||
@ -49,7 +48,7 @@ export type V2PoolInRoute = {
|
|||||||
address?: string
|
address?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetQuoteResult {
|
export interface QuoteData {
|
||||||
quoteId?: string
|
quoteId?: string
|
||||||
blockNumber: string
|
blockNumber: string
|
||||||
amount: string
|
amount: string
|
||||||
@ -68,12 +67,12 @@ export interface GetQuoteResult {
|
|||||||
routeString: string
|
routeString: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export class InterfaceTrade<
|
export class ClassicTrade<
|
||||||
TInput extends Currency,
|
TInput extends Currency,
|
||||||
TOutput extends Currency,
|
TOutput extends Currency,
|
||||||
TTradeType extends TradeType
|
TTradeType extends TradeType
|
||||||
> extends Trade<TInput, TOutput, TTradeType> {
|
> extends Trade<TInput, TOutput, TTradeType> {
|
||||||
gasUseEstimateUSD: CurrencyAmount<Token> | null | undefined
|
gasUseEstimateUSD: string | null | undefined
|
||||||
blockNumber: string | null | undefined
|
blockNumber: string | null | undefined
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
@ -81,8 +80,8 @@ export class InterfaceTrade<
|
|||||||
blockNumber,
|
blockNumber,
|
||||||
...routes
|
...routes
|
||||||
}: {
|
}: {
|
||||||
gasUseEstimateUSD?: CurrencyAmount<Token> | undefined | null
|
gasUseEstimateUSD?: string | null
|
||||||
blockNumber?: string | null | undefined
|
blockNumber?: string | null
|
||||||
v2Routes: {
|
v2Routes: {
|
||||||
routev2: V2Route<TInput, TOutput>
|
routev2: V2Route<TInput, TOutput>
|
||||||
inputAmount: CurrencyAmount<TInput>
|
inputAmount: CurrencyAmount<TInput>
|
||||||
@ -105,3 +104,42 @@ export class InterfaceTrade<
|
|||||||
this.gasUseEstimateUSD = gasUseEstimateUSD
|
this.gasUseEstimateUSD = gasUseEstimateUSD
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type InterfaceTrade = ClassicTrade<Currency, Currency, TradeType>
|
||||||
|
|
||||||
|
export enum QuoteState {
|
||||||
|
SUCCESS = 'Success',
|
||||||
|
NOT_FOUND = 'Not found',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type QuoteResult =
|
||||||
|
| {
|
||||||
|
state: QuoteState.NOT_FOUND
|
||||||
|
data?: undefined
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
state: QuoteState.SUCCESS
|
||||||
|
data: QuoteData
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TradeResult =
|
||||||
|
| {
|
||||||
|
state: QuoteState.NOT_FOUND
|
||||||
|
trade?: undefined
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
state: QuoteState.SUCCESS
|
||||||
|
trade: InterfaceTrade
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum PoolType {
|
||||||
|
V2Pool = 'v2-pool',
|
||||||
|
V3Pool = 'v3-pool',
|
||||||
|
}
|
||||||
|
|
||||||
|
// swap router API special cases these strings to represent native currencies
|
||||||
|
// all chains have "ETH" as native currency symbol except for polygon
|
||||||
|
export enum SwapRouterNativeAssets {
|
||||||
|
MATIC = 'MATIC',
|
||||||
|
ETH = 'ETH',
|
||||||
|
}
|
||||||
|
@ -3,14 +3,16 @@ import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
|
|||||||
import { IMetric, MetricLoggerUnit, setGlobalMetric } from '@uniswap/smart-order-router'
|
import { IMetric, MetricLoggerUnit, setGlobalMetric } from '@uniswap/smart-order-router'
|
||||||
import { sendTiming } from 'components/analytics'
|
import { sendTiming } from 'components/analytics'
|
||||||
import { AVERAGE_L1_BLOCK_TIME } from 'constants/chainInfo'
|
import { AVERAGE_L1_BLOCK_TIME } from 'constants/chainInfo'
|
||||||
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 { INTERNAL_ROUTER_PREFERENCE_PRICE, RouterPreference, useGetQuoteQuery } from 'state/routing/slice'
|
import { INTERNAL_ROUTER_PREFERENCE_PRICE, RouterPreference, useGetQuoteQuery } from 'state/routing/slice'
|
||||||
|
|
||||||
import { InterfaceTrade, TradeState } from './types'
|
import { InterfaceTrade, QuoteState, TradeState } from './types'
|
||||||
import { computeRoutes, transformRoutesToTrade } from './utils'
|
|
||||||
|
const TRADE_INVALID = { state: TradeState.INVALID, trade: undefined } as const
|
||||||
|
const TRADE_NOT_FOUND = { state: TradeState.NO_ROUTE_FOUND, trade: undefined } as const
|
||||||
|
const TRADE_LOADING = { state: TradeState.LOADING, trade: undefined } as const
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the best trade by invoking the routing api or the smart order router on the client
|
* Returns the best trade by invoking the routing api or the smart order router on the client
|
||||||
@ -25,7 +27,7 @@ export function useRoutingAPITrade<TTradeType extends TradeType>(
|
|||||||
routerPreference: RouterPreference | typeof INTERNAL_ROUTER_PREFERENCE_PRICE
|
routerPreference: RouterPreference | typeof INTERNAL_ROUTER_PREFERENCE_PRICE
|
||||||
): {
|
): {
|
||||||
state: TradeState
|
state: TradeState
|
||||||
trade: InterfaceTrade<Currency, Currency, TTradeType> | undefined
|
trade?: InterfaceTrade
|
||||||
} {
|
} {
|
||||||
const [currencyIn, currencyOut]: [Currency | undefined, Currency | undefined] = useMemo(
|
const [currencyIn, currencyOut]: [Currency | undefined, Currency | undefined] = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@ -44,10 +46,9 @@ export function useRoutingAPITrade<TTradeType extends TradeType>(
|
|||||||
})
|
})
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isLoading,
|
|
||||||
isError,
|
isError,
|
||||||
data: quoteResult,
|
data: tradeResult,
|
||||||
currentData,
|
currentData: currentTradeResult,
|
||||||
} = 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 === INTERNAL_ROUTER_PREFERENCE_PRICE ? ms`1m` : AVERAGE_L1_BLOCK_TIME,
|
pollingInterval: routerPreference === INTERNAL_ROUTER_PREFERENCE_PRICE ? ms`1m` : AVERAGE_L1_BLOCK_TIME,
|
||||||
@ -55,72 +56,23 @@ export function useRoutingAPITrade<TTradeType extends TradeType>(
|
|||||||
refetchOnMountOrArgChange: 2 * 60,
|
refetchOnMountOrArgChange: 2 * 60,
|
||||||
})
|
})
|
||||||
|
|
||||||
const route = useMemo(
|
const isCurrent = currentTradeResult === tradeResult
|
||||||
() => computeRoutes(currencyIn, currencyOut, tradeType, quoteResult),
|
|
||||||
[currencyIn, currencyOut, quoteResult, tradeType]
|
|
||||||
)
|
|
||||||
|
|
||||||
// get USD gas cost of trade in active chains stablecoin amount
|
|
||||||
const gasUseEstimateUSD = useStablecoinAmountFromFiatValue(quoteResult?.gasUseEstimateUSD) ?? null
|
|
||||||
|
|
||||||
const isSyncing = currentData !== quoteResult
|
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
if (!currencyIn || !currencyOut || currencyIn.equals(currencyOut)) {
|
if (!amountSpecified || isError || !queryArgs) {
|
||||||
|
return TRADE_INVALID
|
||||||
|
} else if (tradeResult?.state === QuoteState.NOT_FOUND && isCurrent) {
|
||||||
|
return TRADE_NOT_FOUND
|
||||||
|
} else if (!tradeResult?.trade) {
|
||||||
|
// TODO(WEB-3307): use `isLoading` returned by rtk-query hook instead of checking for `trade` status
|
||||||
|
return TRADE_LOADING
|
||||||
|
} else {
|
||||||
return {
|
return {
|
||||||
state: TradeState.INVALID,
|
state: isCurrent ? TradeState.VALID : TradeState.LOADING,
|
||||||
trade: undefined,
|
trade: tradeResult.trade,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}, [amountSpecified, isCurrent, isError, queryArgs, tradeResult])
|
||||||
if (isLoading && !quoteResult) {
|
|
||||||
// only on first hook render
|
|
||||||
return {
|
|
||||||
state: TradeState.LOADING,
|
|
||||||
trade: undefined,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let otherAmount = undefined
|
|
||||||
if (quoteResult) {
|
|
||||||
if (tradeType === TradeType.EXACT_INPUT && currencyOut) {
|
|
||||||
otherAmount = CurrencyAmount.fromRawAmount(currencyOut, quoteResult.quote)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tradeType === TradeType.EXACT_OUTPUT && currencyIn) {
|
|
||||||
otherAmount = CurrencyAmount.fromRawAmount(currencyIn, quoteResult.quote)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isError || !otherAmount || !route || route.length === 0 || !queryArgs) {
|
|
||||||
return {
|
|
||||||
state: TradeState.NO_ROUTE_FOUND,
|
|
||||||
trade: undefined,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const trade = transformRoutesToTrade(route, tradeType, quoteResult?.blockNumber, gasUseEstimateUSD)
|
|
||||||
return {
|
|
||||||
// always return VALID regardless of isFetching status
|
|
||||||
state: isSyncing ? TradeState.SYNCING : TradeState.VALID,
|
|
||||||
trade,
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return { state: TradeState.INVALID, trade: undefined }
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
currencyIn,
|
|
||||||
currencyOut,
|
|
||||||
quoteResult,
|
|
||||||
isLoading,
|
|
||||||
tradeType,
|
|
||||||
isError,
|
|
||||||
route,
|
|
||||||
queryArgs,
|
|
||||||
gasUseEstimateUSD,
|
|
||||||
isSyncing,
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// only want to enable this when app hook called
|
// only want to enable this when app hook called
|
||||||
|
@ -1,51 +1,42 @@
|
|||||||
import { Token, TradeType } from '@uniswap/sdk-core'
|
import { Token } from '@uniswap/sdk-core'
|
||||||
|
import { SupportedChainId } from 'constants/chains'
|
||||||
|
import { nativeOnChain } from 'constants/tokens'
|
||||||
|
|
||||||
import { nativeOnChain } from '../../constants/tokens'
|
import { PoolType } from './types'
|
||||||
import { computeRoutes } from './utils'
|
import { computeRoutes } from './utils'
|
||||||
|
|
||||||
const USDC = new Token(1, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 6, 'USDC')
|
const USDC = new Token(1, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 6, 'USDC')
|
||||||
const DAI = new Token(1, '0x6B175474E89094C44Da98b954EedeAC495271d0F', 6, 'DAI')
|
const DAI = new Token(1, '0x6B175474E89094C44Da98b954EedeAC495271d0F', 6, 'DAI')
|
||||||
const MKR = new Token(1, '0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2', 6, 'MKR')
|
const MKR = new Token(1, '0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2', 6, 'MKR')
|
||||||
|
|
||||||
const ETH = nativeOnChain(1)
|
const ETH = nativeOnChain(SupportedChainId.MAINNET)
|
||||||
|
|
||||||
// helper function to make amounts more readable
|
// helper function to make amounts more readable
|
||||||
const amount = (raw: TemplateStringsArray) => (parseInt(raw[0]) * 1e6).toString()
|
const amount = (raw: TemplateStringsArray) => (parseInt(raw[0]) * 1e6).toString()
|
||||||
|
|
||||||
describe('#useRoute', () => {
|
describe('#useRoute', () => {
|
||||||
it('handles an undefined payload', () => {
|
|
||||||
const result = computeRoutes(undefined, undefined, TradeType.EXACT_INPUT, undefined)
|
|
||||||
|
|
||||||
expect(result).toBeUndefined()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('handles empty edges and nodes', () => {
|
it('handles empty edges and nodes', () => {
|
||||||
const result = computeRoutes(USDC, DAI, TradeType.EXACT_INPUT, {
|
const result = computeRoutes(false, false, [])
|
||||||
route: [],
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(result).toEqual([])
|
expect(result).toEqual([])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('handles a single route trade from DAI to USDC from v3', () => {
|
it('handles a single route trade from DAI to USDC from v3', () => {
|
||||||
const result = computeRoutes(DAI, USDC, TradeType.EXACT_INPUT, {
|
const result = computeRoutes(false, false, [
|
||||||
route: [
|
[
|
||||||
[
|
{
|
||||||
{
|
type: 'v3-pool',
|
||||||
type: 'v3-pool',
|
address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2',
|
||||||
address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2',
|
amountIn: amount`1`,
|
||||||
amountIn: amount`1`,
|
amountOut: amount`5`,
|
||||||
amountOut: amount`5`,
|
fee: '500',
|
||||||
fee: '500',
|
sqrtRatioX96: '2437312313659959819381354528',
|
||||||
sqrtRatioX96: '2437312313659959819381354528',
|
liquidity: '10272714736694327408',
|
||||||
liquidity: '10272714736694327408',
|
tickCurrent: '-69633',
|
||||||
tickCurrent: '-69633',
|
tokenIn: DAI,
|
||||||
tokenIn: DAI,
|
tokenOut: USDC,
|
||||||
tokenOut: USDC,
|
},
|
||||||
},
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
})
|
])
|
||||||
|
|
||||||
const r = result?.[0]
|
const r = result?.[0]
|
||||||
|
|
||||||
@ -60,28 +51,26 @@ describe('#useRoute', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('handles a single route trade from DAI to USDC from v2', () => {
|
it('handles a single route trade from DAI to USDC from v2', () => {
|
||||||
const result = computeRoutes(DAI, USDC, TradeType.EXACT_INPUT, {
|
const result = computeRoutes(false, false, [
|
||||||
route: [
|
[
|
||||||
[
|
{
|
||||||
{
|
type: 'v2-pool',
|
||||||
type: 'v2-pool',
|
address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2',
|
||||||
address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2',
|
amountIn: amount`1`,
|
||||||
amountIn: amount`1`,
|
amountOut: amount`5`,
|
||||||
amountOut: amount`5`,
|
tokenIn: DAI,
|
||||||
tokenIn: DAI,
|
tokenOut: USDC,
|
||||||
tokenOut: USDC,
|
reserve0: {
|
||||||
reserve0: {
|
token: DAI,
|
||||||
token: DAI,
|
quotient: amount`100`,
|
||||||
quotient: amount`100`,
|
|
||||||
},
|
|
||||||
reserve1: {
|
|
||||||
token: USDC,
|
|
||||||
quotient: amount`200`,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
reserve1: {
|
||||||
|
token: USDC,
|
||||||
|
quotient: amount`200`,
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
})
|
])
|
||||||
|
|
||||||
const r = result?.[0]
|
const r = result?.[0]
|
||||||
|
|
||||||
@ -96,54 +85,52 @@ describe('#useRoute', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('handles a multi-route trade from DAI to USDC', () => {
|
it('handles a multi-route trade from DAI to USDC', () => {
|
||||||
const result = computeRoutes(DAI, USDC, TradeType.EXACT_OUTPUT, {
|
const result = computeRoutes(false, false, [
|
||||||
route: [
|
[
|
||||||
[
|
{
|
||||||
{
|
type: 'v2-pool',
|
||||||
type: 'v2-pool',
|
address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2',
|
||||||
address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2',
|
amountIn: amount`5`,
|
||||||
amountIn: amount`5`,
|
amountOut: amount`6`,
|
||||||
amountOut: amount`6`,
|
tokenIn: DAI,
|
||||||
tokenIn: DAI,
|
tokenOut: USDC,
|
||||||
tokenOut: USDC,
|
reserve0: {
|
||||||
reserve0: {
|
token: DAI,
|
||||||
token: DAI,
|
quotient: amount`1000`,
|
||||||
quotient: amount`1000`,
|
|
||||||
},
|
|
||||||
reserve1: {
|
|
||||||
token: USDC,
|
|
||||||
quotient: amount`500`,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
reserve1: {
|
||||||
[
|
token: USDC,
|
||||||
{
|
quotient: amount`500`,
|
||||||
type: 'v3-pool',
|
|
||||||
address: '0x2f8F72aA9304c8B593d555F12eF6589cC3A579A2',
|
|
||||||
amountIn: amount`10`,
|
|
||||||
amountOut: amount`1`,
|
|
||||||
fee: '3000',
|
|
||||||
tokenIn: DAI,
|
|
||||||
tokenOut: MKR,
|
|
||||||
sqrtRatioX96: '2437312313659959819381354528',
|
|
||||||
liquidity: '10272714736694327408',
|
|
||||||
tickCurrent: '-69633',
|
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
type: 'v3-pool',
|
|
||||||
address: '0x3f8F72aA9304c8B593d555F12eF6589cC3A579A2',
|
|
||||||
amountIn: amount`1`,
|
|
||||||
amountOut: amount`200`,
|
|
||||||
fee: '10000',
|
|
||||||
tokenIn: MKR,
|
|
||||||
tokenOut: USDC,
|
|
||||||
sqrtRatioX96: '2437312313659959819381354528',
|
|
||||||
liquidity: '10272714736694327408',
|
|
||||||
tickCurrent: '-69633',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
})
|
[
|
||||||
|
{
|
||||||
|
type: 'v3-pool',
|
||||||
|
address: '0x2f8F72aA9304c8B593d555F12eF6589cC3A579A2',
|
||||||
|
amountIn: amount`10`,
|
||||||
|
amountOut: amount`1`,
|
||||||
|
fee: '3000',
|
||||||
|
tokenIn: DAI,
|
||||||
|
tokenOut: MKR,
|
||||||
|
sqrtRatioX96: '2437312313659959819381354528',
|
||||||
|
liquidity: '10272714736694327408',
|
||||||
|
tickCurrent: '-69633',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'v3-pool',
|
||||||
|
address: '0x3f8F72aA9304c8B593d555F12eF6589cC3A579A2',
|
||||||
|
amountIn: amount`1`,
|
||||||
|
amountOut: amount`200`,
|
||||||
|
fee: '10000',
|
||||||
|
tokenIn: MKR,
|
||||||
|
tokenOut: USDC,
|
||||||
|
sqrtRatioX96: '2437312313659959819381354528',
|
||||||
|
liquidity: '10272714736694327408',
|
||||||
|
tickCurrent: '-69633',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
])
|
||||||
|
|
||||||
expect(result).toBeDefined()
|
expect(result).toBeDefined()
|
||||||
expect(result?.length).toBe(2)
|
expect(result?.length).toBe(2)
|
||||||
@ -165,38 +152,36 @@ describe('#useRoute', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('handles a single route trade with same token pair, different fee tiers', () => {
|
it('handles a single route trade with same token pair, different fee tiers', () => {
|
||||||
const result = computeRoutes(DAI, USDC, TradeType.EXACT_INPUT, {
|
const result = computeRoutes(false, false, [
|
||||||
route: [
|
[
|
||||||
[
|
{
|
||||||
{
|
type: 'v3-pool',
|
||||||
type: 'v3-pool',
|
address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2',
|
||||||
address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2',
|
amountIn: amount`1`,
|
||||||
amountIn: amount`1`,
|
amountOut: amount`5`,
|
||||||
amountOut: amount`5`,
|
fee: '500',
|
||||||
fee: '500',
|
tokenIn: DAI,
|
||||||
tokenIn: DAI,
|
tokenOut: USDC,
|
||||||
tokenOut: USDC,
|
sqrtRatioX96: '2437312313659959819381354528',
|
||||||
sqrtRatioX96: '2437312313659959819381354528',
|
liquidity: '10272714736694327408',
|
||||||
liquidity: '10272714736694327408',
|
tickCurrent: '-69633',
|
||||||
tickCurrent: '-69633',
|
},
|
||||||
},
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{
|
|
||||||
type: 'v3-pool',
|
|
||||||
address: '0x2f8F72aA9304c8B593d555F12eF6589cC3A579A2',
|
|
||||||
amountIn: amount`10`,
|
|
||||||
amountOut: amount`50`,
|
|
||||||
fee: '3000',
|
|
||||||
tokenIn: DAI,
|
|
||||||
tokenOut: USDC,
|
|
||||||
sqrtRatioX96: '2437312313659959819381354528',
|
|
||||||
liquidity: '10272714736694327408',
|
|
||||||
tickCurrent: '-69633',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
})
|
[
|
||||||
|
{
|
||||||
|
type: 'v3-pool',
|
||||||
|
address: '0x2f8F72aA9304c8B593d555F12eF6589cC3A579A2',
|
||||||
|
amountIn: amount`10`,
|
||||||
|
amountOut: amount`50`,
|
||||||
|
fee: '3000',
|
||||||
|
tokenIn: DAI,
|
||||||
|
tokenOut: USDC,
|
||||||
|
sqrtRatioX96: '2437312313659959819381354528',
|
||||||
|
liquidity: '10272714736694327408',
|
||||||
|
tickCurrent: '-69633',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
])
|
||||||
|
|
||||||
expect(result).toBeDefined()
|
expect(result).toBeDefined()
|
||||||
expect(result?.length).toBe(2)
|
expect(result?.length).toBe(2)
|
||||||
@ -206,28 +191,68 @@ describe('#useRoute', () => {
|
|||||||
expect(result?.[0].inputAmount.toSignificant()).toBe('1')
|
expect(result?.[0].inputAmount.toSignificant()).toBe('1')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('computes mixed routes correctly', () => {
|
||||||
|
const result = computeRoutes(false, false, [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
type: PoolType.V3Pool,
|
||||||
|
address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2',
|
||||||
|
amountIn: amount`1`,
|
||||||
|
amountOut: amount`5`,
|
||||||
|
fee: '500',
|
||||||
|
tokenIn: DAI,
|
||||||
|
tokenOut: USDC,
|
||||||
|
sqrtRatioX96: '2437312313659959819381354528',
|
||||||
|
liquidity: '10272714736694327408',
|
||||||
|
tickCurrent: '-69633',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: PoolType.V2Pool,
|
||||||
|
address: 'x2f8F72aA9304c8B593d555F12eF6589cC3A579A2',
|
||||||
|
amountIn: amount`10`,
|
||||||
|
amountOut: amount`50`,
|
||||||
|
tokenIn: USDC,
|
||||||
|
tokenOut: MKR,
|
||||||
|
reserve0: {
|
||||||
|
token: USDC,
|
||||||
|
quotient: amount`100`,
|
||||||
|
},
|
||||||
|
reserve1: {
|
||||||
|
token: MKR,
|
||||||
|
quotient: amount`200`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
])
|
||||||
|
|
||||||
|
expect(result).toBeDefined()
|
||||||
|
expect(result?.length).toBe(1)
|
||||||
|
expect(result?.[0].routev3).toBeNull()
|
||||||
|
expect(result?.[0].routev2).toBeNull()
|
||||||
|
expect(result?.[0].mixedRoute?.output).toStrictEqual(MKR)
|
||||||
|
expect(result?.[0].inputAmount.toSignificant()).toBe('1')
|
||||||
|
})
|
||||||
|
|
||||||
describe('with ETH', () => {
|
describe('with ETH', () => {
|
||||||
it('outputs native ETH as input currency', () => {
|
it('outputs native ETH as input currency', () => {
|
||||||
const WETH = ETH.wrapped
|
const WETH = ETH.wrapped
|
||||||
|
|
||||||
const result = computeRoutes(ETH, USDC, TradeType.EXACT_OUTPUT, {
|
const result = computeRoutes(true, false, [
|
||||||
route: [
|
[
|
||||||
[
|
{
|
||||||
{
|
type: 'v3-pool',
|
||||||
type: 'v3-pool',
|
address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2',
|
||||||
address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2',
|
amountIn: (1e18).toString(),
|
||||||
amountIn: (1e18).toString(),
|
amountOut: amount`5`,
|
||||||
amountOut: amount`5`,
|
fee: '500',
|
||||||
fee: '500',
|
sqrtRatioX96: '2437312313659959819381354528',
|
||||||
sqrtRatioX96: '2437312313659959819381354528',
|
liquidity: '10272714736694327408',
|
||||||
liquidity: '10272714736694327408',
|
tickCurrent: '-69633',
|
||||||
tickCurrent: '-69633',
|
tokenIn: WETH,
|
||||||
tokenIn: WETH,
|
tokenOut: USDC,
|
||||||
tokenOut: USDC,
|
},
|
||||||
},
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
})
|
])
|
||||||
|
|
||||||
expect(result).toBeDefined()
|
expect(result).toBeDefined()
|
||||||
expect(result?.length).toBe(1)
|
expect(result?.length).toBe(1)
|
||||||
@ -239,24 +264,22 @@ describe('#useRoute', () => {
|
|||||||
|
|
||||||
it('outputs native ETH as output currency', () => {
|
it('outputs native ETH as output currency', () => {
|
||||||
const WETH = new Token(1, ETH.wrapped.address, 18, 'WETH')
|
const WETH = new Token(1, ETH.wrapped.address, 18, 'WETH')
|
||||||
const result = computeRoutes(USDC, ETH, TradeType.EXACT_OUTPUT, {
|
const result = computeRoutes(false, true, [
|
||||||
route: [
|
[
|
||||||
[
|
{
|
||||||
{
|
type: 'v3-pool',
|
||||||
type: 'v3-pool',
|
address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2',
|
||||||
address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2',
|
amountIn: amount`5`,
|
||||||
amountIn: amount`5`,
|
amountOut: (1e18).toString(),
|
||||||
amountOut: (1e18).toString(),
|
fee: '500',
|
||||||
fee: '500',
|
sqrtRatioX96: '2437312313659959819381354528',
|
||||||
sqrtRatioX96: '2437312313659959819381354528',
|
liquidity: '10272714736694327408',
|
||||||
liquidity: '10272714736694327408',
|
tickCurrent: '-69633',
|
||||||
tickCurrent: '-69633',
|
tokenIn: USDC,
|
||||||
tokenIn: USDC,
|
tokenOut: WETH,
|
||||||
tokenOut: WETH,
|
},
|
||||||
},
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
})
|
])
|
||||||
|
|
||||||
expect(result?.length).toBe(1)
|
expect(result?.length).toBe(1)
|
||||||
expect(result?.[0].routev3?.input).toStrictEqual(USDC)
|
expect(result?.[0].routev3?.input).toStrictEqual(USDC)
|
||||||
@ -268,28 +291,26 @@ describe('#useRoute', () => {
|
|||||||
it('outputs native ETH as input currency for v2 routes', () => {
|
it('outputs native ETH as input currency for v2 routes', () => {
|
||||||
const WETH = ETH.wrapped
|
const WETH = ETH.wrapped
|
||||||
|
|
||||||
const result = computeRoutes(ETH, USDC, TradeType.EXACT_OUTPUT, {
|
const result = computeRoutes(true, false, [
|
||||||
route: [
|
[
|
||||||
[
|
{
|
||||||
{
|
type: 'v2-pool',
|
||||||
type: 'v2-pool',
|
address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2',
|
||||||
address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2',
|
amountIn: (1e18).toString(),
|
||||||
amountIn: (1e18).toString(),
|
amountOut: amount`5`,
|
||||||
amountOut: amount`5`,
|
tokenIn: WETH,
|
||||||
tokenIn: WETH,
|
tokenOut: USDC,
|
||||||
tokenOut: USDC,
|
reserve0: {
|
||||||
reserve0: {
|
token: WETH,
|
||||||
token: WETH,
|
quotient: amount`100`,
|
||||||
quotient: amount`100`,
|
|
||||||
},
|
|
||||||
reserve1: {
|
|
||||||
token: USDC,
|
|
||||||
quotient: amount`200`,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
reserve1: {
|
||||||
|
token: USDC,
|
||||||
|
quotient: amount`200`,
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
})
|
])
|
||||||
|
|
||||||
expect(result).toBeDefined()
|
expect(result).toBeDefined()
|
||||||
expect(result?.length).toBe(1)
|
expect(result?.length).toBe(1)
|
||||||
@ -301,28 +322,26 @@ describe('#useRoute', () => {
|
|||||||
|
|
||||||
it('outputs native ETH as output currency for v2 routes', () => {
|
it('outputs native ETH as output currency for v2 routes', () => {
|
||||||
const WETH = new Token(1, ETH.wrapped.address, 18, 'WETH')
|
const WETH = new Token(1, ETH.wrapped.address, 18, 'WETH')
|
||||||
const result = computeRoutes(USDC, ETH, TradeType.EXACT_OUTPUT, {
|
const result = computeRoutes(false, true, [
|
||||||
route: [
|
[
|
||||||
[
|
{
|
||||||
{
|
type: 'v2-pool',
|
||||||
type: 'v2-pool',
|
address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2',
|
||||||
address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2',
|
amountIn: amount`5`,
|
||||||
amountIn: amount`5`,
|
amountOut: (1e18).toString(),
|
||||||
amountOut: (1e18).toString(),
|
tokenIn: USDC,
|
||||||
tokenIn: USDC,
|
tokenOut: WETH,
|
||||||
tokenOut: WETH,
|
reserve0: {
|
||||||
reserve0: {
|
token: WETH,
|
||||||
token: WETH,
|
quotient: amount`100`,
|
||||||
quotient: amount`100`,
|
|
||||||
},
|
|
||||||
reserve1: {
|
|
||||||
token: USDC,
|
|
||||||
quotient: amount`200`,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
reserve1: {
|
||||||
|
token: USDC,
|
||||||
|
quotient: amount`200`,
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
})
|
])
|
||||||
|
|
||||||
expect(result?.length).toBe(1)
|
expect(result?.length).toBe(1)
|
||||||
expect(result?.[0].routev2?.input).toStrictEqual(USDC)
|
expect(result?.[0].routev2?.input).toStrictEqual(USDC)
|
||||||
|
@ -1,32 +1,50 @@
|
|||||||
import { MixedRouteSDK, Protocol } from '@uniswap/router-sdk'
|
import { MixedRouteSDK } from '@uniswap/router-sdk'
|
||||||
import { Currency, CurrencyAmount, Token, TradeType } from '@uniswap/sdk-core'
|
import { Currency, CurrencyAmount, Token, TradeType } from '@uniswap/sdk-core'
|
||||||
import { Pair, Route as V2Route } from '@uniswap/v2-sdk'
|
import { Pair, Route as V2Route } from '@uniswap/v2-sdk'
|
||||||
import { FeeAmount, Pool, Route as V3Route } from '@uniswap/v3-sdk'
|
import { FeeAmount, Pool, Route as V3Route } from '@uniswap/v3-sdk'
|
||||||
|
import { isPolygonChain } from 'constants/chains'
|
||||||
|
import { nativeOnChain } from 'constants/tokens'
|
||||||
|
|
||||||
import { GetQuoteResult, InterfaceTrade, V2PoolInRoute, V3PoolInRoute } from './types'
|
import { GetQuoteArgs } from './slice'
|
||||||
|
import {
|
||||||
|
ClassicTrade,
|
||||||
|
PoolType,
|
||||||
|
QuoteData,
|
||||||
|
QuoteState,
|
||||||
|
SwapRouterNativeAssets,
|
||||||
|
TradeResult,
|
||||||
|
V2PoolInRoute,
|
||||||
|
V3PoolInRoute,
|
||||||
|
} from './types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transforms a Routing API quote into an array of routes that can be used to create
|
* Transforms a Routing API quote into an array of routes that can be used to
|
||||||
* a `Trade`.
|
* create a `Trade`.
|
||||||
*/
|
*/
|
||||||
export function computeRoutes(
|
export function computeRoutes(
|
||||||
currencyIn: Currency | undefined,
|
tokenInIsNative: boolean,
|
||||||
currencyOut: Currency | undefined,
|
tokenOutIsNative: boolean,
|
||||||
tradeType: TradeType,
|
routes: QuoteData['route']
|
||||||
quoteResult: Pick<GetQuoteResult, 'route'> | undefined
|
):
|
||||||
) {
|
| {
|
||||||
if (!quoteResult || !quoteResult.route || !currencyIn || !currencyOut) return undefined
|
routev3: V3Route<Currency, Currency> | null
|
||||||
|
routev2: V2Route<Currency, Currency> | null
|
||||||
|
mixedRoute: MixedRouteSDK<Currency, Currency> | null
|
||||||
|
inputAmount: CurrencyAmount<Currency>
|
||||||
|
outputAmount: CurrencyAmount<Currency>
|
||||||
|
}[]
|
||||||
|
| undefined {
|
||||||
|
if (routes.length === 0) return []
|
||||||
|
|
||||||
if (quoteResult.route.length === 0) return []
|
const tokenIn = routes[0]?.[0]?.tokenIn
|
||||||
|
const tokenOut = routes[0]?.[routes[0]?.length - 1]?.tokenOut
|
||||||
|
if (!tokenIn || !tokenOut) throw new Error('Expected both tokenIn and tokenOut to be present')
|
||||||
|
|
||||||
const parsedTokenIn = parseToken(quoteResult.route[0][0].tokenIn)
|
const parsedCurrencyIn = tokenInIsNative ? nativeOnChain(tokenIn.chainId) : parseToken(tokenIn)
|
||||||
const parsedTokenOut = parseToken(quoteResult.route[0][quoteResult.route[0].length - 1].tokenOut)
|
const parsedCurrencyOut = tokenOutIsNative ? nativeOnChain(tokenOut.chainId) : parseToken(tokenOut)
|
||||||
if (parsedTokenIn.address !== currencyIn.wrapped.address) return undefined
|
|
||||||
if (parsedTokenOut.address !== currencyOut.wrapped.address) return undefined
|
|
||||||
if (parsedTokenIn.wrapped.equals(parsedTokenOut.wrapped)) return undefined
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return quoteResult.route.map((route) => {
|
return routes.map((route) => {
|
||||||
if (route.length === 0) {
|
if (route.length === 0) {
|
||||||
throw new Error('Expected route to have at least one pair or pool')
|
throw new Error('Expected route to have at least one pair or pool')
|
||||||
}
|
}
|
||||||
@ -37,68 +55,90 @@ export function computeRoutes(
|
|||||||
throw new Error('Expected both amountIn and amountOut to be present')
|
throw new Error('Expected both amountIn and amountOut to be present')
|
||||||
}
|
}
|
||||||
|
|
||||||
const routeProtocol = getRouteProtocol(route)
|
const isOnlyV2 = isVersionedRoute<V2PoolInRoute>(PoolType.V2Pool, route)
|
||||||
|
const isOnlyV3 = isVersionedRoute<V3PoolInRoute>(PoolType.V3Pool, route)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
routev3:
|
routev3: isOnlyV3 ? new V3Route(route.map(parsePool), parsedCurrencyIn, parsedCurrencyOut) : null,
|
||||||
routeProtocol === Protocol.V3
|
routev2: isOnlyV2 ? new V2Route(route.map(parsePair), parsedCurrencyIn, parsedCurrencyOut) : null,
|
||||||
? new V3Route(route.map(genericPoolPairParser) as Pool[], currencyIn, currencyOut)
|
|
||||||
: null,
|
|
||||||
routev2:
|
|
||||||
routeProtocol === Protocol.V2
|
|
||||||
? new V2Route(route.map(genericPoolPairParser) as Pair[], currencyIn, currencyOut)
|
|
||||||
: null,
|
|
||||||
mixedRoute:
|
mixedRoute:
|
||||||
routeProtocol === Protocol.MIXED
|
!isOnlyV3 && !isOnlyV2
|
||||||
? new MixedRouteSDK(route.map(genericPoolPairParser), currencyIn, currencyOut)
|
? new MixedRouteSDK(route.map(parsePoolOrPair), parsedCurrencyIn, parsedCurrencyOut)
|
||||||
: null,
|
: null,
|
||||||
inputAmount: CurrencyAmount.fromRawAmount(currencyIn, rawAmountIn),
|
inputAmount: CurrencyAmount.fromRawAmount(parsedCurrencyIn, rawAmountIn),
|
||||||
outputAmount: CurrencyAmount.fromRawAmount(currencyOut, rawAmountOut),
|
outputAmount: CurrencyAmount.fromRawAmount(parsedCurrencyOut, rawAmountOut),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// `Route` constructor may throw if inputs/outputs are temporarily out of sync
|
console.error('Error computing routes', e)
|
||||||
// (RTK-Query always returns the latest data which may not be the right inputs/outputs)
|
return
|
||||||
// This is not fatal and will fix itself in future render cycles
|
|
||||||
console.error(e)
|
|
||||||
return undefined
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function transformRoutesToTrade<TTradeType extends TradeType>(
|
const parsePoolOrPair = (pool: V3PoolInRoute | V2PoolInRoute): Pool | Pair => {
|
||||||
route: ReturnType<typeof computeRoutes>,
|
return pool.type === PoolType.V3Pool ? parsePool(pool) : parsePair(pool)
|
||||||
tradeType: TTradeType,
|
|
||||||
blockNumber?: string | null,
|
|
||||||
gasUseEstimateUSD?: CurrencyAmount<Token> | null
|
|
||||||
): InterfaceTrade<Currency, Currency, TTradeType> {
|
|
||||||
return new InterfaceTrade({
|
|
||||||
v2Routes:
|
|
||||||
route
|
|
||||||
?.filter((r): r is typeof route[0] & { routev2: NonNullable<typeof route[0]['routev2']> } => r.routev2 !== null)
|
|
||||||
.map(({ routev2, inputAmount, outputAmount }) => ({ routev2, inputAmount, outputAmount })) ?? [],
|
|
||||||
v3Routes:
|
|
||||||
route
|
|
||||||
?.filter((r): r is typeof route[0] & { routev3: NonNullable<typeof route[0]['routev3']> } => r.routev3 !== null)
|
|
||||||
.map(({ routev3, inputAmount, outputAmount }) => ({ routev3, inputAmount, outputAmount })) ?? [],
|
|
||||||
mixedRoutes:
|
|
||||||
route
|
|
||||||
?.filter(
|
|
||||||
(r): r is typeof route[0] & { mixedRoute: NonNullable<typeof route[0]['mixedRoute']> } =>
|
|
||||||
r.mixedRoute !== null
|
|
||||||
)
|
|
||||||
.map(({ mixedRoute, inputAmount, outputAmount }) => ({ mixedRoute, inputAmount, outputAmount })) ?? [],
|
|
||||||
tradeType,
|
|
||||||
gasUseEstimateUSD,
|
|
||||||
blockNumber,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const parseToken = ({ address, chainId, decimals, symbol }: GetQuoteResult['route'][0][0]['tokenIn']): Token => {
|
function isVersionedRoute<T extends V2PoolInRoute | V3PoolInRoute>(
|
||||||
|
type: T['type'],
|
||||||
|
route: (V3PoolInRoute | V2PoolInRoute)[]
|
||||||
|
): route is T[] {
|
||||||
|
return route.every((pool) => pool.type === type)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function transformRoutesToTrade(args: GetQuoteArgs, data: QuoteData): TradeResult {
|
||||||
|
const { tokenInAddress, tokenOutAddress, tradeType } = args
|
||||||
|
const tokenInIsNative = Object.values(SwapRouterNativeAssets).includes(tokenInAddress as SwapRouterNativeAssets)
|
||||||
|
const tokenOutIsNative = Object.values(SwapRouterNativeAssets).includes(tokenOutAddress as SwapRouterNativeAssets)
|
||||||
|
const { gasUseEstimateUSD, blockNumber } = data
|
||||||
|
const routes = computeRoutes(tokenInIsNative, tokenOutIsNative, data.route)
|
||||||
|
|
||||||
|
const trade = new ClassicTrade({
|
||||||
|
v2Routes:
|
||||||
|
routes
|
||||||
|
?.filter(
|
||||||
|
(r): r is typeof routes[0] & { routev2: NonNullable<typeof routes[0]['routev2']> } => r.routev2 !== null
|
||||||
|
)
|
||||||
|
.map(({ routev2, inputAmount, outputAmount }) => ({
|
||||||
|
routev2,
|
||||||
|
inputAmount,
|
||||||
|
outputAmount,
|
||||||
|
})) ?? [],
|
||||||
|
v3Routes:
|
||||||
|
routes
|
||||||
|
?.filter(
|
||||||
|
(r): r is typeof routes[0] & { routev3: NonNullable<typeof routes[0]['routev3']> } => r.routev3 !== null
|
||||||
|
)
|
||||||
|
.map(({ routev3, inputAmount, outputAmount }) => ({
|
||||||
|
routev3,
|
||||||
|
inputAmount,
|
||||||
|
outputAmount,
|
||||||
|
})) ?? [],
|
||||||
|
mixedRoutes:
|
||||||
|
routes
|
||||||
|
?.filter(
|
||||||
|
(r): r is typeof routes[0] & { mixedRoute: NonNullable<typeof routes[0]['mixedRoute']> } =>
|
||||||
|
r.mixedRoute !== null
|
||||||
|
)
|
||||||
|
.map(({ mixedRoute, inputAmount, outputAmount }) => ({
|
||||||
|
mixedRoute,
|
||||||
|
inputAmount,
|
||||||
|
outputAmount,
|
||||||
|
})) ?? [],
|
||||||
|
tradeType,
|
||||||
|
gasUseEstimateUSD: parseFloat(gasUseEstimateUSD).toFixed(2).toString(),
|
||||||
|
blockNumber,
|
||||||
|
})
|
||||||
|
|
||||||
|
return { state: QuoteState.SUCCESS, trade }
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseToken({ address, chainId, decimals, symbol }: QuoteData['route'][0][0]['tokenIn']): Token {
|
||||||
return new Token(chainId, address, parseInt(decimals.toString()), symbol)
|
return new Token(chainId, address, parseInt(decimals.toString()), symbol)
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsePool = ({ fee, sqrtRatioX96, liquidity, tickCurrent, tokenIn, tokenOut }: V3PoolInRoute): Pool =>
|
function parsePool({ fee, sqrtRatioX96, liquidity, tickCurrent, tokenIn, tokenOut }: V3PoolInRoute): Pool {
|
||||||
new Pool(
|
return new Pool(
|
||||||
parseToken(tokenIn),
|
parseToken(tokenIn),
|
||||||
parseToken(tokenOut),
|
parseToken(tokenOut),
|
||||||
parseInt(fee) as FeeAmount,
|
parseInt(fee) as FeeAmount,
|
||||||
@ -106,6 +146,7 @@ const parsePool = ({ fee, sqrtRatioX96, liquidity, tickCurrent, tokenIn, tokenOu
|
|||||||
liquidity,
|
liquidity,
|
||||||
parseInt(tickCurrent)
|
parseInt(tickCurrent)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const parsePair = ({ reserve0, reserve1 }: V2PoolInRoute): Pair =>
|
const parsePair = ({ reserve0, reserve1 }: V2PoolInRoute): Pair =>
|
||||||
new Pair(
|
new Pair(
|
||||||
@ -113,12 +154,15 @@ const parsePair = ({ reserve0, reserve1 }: V2PoolInRoute): Pair =>
|
|||||||
CurrencyAmount.fromRawAmount(parseToken(reserve1.token), reserve1.quotient)
|
CurrencyAmount.fromRawAmount(parseToken(reserve1.token), reserve1.quotient)
|
||||||
)
|
)
|
||||||
|
|
||||||
const genericPoolPairParser = (pool: V3PoolInRoute | V2PoolInRoute): Pool | Pair => {
|
// TODO(WEB-2050): Convert other instances of tradeType comparison to use this utility function
|
||||||
return pool.type === 'v3-pool' ? parsePool(pool) : parsePair(pool)
|
export function isExactInput(tradeType: TradeType): boolean {
|
||||||
|
return tradeType === TradeType.EXACT_INPUT
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRouteProtocol(route: (V3PoolInRoute | V2PoolInRoute)[]): Protocol {
|
export function currencyAddressForSwapQuote(currency: Currency): string {
|
||||||
if (route.every((pool) => pool.type === 'v2-pool')) return Protocol.V2
|
if (currency.isNative) {
|
||||||
if (route.every((pool) => pool.type === 'v3-pool')) return Protocol.V3
|
return isPolygonChain(currency.chainId) ? SwapRouterNativeAssets.MATIC : SwapRouterNativeAssets.ETH
|
||||||
return Protocol.MIXED
|
}
|
||||||
|
|
||||||
|
return currency.address
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,7 @@ export function useDerivedSwapInfo(
|
|||||||
parsedAmount: CurrencyAmount<Currency> | undefined
|
parsedAmount: CurrencyAmount<Currency> | undefined
|
||||||
inputError?: ReactNode
|
inputError?: ReactNode
|
||||||
trade: {
|
trade: {
|
||||||
trade: InterfaceTrade<Currency, Currency, TradeType> | undefined
|
trade?: InterfaceTrade
|
||||||
state: TradeState
|
state: TradeState
|
||||||
}
|
}
|
||||||
allowedSlippage: Percent
|
allowedSlippage: Percent
|
||||||
|
@ -2,7 +2,7 @@ import { CurrencyAmount, Percent, Token, TradeType } from '@uniswap/sdk-core'
|
|||||||
import { V3Route } from '@uniswap/smart-order-router'
|
import { V3Route } from '@uniswap/smart-order-router'
|
||||||
import { FeeAmount, Pool } from '@uniswap/v3-sdk'
|
import { FeeAmount, Pool } from '@uniswap/v3-sdk'
|
||||||
import JSBI from 'jsbi'
|
import JSBI from 'jsbi'
|
||||||
import { InterfaceTrade } from 'state/routing/types'
|
import { ClassicTrade } from 'state/routing/types'
|
||||||
|
|
||||||
export const TEST_TOKEN_1 = new Token(1, '0x0000000000000000000000000000000000000001', 18, 'ABC', 'Abc')
|
export const TEST_TOKEN_1 = new Token(1, '0x0000000000000000000000000000000000000001', 18, 'ABC', 'Abc')
|
||||||
export const TEST_TOKEN_2 = new Token(1, '0x0000000000000000000000000000000000000002', 18, 'DEF', 'Def')
|
export const TEST_TOKEN_2 = new Token(1, '0x0000000000000000000000000000000000000002', 18, 'DEF', 'Def')
|
||||||
@ -30,7 +30,7 @@ export const TEST_POOL_13 = new Pool(
|
|||||||
export const toCurrencyAmount = (token: Token, amount: number) =>
|
export const toCurrencyAmount = (token: Token, amount: number) =>
|
||||||
CurrencyAmount.fromRawAmount(token, JSBI.BigInt(amount))
|
CurrencyAmount.fromRawAmount(token, JSBI.BigInt(amount))
|
||||||
|
|
||||||
export const TEST_TRADE_EXACT_INPUT = new InterfaceTrade({
|
export const TEST_TRADE_EXACT_INPUT = new ClassicTrade({
|
||||||
v3Routes: [
|
v3Routes: [
|
||||||
{
|
{
|
||||||
routev3: new V3Route([TEST_POOL_12], TEST_TOKEN_1, TEST_TOKEN_2),
|
routev3: new V3Route([TEST_POOL_12], TEST_TOKEN_1, TEST_TOKEN_2),
|
||||||
@ -40,9 +40,10 @@ export const TEST_TRADE_EXACT_INPUT = new InterfaceTrade({
|
|||||||
],
|
],
|
||||||
v2Routes: [],
|
v2Routes: [],
|
||||||
tradeType: TradeType.EXACT_INPUT,
|
tradeType: TradeType.EXACT_INPUT,
|
||||||
|
gasUseEstimateUSD: '1.00',
|
||||||
})
|
})
|
||||||
|
|
||||||
export const TEST_TRADE_EXACT_OUTPUT = new InterfaceTrade({
|
export const TEST_TRADE_EXACT_OUTPUT = new ClassicTrade({
|
||||||
v3Routes: [
|
v3Routes: [
|
||||||
{
|
{
|
||||||
routev3: new V3Route([TEST_POOL_13], TEST_TOKEN_1, TEST_TOKEN_3),
|
routev3: new V3Route([TEST_POOL_13], TEST_TOKEN_1, TEST_TOKEN_3),
|
||||||
|
@ -15,9 +15,7 @@ const V2_DEFAULT_FEE_TIER = 3000
|
|||||||
/**
|
/**
|
||||||
* Loops through all routes on a trade and returns an array of diagram entries.
|
* Loops through all routes on a trade and returns an array of diagram entries.
|
||||||
*/
|
*/
|
||||||
export default function getRoutingDiagramEntries(
|
export default function getRoutingDiagramEntries(trade: InterfaceTrade): RoutingDiagramEntry[] {
|
||||||
trade: InterfaceTrade<Currency, Currency, TradeType>
|
|
||||||
): RoutingDiagramEntry[] {
|
|
||||||
return trade.swaps.map(({ route: { path: tokenPath, pools, protocol }, inputAmount, outputAmount }) => {
|
return trade.swaps.map(({ route: { path: tokenPath, pools, protocol }, inputAmount, outputAmount }) => {
|
||||||
const portion =
|
const portion =
|
||||||
trade.tradeType === TradeType.EXACT_INPUT
|
trade.tradeType === TradeType.EXACT_INPUT
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import { Protocol } from '@uniswap/router-sdk'
|
import { Protocol } from '@uniswap/router-sdk'
|
||||||
import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
|
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
|
||||||
import { routeAmountsToString, SwapRoute } from '@uniswap/smart-order-router'
|
import { routeAmountsToString, SwapRoute } from '@uniswap/smart-order-router'
|
||||||
import { Pool } from '@uniswap/v3-sdk'
|
import { Pool } from '@uniswap/v3-sdk'
|
||||||
import { GetQuoteResult, V2PoolInRoute, V3PoolInRoute } from 'state/routing/types'
|
import { QuoteResult, QuoteState } from 'state/routing/types'
|
||||||
|
import { QuoteData, V2PoolInRoute, V3PoolInRoute } from 'state/routing/types'
|
||||||
|
|
||||||
// from routing-api (https://github.com/Uniswap/routing-api/blob/main/lib/handlers/quote/quote.ts#L243-L311)
|
// from routing-api (https://github.com/Uniswap/routing-api/blob/main/lib/handlers/quote/quote.ts#L243-L311)
|
||||||
export function transformSwapRouteToGetQuoteResult(
|
export function transformSwapRouteToGetQuoteResult(
|
||||||
type: 'exactIn' | 'exactOut',
|
tradeType: TradeType,
|
||||||
amount: CurrencyAmount<Currency>,
|
amount: CurrencyAmount<Currency>,
|
||||||
{
|
{
|
||||||
quote,
|
quote,
|
||||||
@ -19,7 +20,7 @@ export function transformSwapRouteToGetQuoteResult(
|
|||||||
methodParameters,
|
methodParameters,
|
||||||
blockNumber,
|
blockNumber,
|
||||||
}: SwapRoute
|
}: SwapRoute
|
||||||
): GetQuoteResult {
|
): QuoteResult {
|
||||||
const routeResponse: Array<(V3PoolInRoute | V2PoolInRoute)[]> = []
|
const routeResponse: Array<(V3PoolInRoute | V2PoolInRoute)[]> = []
|
||||||
|
|
||||||
for (const subRoute of route) {
|
for (const subRoute of route) {
|
||||||
@ -34,12 +35,12 @@ export function transformSwapRouteToGetQuoteResult(
|
|||||||
|
|
||||||
let edgeAmountIn = undefined
|
let edgeAmountIn = undefined
|
||||||
if (i === 0) {
|
if (i === 0) {
|
||||||
edgeAmountIn = type === 'exactIn' ? amount.quotient.toString() : quote.quotient.toString()
|
edgeAmountIn = tradeType === TradeType.EXACT_INPUT ? amount.quotient.toString() : quote.quotient.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
let edgeAmountOut = undefined
|
let edgeAmountOut = undefined
|
||||||
if (i === pools.length - 1) {
|
if (i === pools.length - 1) {
|
||||||
edgeAmountOut = type === 'exactIn' ? quote.quotient.toString() : amount.quotient.toString()
|
edgeAmountOut = tradeType === TradeType.EXACT_INPUT ? quote.quotient.toString() : amount.quotient.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextPool instanceof Pool) {
|
if (nextPool instanceof Pool) {
|
||||||
@ -109,7 +110,7 @@ export function transformSwapRouteToGetQuoteResult(
|
|||||||
routeResponse.push(curRoute)
|
routeResponse.push(curRoute)
|
||||||
}
|
}
|
||||||
|
|
||||||
const result: GetQuoteResult = {
|
const result: QuoteData = {
|
||||||
methodParameters,
|
methodParameters,
|
||||||
blockNumber: blockNumber.toString(),
|
blockNumber: blockNumber.toString(),
|
||||||
amount: amount.quotient.toString(),
|
amount: amount.quotient.toString(),
|
||||||
@ -127,5 +128,5 @@ export function transformSwapRouteToGetQuoteResult(
|
|||||||
routeString: routeAmountsToString(route),
|
routeString: routeAmountsToString(route),
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return { state: QuoteState.SUCCESS, data: result }
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user