feat: create use best trade hook for widgets (#3226)

* create use best trade hook for widgets

* update comment in hook file

* refactor loading state conditional

* update logic in use best trade

* clean code in best trade hook
This commit is contained in:
Ian Lapham 2022-02-04 18:38:27 -05:00 committed by GitHub
parent 1c278d5012
commit 2aa1e40481
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 108 additions and 2 deletions

@ -0,0 +1,106 @@
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
import { useClientSideV3Trade } from 'hooks/useClientSideV3Trade'
import useDebounce from 'hooks/useDebounce'
import { useMemo } from 'react'
import { InterfaceTrade, TradeState } from 'state/routing/types'
import useClientSideSmartOrderRouterTrade from '../routing/useClientSideSmartOrderRouterTrade'
/**
* Returns the currency amount from independent field, currency from independent field,
* and currency from dependent field.
*/
function getTradeInputs(
trade: InterfaceTrade<Currency, Currency, TradeType> | undefined,
tradeType: TradeType
): [CurrencyAmount<Currency> | undefined, Currency | undefined, Currency | undefined] {
if (trade) {
if (tradeType === TradeType.EXACT_INPUT) {
return [trade.inputAmount, trade.inputAmount.currency, trade.outputAmount.currency]
}
if (tradeType === TradeType.EXACT_OUTPUT) {
return [trade.outputAmount, trade.outputAmount.currency, trade.inputAmount.currency]
}
}
return [undefined, undefined, undefined]
}
interface TradeDebouncingParams {
amounts: [CurrencyAmount<Currency> | undefined, CurrencyAmount<Currency> | undefined]
indepdenentCurrencies: [Currency | undefined, Currency | undefined]
dependentCurrencies: [Currency | undefined, Currency | undefined]
}
/**
* Returns wether debounced values are stale compared to latest values from trade.
*/
function isTradeDebouncing({ amounts, indepdenentCurrencies, dependentCurrencies }: TradeDebouncingParams): boolean {
// Ensure that amount from user input matches latest trade.
const amountsMatch = amounts[0] && amounts[1]?.equalTo(amounts[0])
// Ensure active swap currencies match latest trade.
const currenciesMatch =
indepdenentCurrencies[0] &&
indepdenentCurrencies[1]?.equals(indepdenentCurrencies[0]) &&
dependentCurrencies[0] &&
dependentCurrencies[1]?.equals(dependentCurrencies[0])
return !amountsMatch || !currenciesMatch
}
/**
* Returns the best v2+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 useBestTrade(
tradeType: TradeType,
amountSpecified?: CurrencyAmount<Currency>,
otherCurrency?: Currency
): {
state: TradeState
trade: InterfaceTrade<Currency, Currency, TradeType> | undefined
} {
// Debounce is used to prevent excessive requests to SOR, as it is data intensive.
// This helps provide a "syncing" state the UI can reference for loading animations.
const [debouncedAmount, debouncedOtherCurrency] = useDebounce(
useMemo(() => [amountSpecified, otherCurrency], [amountSpecified, otherCurrency]),
200
)
const clientSORTrade = useClientSideSmartOrderRouterTrade(tradeType, debouncedAmount, debouncedOtherCurrency)
const [amountFromLatestTrade, currencyFromTrade, otherCurrencyFromTrade] = getTradeInputs(
clientSORTrade.trade,
tradeType
)
const debouncing =
(amountSpecified && debouncedAmount && amountSpecified !== debouncedAmount) ||
(debouncedOtherCurrency && otherCurrency && debouncedOtherCurrency !== otherCurrency)
const syncing = isTradeDebouncing({
amounts: [amountFromLatestTrade, debouncedAmount],
indepdenentCurrencies: [currencyFromTrade, debouncedOtherCurrency],
dependentCurrencies: [otherCurrencyFromTrade, otherCurrency],
})
const useFallback = !syncing && clientSORTrade.state === TradeState.NO_ROUTE_FOUND
// Use a simple client side logic as backup if SOR is not available.
const fallbackTrade = useClientSideV3Trade(
tradeType,
useFallback ? debouncedAmount : undefined,
useFallback ? debouncedOtherCurrency : undefined
)
return useMemo(
() => ({
...(useFallback ? fallbackTrade : clientSORTrade),
...(syncing ? { state: TradeState.SYNCING } : {}),
...(debouncing ? { state: TradeState.LOADING } : {}),
}),
[debouncing, fallbackTrade, syncing, clientSORTrade, useFallback]
)
}

@ -12,8 +12,8 @@ import { ReactNode, useEffect, useMemo } from 'react'
import { InterfaceTrade, TradeState } from 'state/routing/types' import { InterfaceTrade, TradeState } from 'state/routing/types'
import { isAddress } from '../../../utils' import { isAddress } from '../../../utils'
import useClientSideSmartOrderRouterTrade from '../routing/useClientSideSmartOrderRouterTrade'
import useActiveWeb3React from '../useActiveWeb3React' import useActiveWeb3React from '../useActiveWeb3React'
import { useBestTrade } from './useBestTrade'
interface SwapInfo { interface SwapInfo {
currencies: { [field in Field]?: Currency } currencies: { [field in Field]?: Currency }
@ -60,7 +60,7 @@ function useComputeSwapInfo(): SwapInfo {
) )
//@TODO(ianlapham): this would eventually be replaced with routing api logic. //@TODO(ianlapham): this would eventually be replaced with routing api logic.
const trade = useClientSideSmartOrderRouterTrade( const trade = useBestTrade(
isExactIn ? TradeType.EXACT_INPUT : TradeType.EXACT_OUTPUT, isExactIn ? TradeType.EXACT_INPUT : TradeType.EXACT_OUTPUT,
parsedAmount, parsedAmount,
(isExactIn ? outputCurrency : inputCurrency) ?? undefined (isExactIn ? outputCurrency : inputCurrency) ?? undefined