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[] {
const allPairs = useV2Pairs(allCurrencyCombinations)
// only pass along valid pairs, non-duplicated pairs
return useMemo(
() =>
Object.values(
allPairs
// filter out invalid pairs
.filter((result): result is [PairState.EXISTS, Pair] => Boolean(result[0] === PairState.EXISTS && result[1]))
// filter out duplicated pairs
.reduce<{ [pairAddress: string]: Pair }>((memo, [, curr]) => {
memo[curr.liquidityToken.address] = memo[curr.liquidityToken.address] ?? curr
return memo
}, {})
.map(([, pair]) => pair)
),
[allPairs]
)
......@@ -31,71 +26,61 @@ function useAllCommonPairs(currencyA?: Currency, currencyB?: Currency): Pair[] {
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(
currencyAmountIn?: CurrencyAmount<Currency>,
currencyOut?: Currency,
export function useBestV2Trade(
tradeType: TradeType.EXACT_INPUT | TradeType.EXACT_OUTPUT,
amountSpecified?: CurrencyAmount<Currency>,
otherCurrency?: Currency,
{ maxHops = MAX_HOPS } = {}
): Trade<Currency, Currency, TradeType.EXACT_INPUT> | null {
const allowedPairs = useAllCommonPairs(currencyAmountIn?.currency, currencyOut)
): Trade<Currency, Currency, TradeType.EXACT_INPUT | TradeType.EXACT_OUTPUT> | null {
const [currencyIn, currencyOut] = useMemo(
() =>
tradeType === TradeType.EXACT_INPUT
? [amountSpecified?.currency, otherCurrency]
: [otherCurrency, amountSpecified?.currency],
[tradeType, amountSpecified, otherCurrency]
)
const allowedPairs = useAllCommonPairs(currencyIn, currencyOut)
return useMemo(() => {
if (currencyAmountIn && currencyOut && allowedPairs.length > 0) {
if (amountSpecified && currencyIn && currencyOut && allowedPairs.length > 0) {
if (maxHops === 1) {
return (
Trade.bestTradeExactIn(allowedPairs, currencyAmountIn, currencyOut, { maxHops: 1, maxNumResults: 1 })[0] ??
null
)
}
// search through trades with varying hops, find best trade out of them
let bestTradeSoFar: Trade<Currency, Currency, TradeType.EXACT_INPUT> | null = 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
const options = { maxHops: 1, maxNumResults: 1 }
if (tradeType === TradeType.EXACT_INPUT) {
const amountIn = amountSpecified
return Trade.bestTradeExactIn(allowedPairs, amountIn, currencyOut, options)[0] ?? null
} else {
const amountOut = amountSpecified
return Trade.bestTradeExactOut(allowedPairs, currencyIn, amountOut, options)[0] ?? null
}
}
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
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++) {
const currentTrade =
Trade.bestTradeExactOut(allowedPairs, currencyIn, currencyAmountOut, { maxHops: i, maxNumResults: 1 })[0] ??
null
const options = { maxHops: i, maxNumResults: 1 }
let currentTrade: Trade<Currency, Currency, TradeType.EXACT_INPUT | TradeType.EXACT_OUTPUT> | 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)) {
bestTradeSoFar = currentTrade
}
}
return bestTradeSoFar
}
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 { Route, SwapQuoter, Trade } from '@uniswap/v3-sdk'
import { SupportedChainId } from 'constants/chains'
import { BigNumber } from 'ethers'
import JSBI from 'jsbi'
import { useMemo } from 'react'
import { V3TradeState } from 'state/routing/types'
import { useSingleContractWithCallData } from '../state/multicall/hooks'
......@@ -17,108 +17,31 @@ const QUOTE_GAS_OVERRIDES: { [chainId: number]: number } = {
const DEFAULT_GAS_QUOTE = 2_000_000
/**
* Returns the best v3 trade for a desired exact input swap
* @param amountIn the amount to swap in
* @param currencyOut the desired output currency
* Returns the best v3 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 useClientV3TradeExactIn(
amountIn?: CurrencyAmount<Currency>,
currencyOut?: Currency
): { state: V3TradeState; trade: Trade<Currency, Currency, TradeType.EXACT_INPUT> | null } {
const { routes, loading: routesLoading } = useAllV3Routes(amountIn?.currency, currencyOut)
const quoter = useV3Quoter()
const { chainId } = useActiveWeb3React()
const quotesResults = useSingleContractWithCallData(
quoter,
amountIn
? routes.map((route) => SwapQuoter.quoteCallParameters(route, amountIn, TradeType.EXACT_INPUT).calldata)
: [],
{
gasRequired: chainId ? QUOTE_GAS_OVERRIDES[chainId] ?? DEFAULT_GAS_QUOTE : undefined,
}
export function useClientSideV3Trade<TTradeType extends TradeType>(
tradeType: TTradeType,
amountSpecified?: CurrencyAmount<Currency>,
otherCurrency?: Currency
): { state: V3TradeState; trade: Trade<Currency, Currency, TTradeType> | null } {
const [currencyIn, currencyOut] = useMemo(
() =>
tradeType === TradeType.EXACT_INPUT
? [amountSpecified?.currency, otherCurrency]
: [otherCurrency, amountSpecified?.currency],
[tradeType, amountSpecified, otherCurrency]
)
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 { routes, loading: routesLoading } = useAllV3Routes(currencyIn, currencyOut)
const quoter = useV3Quoter()
const { chainId } = useActiveWeb3React()
const quotesResults = useSingleContractWithCallData(
quoter,
amountOut
? routes.map((route) => SwapQuoter.quoteCallParameters(route, amountOut, TradeType.EXACT_OUTPUT).calldata)
amountSpecified
? routes.map((route) => SwapQuoter.quoteCallParameters(route, amountSpecified, tradeType).calldata)
: [],
{
gasRequired: chainId ? QUOTE_GAS_OVERRIDES[chainId] ?? DEFAULT_GAS_QUOTE : undefined,
......@@ -127,11 +50,14 @@ export function useClientSideV3TradeExactOut(
return useMemo(() => {
if (
!amountOut ||
!amountSpecified ||
!currencyIn ||
!currencyOut ||
quotesResults.some(({ valid }) => !valid) ||
// skip when tokens are the same
amountOut.currency.equals(currencyIn)
(tradeType === TradeType.EXACT_INPUT
? amountSpecified.currency.equals(currencyOut)
: amountSpecified.currency.equals(currencyIn))
) {
return {
state: V3TradeState.INVALID,
......@@ -146,19 +72,36 @@ export function useClientSideV3TradeExactOut(
}
}
const { bestRoute, amountIn } = quotesResults.reduce(
(currentBest: { bestRoute: Route<Currency, Currency> | null; amountIn: BigNumber | null }, { result }, i) => {
const { bestRoute, amountIn, amountOut } = quotesResults.reduce(
(
currentBest: {
bestRoute: Route<Currency, Currency> | null
amountIn: CurrencyAmount<Currency> | null
amountOut: CurrencyAmount<Currency> | null
},
{ result },
i
) => {
if (!result) return currentBest
if (currentBest.amountIn === null) {
return {
bestRoute: routes[i],
amountIn: result.amountIn,
// overwrite the current best if it's not defined or if this route is better
if (tradeType === TradeType.EXACT_INPUT) {
const amountOut = CurrencyAmount.fromRawAmount(currencyOut, result.amountOut.toString())
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)) {
return {
bestRoute: routes[i],
amountIn: result.amountIn,
} else {
const amountIn = CurrencyAmount.fromRawAmount(currencyIn, result.amountIn.toString())
if (currentBest.amountIn === null || JSBI.greaterThan(currentBest.amountIn.quotient, amountIn.quotient)) {
return {
bestRoute: routes[i],
amountIn,
amountOut: amountSpecified,
}
}
}
......@@ -167,10 +110,11 @@ export function useClientSideV3TradeExactOut(
{
bestRoute: null,
amountIn: null,
amountOut: null,
}
)
if (!bestRoute || !amountIn) {
if (!bestRoute || !amountIn || !amountOut) {
return {
state: V3TradeState.NO_ROUTE_FOUND,
trade: null,
......@@ -181,10 +125,10 @@ export function useClientSideV3TradeExactOut(
state: V3TradeState.VALID,
trade: Trade.createUncheckedTrade({
route: bestRoute,
tradeType: TradeType.EXACT_OUTPUT,
inputAmount: CurrencyAmount.fromRawAmount(currencyIn, amountIn.toString()),
tradeType,
inputAmount: amountIn,
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 { SupportedChainId } from '../constants/chains'
import { DAI_OPTIMISM, USDC, USDC_ARBITRUM } from '../constants/tokens'
import { useV2TradeExactOut } from './useV2Trade'
import { useClientSideV3TradeExactOut } from './useClientSideV3Trade'
import { useBestV2Trade } from './useBestV2Trade'
import { useClientSideV3Trade } from './useClientSideV3Trade'
import { useActiveWeb3React } from './web3'
// Stablecoin amounts used when calculating spot price for a given currency.
......@@ -24,10 +24,10 @@ export default function useUSDCPrice(currency?: Currency): Price<Currency, Token
const amountOut = chainId ? STABLECOIN_AMOUNT_OUT[chainId] : undefined
const stablecoin = amountOut?.currency
const v2USDCTrade = useV2TradeExactOut(currency, amountOut, {
const v2USDCTrade = useBestV2Trade(TradeType.EXACT_OUTPUT, amountOut, currency, {
maxHops: 2,
})
const v3USDCTrade = useClientSideV3TradeExactOut(currency, amountOut)
const v3USDCTrade = useClientSideV3Trade(TradeType.EXACT_OUTPUT, amountOut, currency)
return useMemo(() => {
if (!currency || !stablecoin) {
......
import { skipToken } from '@reduxjs/toolkit/query/react'
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
import { Trade } from '@uniswap/v3-sdk'
import { BigNumber } from 'ethers'
import ms from 'ms.macro'
import { useMemo } from 'react'
import { useBlockNumber } from 'state/application/hooks'
import { useGetQuoteQuery } from 'state/routing/slice'
import { V3TradeState } from './types'
import { computeRoutes } from './computeRoutes'
import { V3TradeState } from './types'
function useFreshData<T>(data: T, dataBlockNumber: number, maxBlockAge = 10): T | undefined {
const localBlockNumber = useBlockNumber()
......@@ -28,12 +27,12 @@ function useRoutingAPIArguments({
tokenIn,
tokenOut,
amount,
type,
tradeType,
}: {
tokenIn: Currency | undefined
tokenOut: Currency | undefined
amount: CurrencyAmount<Currency> | undefined
type: 'exactIn' | 'exactOut'
tradeType: TradeType
}) {
if (!tokenIn || !tokenOut || !amount || tokenIn.equals(tokenOut)) {
return undefined
......@@ -45,80 +44,34 @@ function useRoutingAPIArguments({
tokenOutAddress: tokenOut.wrapped.address,
tokenOutChainId: tokenOut.chainId,
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({
tokenIn: amountIn?.currency,
tokenOut: currencyOut,
amount: amountIn,
type: 'exactIn',
})
const { isLoading, isError, data } = useGetQuoteQuery(queryArgs ?? skipToken, {
pollingInterval: ms`10s`,
refetchOnFocus: true,
})
const quoteResult = useFreshData(data, Number(data?.blockNumber) || 0)
const routes = useMemo(
() => computeRoutes(amountIn?.currency, currencyOut, quoteResult),
[amountIn, currencyOut, quoteResult]
/**
* Returns the best v3 trade by invoking the routing api
* @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 useRoutingAPITrade<TTradeType extends TradeType>(
tradeType: TTradeType,
amountSpecified?: CurrencyAmount<Currency>,
otherCurrency?: Currency
): { state: V3TradeState; trade: Trade<Currency, Currency, TTradeType> | null } {
const [currencyIn, currencyOut]: [Currency | undefined, Currency | undefined] = useMemo(
() =>
tradeType === TradeType.EXACT_INPUT
? [amountSpecified?.currency, otherCurrency]
: [otherCurrency, amountSpecified?.currency],
[amountSpecified, otherCurrency, tradeType]
)
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({
tokenIn: currencyIn,
tokenOut: amountOut?.currency,
amount: amountOut,
type: 'exactOut',
tokenOut: currencyOut,
amount: amountSpecified,
tradeType,
})
const { isLoading, isError, data } = useGetQuoteQuery(queryArgs ?? skipToken, {
......@@ -129,12 +82,12 @@ export function useRoutingAPITradeExactOut(currencyIn?: Currency, amountOut?: Cu
const quoteResult = useFreshData(data, Number(data?.blockNumber) || 0)
const routes = useMemo(
() => computeRoutes(currencyIn, amountOut?.currency, quoteResult),
[amountOut, currencyIn, quoteResult]
() => computeRoutes(currencyIn, currencyOut, quoteResult),
[currencyIn, currencyOut, quoteResult]
)
return useMemo(() => {
if (!amountOut || !currencyIn) {
if (!currencyIn || !currencyOut) {
return {
state: V3TradeState.INVALID,
trade: null,
......@@ -142,34 +95,38 @@ export function useRoutingAPITradeExactOut(currencyIn?: Currency, amountOut?: Cu
}
if (isLoading && !quoteResult) {
// only on first hook render
return {
state: V3TradeState.LOADING,
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 {
state: V3TradeState.NO_ROUTE_FOUND,
trade: null,
}
}
const trade = Trade.createUncheckedTradeWithMultipleRoutes<Currency, Currency, TradeType.EXACT_OUTPUT>({
const trade = Trade.createUncheckedTradeWithMultipleRoutes<Currency, Currency, TTradeType>({
routes,
tradeType: TradeType.EXACT_OUTPUT,
tradeType,
})
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,
}
}, [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'
import { Trade as V2Trade } from '@uniswap/v2-sdk'
import { Trade as V3Trade } from '@uniswap/v3-sdk'
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 { ParsedQs } from 'qs'
import { useCallback, useEffect, useMemo, useState } from 'react'
......@@ -16,7 +17,6 @@ import useENS from '../../hooks/useENS'
import useParsedQueryString from '../../hooks/useParsedQueryString'
import useSwapSlippageTolerance from '../../hooks/useSwapSlippageTolerance'
import { Version } from '../../hooks/useToggledVersion'
import { useV2TradeExactIn, useV2TradeExactOut } from '../../hooks/useV2Trade'
import { useActiveWeb3React } from '../../hooks/web3'
import { isAddress } from '../../utils'
import { AppState } from '../index'
......@@ -157,27 +157,17 @@ export function useDerivedSwapInfo(toggledVersion: Version | undefined): {
// get v2 and v3 quotes
// skip if other version is toggled
const bestV2TradeExactIn = useV2TradeExactIn(
toggledVersion !== Version.v3 && isExactIn ? parsedAmount : undefined,
outputCurrency ?? undefined
)
const bestV2TradeExactOut = useV2TradeExactOut(
inputCurrency ?? undefined,
toggledVersion !== Version.v3 && !isExactIn ? parsedAmount : undefined
)
const bestV3TradeExactIn = useV3TradeExactIn(
toggledVersion !== Version.v2 && isExactIn ? parsedAmount : undefined,
outputCurrency ?? undefined
const v2Trade = useBestV2Trade(
isExactIn ? TradeType.EXACT_INPUT : TradeType.EXACT_OUTPUT,
parsedAmount,
(isExactIn ? outputCurrency : inputCurrency) ?? undefined
)
const bestV3TradeExactOut = useV3TradeExactOut(
inputCurrency ?? undefined,
toggledVersion !== Version.v2 && !isExactIn ? parsedAmount : undefined
const v3Trade = useBestV3Trade(
isExactIn ? TradeType.EXACT_INPUT : TradeType.EXACT_OUTPUT,
toggledVersion !== Version.v2 ? parsedAmount : undefined,
(isExactIn ? outputCurrency : inputCurrency) ?? undefined
)
const v2Trade = isExactIn ? bestV2TradeExactIn : bestV2TradeExactOut
const v3Trade = isExactIn ? bestV3TradeExactIn : bestV3TradeExactOut
const isV2TradeBetter = useMemo(() => {
try {
// avoids comparing trades when V3Trade is not in a ready state.
......@@ -221,11 +211,7 @@ export function useDerivedSwapInfo(toggledVersion: Version | undefined): {
if (!to || !formattedTo) {
inputError = inputError ?? t`Enter a recipient`
} else {
if (
BAD_RECIPIENT_ADDRESSES[formattedTo] ||
(bestV2TradeExactIn && involvesAddress(bestV2TradeExactIn, formattedTo)) ||
(bestV2TradeExactOut && involvesAddress(bestV2TradeExactOut, formattedTo))
) {
if (BAD_RECIPIENT_ADDRESSES[formattedTo] || (v2Trade && involvesAddress(v2Trade, formattedTo))) {
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