Commit cc9650b7 authored by Justin Domingue's avatar Justin Domingue Committed by GitHub

refactor: simplify trade hooks (#2371)

* merge tradeExactIn|Out

* cleanup

* reset polling interval

* polish

* improved types and fixed client side

* rename combined to bestv3
parent e242faca
...@@ -11,18 +11,13 @@ function useAllCommonPairs(currencyA?: Currency, currencyB?: Currency): Pair[] { ...@@ -11,18 +11,13 @@ function useAllCommonPairs(currencyA?: Currency, currencyB?: Currency): Pair[] {
const allPairs = useV2Pairs(allCurrencyCombinations) const allPairs = useV2Pairs(allCurrencyCombinations)
// only pass along valid pairs, non-duplicated pairs
return useMemo( return useMemo(
() => () =>
Object.values( Object.values(
allPairs allPairs
// filter out invalid pairs // filter out invalid pairs
.filter((result): result is [PairState.EXISTS, Pair] => Boolean(result[0] === PairState.EXISTS && result[1])) .filter((result): result is [PairState.EXISTS, Pair] => Boolean(result[0] === PairState.EXISTS && result[1]))
// filter out duplicated pairs .map(([, pair]) => pair)
.reduce<{ [pairAddress: string]: Pair }>((memo, [, curr]) => {
memo[curr.liquidityToken.address] = memo[curr.liquidityToken.address] ?? curr
return memo
}, {})
), ),
[allPairs] [allPairs]
) )
...@@ -31,71 +26,61 @@ function useAllCommonPairs(currencyA?: Currency, currencyB?: Currency): Pair[] { ...@@ -31,71 +26,61 @@ function useAllCommonPairs(currencyA?: Currency, currencyB?: Currency): Pair[] {
const MAX_HOPS = 3 const MAX_HOPS = 3
/** /**
* Returns the best trade for the exact amount of tokens in to the given token out * Returns the best v2 trade for a desired swap
* @param tradeType whether the swap is an exact in/out
* @param amountSpecified the exact amount to swap in/out
* @param otherCurrency the desired output/payment currency
*/ */
export function useV2TradeExactIn( export function useBestV2Trade(
currencyAmountIn?: CurrencyAmount<Currency>, tradeType: TradeType.EXACT_INPUT | TradeType.EXACT_OUTPUT,
currencyOut?: Currency, amountSpecified?: CurrencyAmount<Currency>,
otherCurrency?: Currency,
{ maxHops = MAX_HOPS } = {} { maxHops = MAX_HOPS } = {}
): Trade<Currency, Currency, TradeType.EXACT_INPUT> | null { ): Trade<Currency, Currency, TradeType.EXACT_INPUT | TradeType.EXACT_OUTPUT> | null {
const allowedPairs = useAllCommonPairs(currencyAmountIn?.currency, currencyOut) const [currencyIn, currencyOut] = useMemo(
() =>
tradeType === TradeType.EXACT_INPUT
? [amountSpecified?.currency, otherCurrency]
: [otherCurrency, amountSpecified?.currency],
[tradeType, amountSpecified, otherCurrency]
)
const allowedPairs = useAllCommonPairs(currencyIn, currencyOut)
return useMemo(() => { return useMemo(() => {
if (currencyAmountIn && currencyOut && allowedPairs.length > 0) { if (amountSpecified && currencyIn && currencyOut && allowedPairs.length > 0) {
if (maxHops === 1) { if (maxHops === 1) {
return ( const options = { maxHops: 1, maxNumResults: 1 }
Trade.bestTradeExactIn(allowedPairs, currencyAmountIn, currencyOut, { maxHops: 1, maxNumResults: 1 })[0] ?? if (tradeType === TradeType.EXACT_INPUT) {
null const amountIn = amountSpecified
) return Trade.bestTradeExactIn(allowedPairs, amountIn, currencyOut, options)[0] ?? null
} } else {
// search through trades with varying hops, find best trade out of them const amountOut = amountSpecified
let bestTradeSoFar: Trade<Currency, Currency, TradeType.EXACT_INPUT> | null = null return Trade.bestTradeExactOut(allowedPairs, currencyIn, amountOut, options)[0] ?? null
for (let i = 1; i <= maxHops; i++) {
const currentTrade: Trade<Currency, Currency, TradeType.EXACT_INPUT> | null =
Trade.bestTradeExactIn(allowedPairs, currencyAmountIn, currencyOut, { maxHops: i, maxNumResults: 1 })[0] ??
null
// if current trade is best yet, save it
if (isTradeBetter(bestTradeSoFar, currentTrade, BETTER_TRADE_LESS_HOPS_THRESHOLD)) {
bestTradeSoFar = currentTrade
} }
} }
return bestTradeSoFar
}
return null
}, [allowedPairs, currencyAmountIn, currencyOut, maxHops])
}
/**
* Returns the best trade for the token in to the exact amount of token out
*/
export function useV2TradeExactOut(
currencyIn?: Currency,
currencyAmountOut?: CurrencyAmount<Currency>,
{ maxHops = MAX_HOPS } = {}
): Trade<Currency, Currency, TradeType.EXACT_OUTPUT> | null {
const allowedPairs = useAllCommonPairs(currencyIn, currencyAmountOut?.currency)
return useMemo(() => {
if (currencyIn && currencyAmountOut && allowedPairs.length > 0) {
if (maxHops === 1) {
return (
Trade.bestTradeExactOut(allowedPairs, currencyIn, currencyAmountOut, { maxHops: 1, maxNumResults: 1 })[0] ??
null
)
}
// search through trades with varying hops, find best trade out of them // search through trades with varying hops, find best trade out of them
let bestTradeSoFar: Trade<Currency, Currency, TradeType.EXACT_OUTPUT> | null = null let bestTradeSoFar: Trade<Currency, Currency, TradeType.EXACT_INPUT | TradeType.EXACT_OUTPUT> | null = null
for (let i = 1; i <= maxHops; i++) { for (let i = 1; i <= maxHops; i++) {
const currentTrade = const options = { maxHops: i, maxNumResults: 1 }
Trade.bestTradeExactOut(allowedPairs, currencyIn, currencyAmountOut, { maxHops: i, maxNumResults: 1 })[0] ?? let currentTrade: Trade<Currency, Currency, TradeType.EXACT_INPUT | TradeType.EXACT_OUTPUT> | null
null
if (tradeType === TradeType.EXACT_INPUT) {
const amountIn = amountSpecified
currentTrade = Trade.bestTradeExactIn(allowedPairs, amountIn, currencyOut, options)[0] ?? null
} else {
const amountOut = amountSpecified
currentTrade = Trade.bestTradeExactOut(allowedPairs, currencyIn, amountOut, options)[0] ?? null
}
// if current trade is best yet, save it
if (isTradeBetter(bestTradeSoFar, currentTrade, BETTER_TRADE_LESS_HOPS_THRESHOLD)) { if (isTradeBetter(bestTradeSoFar, currentTrade, BETTER_TRADE_LESS_HOPS_THRESHOLD)) {
bestTradeSoFar = currentTrade bestTradeSoFar = currentTrade
} }
} }
return bestTradeSoFar return bestTradeSoFar
} }
return null return null
}, [currencyIn, currencyAmountOut, allowedPairs, maxHops]) }, [tradeType, amountSpecified, currencyIn, currencyOut, allowedPairs, maxHops])
} }
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
import { Trade } from '@uniswap/v3-sdk'
import { V3TradeState } from 'state/routing/types'
import { useRoutingAPITrade } from 'state/routing/useRoutingAPITrade'
import { useRoutingAPIEnabled } from 'state/user/hooks'
import { useClientSideV3Trade } from './useClientSideV3Trade'
import useDebounce from './useDebounce'
import useIsWindowVisible from './useIsWindowVisible'
/**
* Returns the best v3 trade for a desired swap.
* Uses optimized routes from the Routing API and falls back to the v3 router.
* @param tradeType whether the swap is an exact in/out
* @param amountSpecified the exact amount to swap in/out
* @param otherCurrency the desired output/payment currency
*/
export function useBestV3Trade(
tradeType: TradeType,
amountSpecified?: CurrencyAmount<Currency>,
otherCurrency?: Currency
): {
state: V3TradeState
trade: Trade<Currency, Currency, typeof tradeType> | null
} {
const routingAPIEnabled = useRoutingAPIEnabled()
const isWindowVisible = useIsWindowVisible()
const debouncedAmount = useDebounce(amountSpecified, 100)
const routingAPITrade = useRoutingAPITrade(
tradeType,
routingAPIEnabled && isWindowVisible ? debouncedAmount : undefined,
otherCurrency
)
const isLoading = amountSpecified !== undefined && debouncedAmount === undefined
// consider trade debouncing when inputs/outputs do not match
const debouncing =
routingAPITrade.trade &&
amountSpecified &&
(tradeType === TradeType.EXACT_INPUT
? !routingAPITrade.trade.inputAmount.equalTo(amountSpecified) ||
!amountSpecified.currency.equals(routingAPITrade.trade.inputAmount.currency) ||
!otherCurrency?.equals(routingAPITrade.trade.outputAmount.currency)
: !routingAPITrade.trade.outputAmount.equalTo(amountSpecified) ||
!amountSpecified.currency.equals(routingAPITrade.trade.outputAmount.currency) ||
!otherCurrency?.equals(routingAPITrade.trade.inputAmount.currency))
const useFallback = !routingAPIEnabled || (!debouncing && routingAPITrade.state === V3TradeState.NO_ROUTE_FOUND)
// only use client side router if routing api trade failed
const bestV3Trade = useClientSideV3Trade(
tradeType,
useFallback ? debouncedAmount : undefined,
useFallback ? otherCurrency : undefined
)
return {
...(useFallback ? bestV3Trade : routingAPITrade),
...(debouncing ? { state: V3TradeState.SYNCING } : {}),
...(isLoading ? { state: V3TradeState.LOADING } : {}),
}
}
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core' import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
import { Route, SwapQuoter, Trade } from '@uniswap/v3-sdk' import { Route, SwapQuoter, Trade } from '@uniswap/v3-sdk'
import { SupportedChainId } from 'constants/chains' import { SupportedChainId } from 'constants/chains'
import { BigNumber } from 'ethers' import JSBI from 'jsbi'
import { useMemo } from 'react' import { useMemo } from 'react'
import { V3TradeState } from 'state/routing/types' import { V3TradeState } from 'state/routing/types'
import { useSingleContractWithCallData } from '../state/multicall/hooks' import { useSingleContractWithCallData } from '../state/multicall/hooks'
...@@ -17,108 +17,31 @@ const QUOTE_GAS_OVERRIDES: { [chainId: number]: number } = { ...@@ -17,108 +17,31 @@ const QUOTE_GAS_OVERRIDES: { [chainId: number]: number } = {
const DEFAULT_GAS_QUOTE = 2_000_000 const DEFAULT_GAS_QUOTE = 2_000_000
/** /**
* Returns the best v3 trade for a desired exact input swap * Returns the best v3 trade for a desired swap
* @param amountIn the amount to swap in * @param tradeType whether the swap is an exact in/out
* @param currencyOut the desired output currency * @param amountSpecified the exact amount to swap in/out
* @param otherCurrency the desired output/payment currency
*/ */
export function useClientV3TradeExactIn( export function useClientSideV3Trade<TTradeType extends TradeType>(
amountIn?: CurrencyAmount<Currency>, tradeType: TTradeType,
currencyOut?: Currency amountSpecified?: CurrencyAmount<Currency>,
): { state: V3TradeState; trade: Trade<Currency, Currency, TradeType.EXACT_INPUT> | null } { otherCurrency?: Currency
const { routes, loading: routesLoading } = useAllV3Routes(amountIn?.currency, currencyOut) ): { state: V3TradeState; trade: Trade<Currency, Currency, TTradeType> | null } {
const [currencyIn, currencyOut] = useMemo(
const quoter = useV3Quoter() () =>
const { chainId } = useActiveWeb3React() tradeType === TradeType.EXACT_INPUT
const quotesResults = useSingleContractWithCallData( ? [amountSpecified?.currency, otherCurrency]
quoter, : [otherCurrency, amountSpecified?.currency],
amountIn [tradeType, amountSpecified, otherCurrency]
? routes.map((route) => SwapQuoter.quoteCallParameters(route, amountIn, TradeType.EXACT_INPUT).calldata)
: [],
{
gasRequired: chainId ? QUOTE_GAS_OVERRIDES[chainId] ?? DEFAULT_GAS_QUOTE : undefined,
}
) )
const { routes, loading: routesLoading } = useAllV3Routes(currencyIn, currencyOut)
return useMemo(() => {
if (
!amountIn ||
!currencyOut ||
// skip when tokens are the same
amountIn.currency.equals(currencyOut)
) {
return {
state: V3TradeState.INVALID,
trade: null,
}
}
if (routesLoading || quotesResults.some(({ loading }) => loading)) {
return {
state: V3TradeState.LOADING,
trade: null,
}
}
const { bestRoute, amountOut } = quotesResults.reduce(
(currentBest: { bestRoute: Route<Currency, Currency> | null; amountOut: BigNumber | null }, { result }, i) => {
if (!result) return currentBest
if (currentBest.amountOut === null) {
return {
bestRoute: routes[i],
amountOut: result.amountOut,
}
} else if (currentBest.amountOut.lt(result.amountOut)) {
return {
bestRoute: routes[i],
amountOut: result.amountOut,
}
}
return currentBest
},
{
bestRoute: null,
amountOut: null,
}
)
if (!bestRoute || !amountOut) {
return {
state: V3TradeState.NO_ROUTE_FOUND,
trade: null,
}
}
return {
state: V3TradeState.VALID,
trade: Trade.createUncheckedTrade({
route: bestRoute,
tradeType: TradeType.EXACT_INPUT,
inputAmount: amountIn,
outputAmount: CurrencyAmount.fromRawAmount(currencyOut, amountOut.toString()),
}),
}
}, [amountIn, currencyOut, quotesResults, routes, routesLoading])
}
/**
* Returns the best v3 trade for a desired exact output swap
* @param currencyIn the desired input currency
* @param amountOut the amount to swap out
*/
export function useClientSideV3TradeExactOut(
currencyIn?: Currency,
amountOut?: CurrencyAmount<Currency>
): { state: V3TradeState; trade: Trade<Currency, Currency, TradeType.EXACT_OUTPUT> | null } {
const { routes, loading: routesLoading } = useAllV3Routes(currencyIn, amountOut?.currency)
const quoter = useV3Quoter() const quoter = useV3Quoter()
const { chainId } = useActiveWeb3React() const { chainId } = useActiveWeb3React()
const quotesResults = useSingleContractWithCallData( const quotesResults = useSingleContractWithCallData(
quoter, quoter,
amountOut amountSpecified
? routes.map((route) => SwapQuoter.quoteCallParameters(route, amountOut, TradeType.EXACT_OUTPUT).calldata) ? routes.map((route) => SwapQuoter.quoteCallParameters(route, amountSpecified, tradeType).calldata)
: [], : [],
{ {
gasRequired: chainId ? QUOTE_GAS_OVERRIDES[chainId] ?? DEFAULT_GAS_QUOTE : undefined, gasRequired: chainId ? QUOTE_GAS_OVERRIDES[chainId] ?? DEFAULT_GAS_QUOTE : undefined,
...@@ -127,11 +50,14 @@ export function useClientSideV3TradeExactOut( ...@@ -127,11 +50,14 @@ export function useClientSideV3TradeExactOut(
return useMemo(() => { return useMemo(() => {
if ( if (
!amountOut || !amountSpecified ||
!currencyIn || !currencyIn ||
!currencyOut ||
quotesResults.some(({ valid }) => !valid) || quotesResults.some(({ valid }) => !valid) ||
// skip when tokens are the same // skip when tokens are the same
amountOut.currency.equals(currencyIn) (tradeType === TradeType.EXACT_INPUT
? amountSpecified.currency.equals(currencyOut)
: amountSpecified.currency.equals(currencyIn))
) { ) {
return { return {
state: V3TradeState.INVALID, state: V3TradeState.INVALID,
...@@ -146,19 +72,36 @@ export function useClientSideV3TradeExactOut( ...@@ -146,19 +72,36 @@ export function useClientSideV3TradeExactOut(
} }
} }
const { bestRoute, amountIn } = quotesResults.reduce( const { bestRoute, amountIn, amountOut } = quotesResults.reduce(
(currentBest: { bestRoute: Route<Currency, Currency> | null; amountIn: BigNumber | null }, { result }, i) => { (
currentBest: {
bestRoute: Route<Currency, Currency> | null
amountIn: CurrencyAmount<Currency> | null
amountOut: CurrencyAmount<Currency> | null
},
{ result },
i
) => {
if (!result) return currentBest if (!result) return currentBest
if (currentBest.amountIn === null) { // overwrite the current best if it's not defined or if this route is better
return { if (tradeType === TradeType.EXACT_INPUT) {
bestRoute: routes[i], const amountOut = CurrencyAmount.fromRawAmount(currencyOut, result.amountOut.toString())
amountIn: result.amountIn, if (currentBest.amountOut === null || JSBI.lessThan(currentBest.amountOut.quotient, amountOut.quotient)) {
return {
bestRoute: routes[i],
amountIn: amountSpecified,
amountOut,
}
} }
} else if (currentBest.amountIn.gt(result.amountIn)) { } else {
return { const amountIn = CurrencyAmount.fromRawAmount(currencyIn, result.amountIn.toString())
bestRoute: routes[i], if (currentBest.amountIn === null || JSBI.greaterThan(currentBest.amountIn.quotient, amountIn.quotient)) {
amountIn: result.amountIn, return {
bestRoute: routes[i],
amountIn,
amountOut: amountSpecified,
}
} }
} }
...@@ -167,10 +110,11 @@ export function useClientSideV3TradeExactOut( ...@@ -167,10 +110,11 @@ export function useClientSideV3TradeExactOut(
{ {
bestRoute: null, bestRoute: null,
amountIn: null, amountIn: null,
amountOut: null,
} }
) )
if (!bestRoute || !amountIn) { if (!bestRoute || !amountIn || !amountOut) {
return { return {
state: V3TradeState.NO_ROUTE_FOUND, state: V3TradeState.NO_ROUTE_FOUND,
trade: null, trade: null,
...@@ -181,10 +125,10 @@ export function useClientSideV3TradeExactOut( ...@@ -181,10 +125,10 @@ export function useClientSideV3TradeExactOut(
state: V3TradeState.VALID, state: V3TradeState.VALID,
trade: Trade.createUncheckedTrade({ trade: Trade.createUncheckedTrade({
route: bestRoute, route: bestRoute,
tradeType: TradeType.EXACT_OUTPUT, tradeType,
inputAmount: CurrencyAmount.fromRawAmount(currencyIn, amountIn.toString()), inputAmount: amountIn,
outputAmount: amountOut, outputAmount: amountOut,
}), }),
} }
}, [amountOut, currencyIn, quotesResults, routes, routesLoading]) }, [amountSpecified, currencyIn, currencyOut, quotesResults, routes, routesLoading, tradeType])
} }
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
import { Trade } from '@uniswap/v3-sdk'
import { V3TradeState } from 'state/routing/types'
import { useRoutingAPITradeExactIn, useRoutingAPITradeExactOut } from 'state/routing/useRoutingAPITrade'
import { useRoutingAPIEnabled } from 'state/user/hooks'
import useDebounce from './useDebounce'
import useIsWindowVisible from './useIsWindowVisible'
import { useClientV3TradeExactIn, useClientSideV3TradeExactOut } from './useClientSideV3Trade'
/**
* Returns the best v3 trade for a desired exact input swap.
* Uses optimized routes from the Routing API and falls back to the v3 router.
* @param amountIn The amount to swap in
* @param currentOut the desired output currency
*/
export function useV3TradeExactIn(
amountIn?: CurrencyAmount<Currency>,
currencyOut?: Currency
): {
state: V3TradeState
trade: Trade<Currency, Currency, TradeType.EXACT_INPUT> | null
} {
const routingAPIEnabled = useRoutingAPIEnabled()
const isWindowVisible = useIsWindowVisible()
const debouncedAmountIn = useDebounce(amountIn, 100)
const routingAPITradeExactIn = useRoutingAPITradeExactIn(
routingAPIEnabled && isWindowVisible ? debouncedAmountIn : undefined,
currencyOut
)
const isLoading = amountIn !== undefined && debouncedAmountIn === undefined
// consider trade debouncing when inputs/outputs do not match
const debouncing =
routingAPITradeExactIn.trade &&
amountIn &&
(!routingAPITradeExactIn.trade.inputAmount.equalTo(amountIn) ||
!amountIn.currency.equals(routingAPITradeExactIn.trade.inputAmount.currency) ||
!currencyOut?.equals(routingAPITradeExactIn.trade.outputAmount.currency))
const useFallback =
!routingAPIEnabled || (!debouncing && routingAPITradeExactIn.state === V3TradeState.NO_ROUTE_FOUND)
// only use client side router if routing api trade failed
const bestV3TradeExactIn = useClientV3TradeExactIn(
useFallback ? debouncedAmountIn : undefined,
useFallback ? currencyOut : undefined
)
return {
...(useFallback ? bestV3TradeExactIn : routingAPITradeExactIn),
...(debouncing ? { state: V3TradeState.SYNCING } : {}),
...(isLoading ? { state: V3TradeState.LOADING } : {}),
}
}
/**
* Returns the best v3 trade for a desired exact output swap.
* Uses optimized routes from the Routing API and falls back to the v3 router.
* @param currentIn the desired input currency
* @param amountOut The amount to swap out
*/
export function useV3TradeExactOut(
currencyIn?: Currency,
amountOut?: CurrencyAmount<Currency>
): {
state: V3TradeState
trade: Trade<Currency, Currency, TradeType.EXACT_OUTPUT> | null
} {
const routingAPIEnabled = useRoutingAPIEnabled()
const isWindowVisible = useIsWindowVisible()
const debouncedAmountOut = useDebounce(amountOut, 100)
const routingAPITradeExactOut = useRoutingAPITradeExactOut(
routingAPIEnabled && isWindowVisible ? currencyIn : undefined,
debouncedAmountOut
)
const isLoading = amountOut !== undefined && debouncedAmountOut === undefined
const debouncing =
routingAPITradeExactOut.trade &&
amountOut &&
(!routingAPITradeExactOut.trade.outputAmount.equalTo(amountOut) ||
!currencyIn?.equals(routingAPITradeExactOut.trade.inputAmount.currency) ||
!amountOut.currency.equals(routingAPITradeExactOut.trade.outputAmount.currency))
const useFallback =
!routingAPIEnabled || (!debouncing && routingAPITradeExactOut.state === V3TradeState.NO_ROUTE_FOUND)
const bestV3TradeExactOut = useClientSideV3TradeExactOut(
useFallback ? currencyIn : undefined,
useFallback ? debouncedAmountOut : undefined
)
return {
...(useFallback ? bestV3TradeExactOut : routingAPITradeExactOut),
...(debouncing ? { state: V3TradeState.SYNCING } : {}),
...(isLoading ? { state: V3TradeState.LOADING } : {}),
}
}
import { Currency, CurrencyAmount, Price, Token } from '@uniswap/sdk-core' import { Currency, CurrencyAmount, Price, Token, TradeType } from '@uniswap/sdk-core'
import { useMemo } from 'react' import { useMemo } from 'react'
import { SupportedChainId } from '../constants/chains' import { SupportedChainId } from '../constants/chains'
import { DAI_OPTIMISM, USDC, USDC_ARBITRUM } from '../constants/tokens' import { DAI_OPTIMISM, USDC, USDC_ARBITRUM } from '../constants/tokens'
import { useV2TradeExactOut } from './useV2Trade' import { useBestV2Trade } from './useBestV2Trade'
import { useClientSideV3TradeExactOut } from './useClientSideV3Trade' import { useClientSideV3Trade } from './useClientSideV3Trade'
import { useActiveWeb3React } from './web3' import { useActiveWeb3React } from './web3'
// Stablecoin amounts used when calculating spot price for a given currency. // Stablecoin amounts used when calculating spot price for a given currency.
...@@ -24,10 +24,10 @@ export default function useUSDCPrice(currency?: Currency): Price<Currency, Token ...@@ -24,10 +24,10 @@ export default function useUSDCPrice(currency?: Currency): Price<Currency, Token
const amountOut = chainId ? STABLECOIN_AMOUNT_OUT[chainId] : undefined const amountOut = chainId ? STABLECOIN_AMOUNT_OUT[chainId] : undefined
const stablecoin = amountOut?.currency const stablecoin = amountOut?.currency
const v2USDCTrade = useV2TradeExactOut(currency, amountOut, { const v2USDCTrade = useBestV2Trade(TradeType.EXACT_OUTPUT, amountOut, currency, {
maxHops: 2, maxHops: 2,
}) })
const v3USDCTrade = useClientSideV3TradeExactOut(currency, amountOut) const v3USDCTrade = useClientSideV3Trade(TradeType.EXACT_OUTPUT, amountOut, currency)
return useMemo(() => { return useMemo(() => {
if (!currency || !stablecoin) { if (!currency || !stablecoin) {
......
import { skipToken } from '@reduxjs/toolkit/query/react' import { skipToken } from '@reduxjs/toolkit/query/react'
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core' import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
import { Trade } from '@uniswap/v3-sdk' import { Trade } from '@uniswap/v3-sdk'
import { BigNumber } from 'ethers'
import ms from 'ms.macro' import ms from 'ms.macro'
import { useMemo } from 'react' import { useMemo } from 'react'
import { useBlockNumber } from 'state/application/hooks' import { useBlockNumber } from 'state/application/hooks'
import { useGetQuoteQuery } from 'state/routing/slice' import { useGetQuoteQuery } from 'state/routing/slice'
import { V3TradeState } from './types'
import { computeRoutes } from './computeRoutes' import { computeRoutes } from './computeRoutes'
import { V3TradeState } from './types'
function useFreshData<T>(data: T, dataBlockNumber: number, maxBlockAge = 10): T | undefined { function useFreshData<T>(data: T, dataBlockNumber: number, maxBlockAge = 10): T | undefined {
const localBlockNumber = useBlockNumber() const localBlockNumber = useBlockNumber()
...@@ -28,12 +27,12 @@ function useRoutingAPIArguments({ ...@@ -28,12 +27,12 @@ function useRoutingAPIArguments({
tokenIn, tokenIn,
tokenOut, tokenOut,
amount, amount,
type, tradeType,
}: { }: {
tokenIn: Currency | undefined tokenIn: Currency | undefined
tokenOut: Currency | undefined tokenOut: Currency | undefined
amount: CurrencyAmount<Currency> | undefined amount: CurrencyAmount<Currency> | undefined
type: 'exactIn' | 'exactOut' tradeType: TradeType
}) { }) {
if (!tokenIn || !tokenOut || !amount || tokenIn.equals(tokenOut)) { if (!tokenIn || !tokenOut || !amount || tokenIn.equals(tokenOut)) {
return undefined return undefined
...@@ -45,80 +44,34 @@ function useRoutingAPIArguments({ ...@@ -45,80 +44,34 @@ function useRoutingAPIArguments({
tokenOutAddress: tokenOut.wrapped.address, tokenOutAddress: tokenOut.wrapped.address,
tokenOutChainId: tokenOut.chainId, tokenOutChainId: tokenOut.chainId,
amount: amount.quotient.toString(), amount: amount.quotient.toString(),
type, type: (tradeType === TradeType.EXACT_INPUT ? 'exactIn' : 'exactOut') as 'exactIn' | 'exactOut',
} }
} }
export function useRoutingAPITradeExactIn(amountIn?: CurrencyAmount<Currency>, currencyOut?: Currency) { /**
const queryArgs = useRoutingAPIArguments({ * Returns the best v3 trade by invoking the routing api
tokenIn: amountIn?.currency, * @param tradeType whether the swap is an exact in/out
tokenOut: currencyOut, * @param amountSpecified the exact amount to swap in/out
amount: amountIn, * @param otherCurrency the desired output/payment currency
type: 'exactIn', */
}) export function useRoutingAPITrade<TTradeType extends TradeType>(
tradeType: TTradeType,
const { isLoading, isError, data } = useGetQuoteQuery(queryArgs ?? skipToken, { amountSpecified?: CurrencyAmount<Currency>,
pollingInterval: ms`10s`, otherCurrency?: Currency
refetchOnFocus: true, ): { state: V3TradeState; trade: Trade<Currency, Currency, TTradeType> | null } {
}) const [currencyIn, currencyOut]: [Currency | undefined, Currency | undefined] = useMemo(
() =>
const quoteResult = useFreshData(data, Number(data?.blockNumber) || 0) tradeType === TradeType.EXACT_INPUT
? [amountSpecified?.currency, otherCurrency]
const routes = useMemo( : [otherCurrency, amountSpecified?.currency],
() => computeRoutes(amountIn?.currency, currencyOut, quoteResult), [amountSpecified, otherCurrency, tradeType]
[amountIn, currencyOut, quoteResult]
) )
return useMemo(() => {
if (!amountIn || !currencyOut) {
return {
state: V3TradeState.INVALID,
trade: null,
}
}
if (isLoading && !quoteResult) {
// only on first hook render
return {
state: V3TradeState.LOADING,
trade: null,
}
}
const amountOut =
currencyOut && quoteResult ? CurrencyAmount.fromRawAmount(currencyOut, quoteResult.quote) : undefined
if (isError || !amountOut || !routes || routes.length === 0 || !queryArgs) {
return {
state: V3TradeState.NO_ROUTE_FOUND,
trade: null,
}
}
const trade = Trade.createUncheckedTradeWithMultipleRoutes<Currency, Currency, TradeType.EXACT_INPUT>({
routes,
tradeType: TradeType.EXACT_INPUT,
})
const gasPriceWei = BigNumber.from(quoteResult?.gasPriceWei)
const gasUseEstimate = BigNumber.from(quoteResult?.gasUseEstimate)
return {
// always return VALID regardless of isFetching status
state: V3TradeState.VALID,
trade,
gasPriceWei,
gasUseEstimate,
}
}, [amountIn, currencyOut, isLoading, quoteResult, isError, routes, queryArgs])
}
export function useRoutingAPITradeExactOut(currencyIn?: Currency, amountOut?: CurrencyAmount<Currency>) {
const queryArgs = useRoutingAPIArguments({ const queryArgs = useRoutingAPIArguments({
tokenIn: currencyIn, tokenIn: currencyIn,
tokenOut: amountOut?.currency, tokenOut: currencyOut,
amount: amountOut, amount: amountSpecified,
type: 'exactOut', tradeType,
}) })
const { isLoading, isError, data } = useGetQuoteQuery(queryArgs ?? skipToken, { const { isLoading, isError, data } = useGetQuoteQuery(queryArgs ?? skipToken, {
...@@ -129,12 +82,12 @@ export function useRoutingAPITradeExactOut(currencyIn?: Currency, amountOut?: Cu ...@@ -129,12 +82,12 @@ export function useRoutingAPITradeExactOut(currencyIn?: Currency, amountOut?: Cu
const quoteResult = useFreshData(data, Number(data?.blockNumber) || 0) const quoteResult = useFreshData(data, Number(data?.blockNumber) || 0)
const routes = useMemo( const routes = useMemo(
() => computeRoutes(currencyIn, amountOut?.currency, quoteResult), () => computeRoutes(currencyIn, currencyOut, quoteResult),
[amountOut, currencyIn, quoteResult] [currencyIn, currencyOut, quoteResult]
) )
return useMemo(() => { return useMemo(() => {
if (!amountOut || !currencyIn) { if (!currencyIn || !currencyOut) {
return { return {
state: V3TradeState.INVALID, state: V3TradeState.INVALID,
trade: null, trade: null,
...@@ -142,34 +95,38 @@ export function useRoutingAPITradeExactOut(currencyIn?: Currency, amountOut?: Cu ...@@ -142,34 +95,38 @@ export function useRoutingAPITradeExactOut(currencyIn?: Currency, amountOut?: Cu
} }
if (isLoading && !quoteResult) { if (isLoading && !quoteResult) {
// only on first hook render
return { return {
state: V3TradeState.LOADING, state: V3TradeState.LOADING,
trade: null, trade: null,
} }
} }
const amountIn = currencyIn && quoteResult ? CurrencyAmount.fromRawAmount(currencyIn, quoteResult.quote) : undefined const otherAmount =
tradeType === TradeType.EXACT_INPUT
? currencyOut && quoteResult
? CurrencyAmount.fromRawAmount(currencyOut, quoteResult.quote)
: undefined
: currencyIn && quoteResult
? CurrencyAmount.fromRawAmount(currencyIn, quoteResult.quote)
: undefined
if (isError || !amountIn || !routes || routes.length === 0 || !queryArgs) { if (isError || !otherAmount || !routes || routes.length === 0 || !queryArgs) {
return { return {
state: V3TradeState.NO_ROUTE_FOUND, state: V3TradeState.NO_ROUTE_FOUND,
trade: null, trade: null,
} }
} }
const trade = Trade.createUncheckedTradeWithMultipleRoutes<Currency, Currency, TradeType.EXACT_OUTPUT>({ const trade = Trade.createUncheckedTradeWithMultipleRoutes<Currency, Currency, TTradeType>({
routes, routes,
tradeType: TradeType.EXACT_OUTPUT, tradeType,
}) })
const gasPriceWei = BigNumber.from(quoteResult?.gasPriceWei)
const gasUseEstimate = BigNumber.from(quoteResult?.gasUseEstimate)
return { return {
// always return VALID regardless of isFetching status
state: V3TradeState.VALID, state: V3TradeState.VALID,
trade, trade,
gasPriceWei,
gasUseEstimate,
} }
}, [amountOut, currencyIn, isLoading, quoteResult, isError, routes, queryArgs]) }, [currencyIn, currencyOut, isLoading, quoteResult, isError, routes, queryArgs, tradeType])
} }
...@@ -4,7 +4,8 @@ import { Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core' ...@@ -4,7 +4,8 @@ import { Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core'
import { Trade as V2Trade } from '@uniswap/v2-sdk' import { Trade as V2Trade } from '@uniswap/v2-sdk'
import { Trade as V3Trade } from '@uniswap/v3-sdk' import { Trade as V3Trade } from '@uniswap/v3-sdk'
import { TWO_PERCENT } from 'constants/misc' import { TWO_PERCENT } from 'constants/misc'
import { useV3TradeExactIn, useV3TradeExactOut } from 'hooks/useCombinedV3Trade' import { useBestV2Trade } from 'hooks/useBestV2Trade'
import { useBestV3Trade } from 'hooks/useBestV3Trade'
import JSBI from 'jsbi' import JSBI from 'jsbi'
import { ParsedQs } from 'qs' import { ParsedQs } from 'qs'
import { useCallback, useEffect, useMemo, useState } from 'react' import { useCallback, useEffect, useMemo, useState } from 'react'
...@@ -16,7 +17,6 @@ import useENS from '../../hooks/useENS' ...@@ -16,7 +17,6 @@ import useENS from '../../hooks/useENS'
import useParsedQueryString from '../../hooks/useParsedQueryString' import useParsedQueryString from '../../hooks/useParsedQueryString'
import useSwapSlippageTolerance from '../../hooks/useSwapSlippageTolerance' import useSwapSlippageTolerance from '../../hooks/useSwapSlippageTolerance'
import { Version } from '../../hooks/useToggledVersion' import { Version } from '../../hooks/useToggledVersion'
import { useV2TradeExactIn, useV2TradeExactOut } from '../../hooks/useV2Trade'
import { useActiveWeb3React } from '../../hooks/web3' import { useActiveWeb3React } from '../../hooks/web3'
import { isAddress } from '../../utils' import { isAddress } from '../../utils'
import { AppState } from '../index' import { AppState } from '../index'
...@@ -157,27 +157,17 @@ export function useDerivedSwapInfo(toggledVersion: Version | undefined): { ...@@ -157,27 +157,17 @@ export function useDerivedSwapInfo(toggledVersion: Version | undefined): {
// get v2 and v3 quotes // get v2 and v3 quotes
// skip if other version is toggled // skip if other version is toggled
const bestV2TradeExactIn = useV2TradeExactIn( const v2Trade = useBestV2Trade(
toggledVersion !== Version.v3 && isExactIn ? parsedAmount : undefined, isExactIn ? TradeType.EXACT_INPUT : TradeType.EXACT_OUTPUT,
outputCurrency ?? undefined parsedAmount,
) (isExactIn ? outputCurrency : inputCurrency) ?? undefined
const bestV2TradeExactOut = useV2TradeExactOut(
inputCurrency ?? undefined,
toggledVersion !== Version.v3 && !isExactIn ? parsedAmount : undefined
)
const bestV3TradeExactIn = useV3TradeExactIn(
toggledVersion !== Version.v2 && isExactIn ? parsedAmount : undefined,
outputCurrency ?? undefined
) )
const bestV3TradeExactOut = useV3TradeExactOut( const v3Trade = useBestV3Trade(
inputCurrency ?? undefined, isExactIn ? TradeType.EXACT_INPUT : TradeType.EXACT_OUTPUT,
toggledVersion !== Version.v2 && !isExactIn ? parsedAmount : undefined toggledVersion !== Version.v2 ? parsedAmount : undefined,
(isExactIn ? outputCurrency : inputCurrency) ?? undefined
) )
const v2Trade = isExactIn ? bestV2TradeExactIn : bestV2TradeExactOut
const v3Trade = isExactIn ? bestV3TradeExactIn : bestV3TradeExactOut
const isV2TradeBetter = useMemo(() => { const isV2TradeBetter = useMemo(() => {
try { try {
// avoids comparing trades when V3Trade is not in a ready state. // avoids comparing trades when V3Trade is not in a ready state.
...@@ -221,11 +211,7 @@ export function useDerivedSwapInfo(toggledVersion: Version | undefined): { ...@@ -221,11 +211,7 @@ export function useDerivedSwapInfo(toggledVersion: Version | undefined): {
if (!to || !formattedTo) { if (!to || !formattedTo) {
inputError = inputError ?? t`Enter a recipient` inputError = inputError ?? t`Enter a recipient`
} else { } else {
if ( if (BAD_RECIPIENT_ADDRESSES[formattedTo] || (v2Trade && involvesAddress(v2Trade, formattedTo))) {
BAD_RECIPIENT_ADDRESSES[formattedTo] ||
(bestV2TradeExactIn && involvesAddress(bestV2TradeExactIn, formattedTo)) ||
(bestV2TradeExactOut && involvesAddress(bestV2TradeExactOut, formattedTo))
) {
inputError = inputError ?? t`Invalid recipient` inputError = inputError ?? t`Invalid recipient`
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment