Commit 05b2711a authored by Ian Lapham's avatar Ian Lapham Committed by GitHub

feat: update widget with client side SOR (#3210)

* start SOR by creating custom widget hook

* update best trade hook to use SOR in widget

* update organization for client side SOR logic

* fix auto router chain id import

* remove dependency on react GA for widget

* update dependencies for SOr

* remove new useBestTrade.ts

* update loading logic for fetching hook

* update dependencies with import from ethersproject

* update import version

* add try catch on SOR usage

* code cleanup, nit fixes
parent d0607822
......@@ -2,11 +2,11 @@
import { t, Trans } from '@lingui/macro'
import { Percent } from '@uniswap/sdk-core'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { AUTO_ROUTER_SUPPORTED_CHAINS } from 'lib/hooks/routing/clientSideSmartOrderRouter/constants'
import { useContext, useRef, useState } from 'react'
import { Settings, X } from 'react-feather'
import ReactGA from 'react-ga'
import { Text } from 'rebass'
import { AUTO_ROUTER_SUPPORTED_CHAINS } from 'state/routing/clientSideSmartOrderRouter/constants'
import styled, { ThemeContext } from 'styled-components/macro'
import { useOnClickOutside } from '../../hooks/useOnClickOutside'
......
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { AUTO_ROUTER_SUPPORTED_CHAINS } from 'state/routing/clientSideSmartOrderRouter/constants'
import { AUTO_ROUTER_SUPPORTED_CHAINS } from 'lib/hooks/routing/clientSideSmartOrderRouter/constants'
export default function useAutoRouterSupported(): boolean {
const { chainId } = useActiveWeb3React()
......
import { AlphaRouterParams, IMetric, MetricLoggerUnit, setGlobalMetric } from '@uniswap/smart-order-router'
import { JsonRpcProvider } from '@ethersproject/providers'
import { AlphaRouterParams } from '@uniswap/smart-order-router'
import { INFURA_NETWORK_URLS } from 'constants/chainInfo'
import { SupportedChainId } from 'constants/chains'
import { providers } from 'ethers/lib/ethers'
import ReactGA from 'react-ga'
import { AUTO_ROUTER_SUPPORTED_CHAINS } from './constants'
......@@ -14,7 +13,7 @@ export type Dependencies = {
export function buildDependencies(): Dependencies {
const dependenciesByChain: Dependencies = {}
for (const chainId of AUTO_ROUTER_SUPPORTED_CHAINS) {
const provider = new providers.JsonRpcProvider(INFURA_NETWORK_URLS[chainId])
const provider = new JsonRpcProvider(INFURA_NETWORK_URLS[chainId])
dependenciesByChain[chainId] = {
chainId,
......@@ -24,20 +23,3 @@ export function buildDependencies(): Dependencies {
return dependenciesByChain
}
class GAMetric extends IMetric {
putDimensions() {
return
}
putMetric(key: string, value: number, unit?: MetricLoggerUnit) {
ReactGA.timing({
category: 'Routing API',
variable: `${key} | ${unit}`,
value,
label: 'client',
})
}
}
setGlobalMetric(new GAMetric())
import { Protocol } from '@uniswap/router-sdk'
import { BigintIsh, CurrencyAmount, Token, TradeType } from '@uniswap/sdk-core'
import { AlphaRouter, AlphaRouterConfig, ChainId } from '@uniswap/smart-order-router'
import JSBI from 'jsbi'
......@@ -8,7 +9,7 @@ import { buildDependencies } from './dependencies'
const routerParamsByChain = buildDependencies()
export async function getQuote(
async function getQuote(
{
type,
chainId,
......@@ -50,3 +51,52 @@ export async function getQuote(
return { data: transformSwapRouteToGetQuoteResult(type, amount, swapRoute) }
}
const protocols: Protocol[] = [Protocol.V2, Protocol.V3]
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({
tokenInAddress,
tokenInChainId,
tokenInDecimals,
tokenInSymbol,
tokenOutAddress,
tokenOutChainId,
tokenOutDecimals,
tokenOutSymbol,
amount,
type,
}: QuoteArguments) {
return getQuote(
{
type,
chainId: tokenInChainId,
tokenIn: {
address: tokenInAddress,
chainId: tokenInChainId,
decimals: tokenInDecimals,
symbol: tokenInSymbol,
},
tokenOut: {
address: tokenOutAddress,
chainId: tokenOutChainId,
decimals: tokenOutDecimals,
symbol: tokenOutSymbol,
},
amount,
},
{ protocols }
)
}
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
import { useStablecoinAmountFromFiatValue } from 'hooks/useUSDCPrice'
import { useEffect, useMemo, useState } from 'react'
import { GetQuoteResult, InterfaceTrade, TradeState } from 'state/routing/types'
import { computeRoutes, transformRoutesToTrade } from 'state/routing/utils'
import { getClientSideQuote } from './clientSideSmartOrderRouter'
import { useRoutingAPIArguments } from './useRoutingAPIArguments'
export default function useClientSideSmartOrderRouterTrade<TTradeType extends TradeType>(
tradeType: TTradeType,
amountSpecified?: CurrencyAmount<Currency>,
otherCurrency?: Currency
): {
state: TradeState
trade: InterfaceTrade<Currency, Currency, TTradeType> | undefined
} {
const [currencyIn, currencyOut]: [Currency | undefined, Currency | undefined] = useMemo(
() =>
tradeType === TradeType.EXACT_INPUT
? [amountSpecified?.currency, otherCurrency]
: [otherCurrency, amountSpecified?.currency],
[amountSpecified, otherCurrency, tradeType]
)
const queryArgs = useRoutingAPIArguments({
tokenIn: currencyIn,
tokenOut: currencyOut,
amount: amountSpecified,
tradeType,
useClientSideRouter: true,
})
const [loading, setLoading] = useState(false)
const [{ quoteResult, error }, setFetchedResult] = useState<{
quoteResult: GetQuoteResult | undefined
error: unknown
}>({
quoteResult: undefined,
error: undefined,
})
// When arguments update, make a new call to SOR for updated quote
useEffect(() => {
setLoading(true)
fetchQuote()
async function fetchQuote() {
try {
if (queryArgs) {
const result = await getClientSideQuote(queryArgs)
setFetchedResult({
quoteResult: result.data,
error: result.error,
})
}
} catch (e) {
setFetchedResult({
quoteResult: undefined,
error: true,
})
} finally {
setLoading(false)
}
}
}, [queryArgs])
const route = useMemo(
() => 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
return useMemo(() => {
if (!currencyIn || !currencyOut) {
return {
state: TradeState.INVALID,
trade: undefined,
}
}
if (loading && !quoteResult) {
// only on first hook render
return {
state: TradeState.LOADING,
trade: undefined,
}
}
let otherAmount = undefined
if (tradeType === TradeType.EXACT_INPUT && currencyOut && quoteResult) {
otherAmount = CurrencyAmount.fromRawAmount(currencyOut, quoteResult.quote)
}
if (tradeType === TradeType.EXACT_OUTPUT && currencyIn && quoteResult) {
otherAmount = CurrencyAmount.fromRawAmount(currencyIn, quoteResult.quote)
}
if (error || !otherAmount || !route || route.length === 0 || !queryArgs) {
return {
state: TradeState.NO_ROUTE_FOUND,
trade: undefined,
}
}
try {
const trade = transformRoutesToTrade(route, tradeType, gasUseEstimateUSD)
return {
// always return VALID regardless of isFetching status
state: TradeState.VALID,
trade,
}
} catch (e) {
console.debug('transformRoutesToTrade failed: ', e)
return { state: TradeState.INVALID, trade: undefined }
}
}, [currencyIn, currencyOut, loading, quoteResult, tradeType, error, route, queryArgs, gasUseEstimateUSD])
}
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
import { useMemo } from 'react'
/**
* Returns query arguments for the Routing API query or undefined if the
* query should be skipped. Input arguments do not need to be memoized, as they will
* be destructured.
*/
export function useRoutingAPIArguments({
tokenIn,
tokenOut,
amount,
tradeType,
useClientSideRouter,
}: {
tokenIn: Currency | undefined
tokenOut: Currency | undefined
amount: CurrencyAmount<Currency> | undefined
tradeType: TradeType
useClientSideRouter: boolean
}) {
return useMemo(
() =>
!tokenIn || !tokenOut || !amount || tokenIn.equals(tokenOut)
? undefined
: {
amount: amount.quotient.toString(),
tokenInAddress: tokenIn.wrapped.address,
tokenInChainId: tokenIn.wrapped.chainId,
tokenInDecimals: tokenIn.wrapped.decimals,
tokenInSymbol: tokenIn.wrapped.symbol,
tokenOutAddress: tokenOut.wrapped.address,
tokenOutChainId: tokenOut.wrapped.chainId,
tokenOutDecimals: tokenOut.wrapped.decimals,
tokenOutSymbol: tokenOut.wrapped.symbol,
useClientSideRouter,
type: (tradeType === TradeType.EXACT_INPUT ? 'exactIn' : 'exactOut') as 'exactIn' | 'exactOut',
},
[amount, tokenIn, tokenOut, tradeType, useClientSideRouter]
)
}
import { Trans } from '@lingui/macro'
import { Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core'
import useAutoSlippageTolerance from 'hooks/useAutoSlippageTolerance'
import { useClientSideV3Trade } from 'hooks/useClientSideV3Trade'
import { atom } from 'jotai'
import { useAtomValue, useUpdateAtom } from 'jotai/utils'
import { useCurrencyBalances } from 'lib/hooks/useCurrencyBalance'
......@@ -12,6 +11,7 @@ import { ReactNode, useEffect, useMemo } from 'react'
import { InterfaceTrade, TradeState } from 'state/routing/types'
import { isAddress } from '../../../utils'
import useClientSideSmartOrderRouterTrade from '../routing/useClientSideSmartOrderRouterTrade'
import useActiveWeb3React from '../useActiveWeb3React'
interface SwapInfo {
......@@ -55,10 +55,8 @@ function useComputeSwapInfo(): SwapInfo {
[inputCurrency, isExactIn, outputCurrency, amount]
)
/**
* @TODO (ianlapham): eventually need a strategy for routing API here
*/
const trade = useClientSideV3Trade(
//@TODO(ianlapham): this would eventually be replaced with routing api logic.
const trade = useClientSideSmartOrderRouterTrade(
isExactIn ? TradeType.EXACT_INPUT : TradeType.EXACT_OUTPUT,
parsedAmount,
(isExactIn ? outputCurrency : inputCurrency) ?? undefined
......
import { createApi, fetchBaseQuery, FetchBaseQueryError } from '@reduxjs/toolkit/query/react'
import { Protocol } from '@uniswap/router-sdk'
import { ChainId } from '@uniswap/smart-order-router'
import { getClientSideQuote } from 'lib/hooks/routing/clientSideSmartOrderRouter'
import ms from 'ms.macro'
import qs from 'qs'
......@@ -15,51 +16,6 @@ const DEFAULT_QUERY_PARAMS = {
// minSplits: '5',
}
async function getClientSideQuote({
tokenInAddress,
tokenInChainId,
tokenInDecimals,
tokenInSymbol,
tokenOutAddress,
tokenOutChainId,
tokenOutDecimals,
tokenOutSymbol,
amount,
type,
}: {
tokenInAddress: string
tokenInChainId: ChainId
tokenInDecimals: number
tokenInSymbol?: string
tokenOutAddress: string
tokenOutChainId: ChainId
tokenOutDecimals: number
tokenOutSymbol?: string
amount: string
type: 'exactIn' | 'exactOut'
}) {
return (await import('./clientSideSmartOrderRouter')).getQuote(
{
type,
chainId: tokenInChainId,
tokenIn: {
address: tokenInAddress,
chainId: tokenInChainId,
decimals: tokenInDecimals,
symbol: tokenInSymbol,
},
tokenOut: {
address: tokenOutAddress,
chainId: tokenOutChainId,
decimals: tokenOutDecimals,
symbol: tokenOutSymbol,
},
amount,
},
{ protocols }
)
}
export const routingApi = createApi({
reducerPath: 'routingApi',
baseQuery: fetchBaseQuery({
......
import { skipToken } from '@reduxjs/toolkit/query/react'
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
import { IMetric, MetricLoggerUnit, setGlobalMetric } from '@uniswap/smart-order-router'
import { useStablecoinAmountFromFiatValue } from 'hooks/useUSDCPrice'
import { useRoutingAPIArguments } from 'lib/hooks/routing/useRoutingAPIArguments'
import useBlockNumber from 'lib/hooks/useBlockNumber'
import ms from 'ms.macro'
import { useMemo } from 'react'
import ReactGA from 'react-ga'
import { useGetQuoteQuery } from 'state/routing/slice'
import { useClientSideRouter } from 'state/user/hooks'
......@@ -21,44 +24,6 @@ function useFreshData<T>(data: T, dataBlockNumber: number, maxBlockAge = 10): T
return data
}
/**
* Returns query arguments for the Routing API query or undefined if the
* query should be skipped.
*/
function useRoutingAPIArguments({
tokenIn,
tokenOut,
amount,
tradeType,
}: {
tokenIn: Currency | undefined
tokenOut: Currency | undefined
amount: CurrencyAmount<Currency> | undefined
tradeType: TradeType
}) {
const [clientSideRouter] = useClientSideRouter()
return useMemo(
() =>
!tokenIn || !tokenOut || !amount || tokenIn.equals(tokenOut)
? undefined
: {
amount: amount.quotient.toString(),
tokenInAddress: tokenIn.wrapped.address,
tokenInChainId: tokenIn.wrapped.chainId,
tokenInDecimals: tokenIn.wrapped.decimals,
tokenInSymbol: tokenIn.wrapped.symbol,
tokenOutAddress: tokenOut.wrapped.address,
tokenOutChainId: tokenOut.wrapped.chainId,
tokenOutDecimals: tokenOut.wrapped.decimals,
tokenOutSymbol: tokenOut.wrapped.symbol,
useClientSideRouter: clientSideRouter,
type: (tradeType === TradeType.EXACT_INPUT ? 'exactIn' : 'exactOut') as 'exactIn' | 'exactOut',
},
[amount, clientSideRouter, tokenIn, tokenOut, tradeType]
)
}
/**
* Returns the best trade by invoking the routing api or the smart order router on the client
* @param tradeType whether the swap is an exact in/out
......@@ -81,11 +46,14 @@ export function useRoutingAPITrade<TTradeType extends TradeType>(
[amountSpecified, otherCurrency, tradeType]
)
const [clientSideRouter] = useClientSideRouter()
const queryArgs = useRoutingAPIArguments({
tokenIn: currencyIn,
tokenOut: currencyOut,
amount: amountSpecified,
tradeType,
useClientSideRouter: clientSideRouter,
})
const { isLoading, isError, data } = useGetQuoteQuery(queryArgs ?? skipToken, {
......@@ -148,3 +116,21 @@ export function useRoutingAPITrade<TTradeType extends TradeType>(
}
}, [currencyIn, currencyOut, isLoading, quoteResult, tradeType, isError, route, queryArgs, gasUseEstimateUSD])
}
// only want to enable this when app hook called
class GAMetric extends IMetric {
putDimensions() {
return
}
putMetric(key: string, value: number, unit?: MetricLoggerUnit) {
ReactGA.timing({
category: 'Routing API',
variable: `${key} | ${unit}`,
value,
label: 'client',
})
}
}
setGlobalMetric(new GAMetric())
......@@ -1828,6 +1828,31 @@
dependencies:
"@ethersproject/logger" "^5.5.0"
"@ethersproject/providers@5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.4.0.tgz#1b9eeaf394d790a662ec66373d7219c82f4433f2"
integrity sha512-XRmI9syLnkNdLA8ikEeg0duxmwSWTTt9S+xabnTOyI51JPJyhQ0QUNT+wvmod218ebb7rLupHDPQ7UVe2/+Tjg==
dependencies:
"@ethersproject/abstract-provider" "^5.4.0"
"@ethersproject/abstract-signer" "^5.4.0"
"@ethersproject/address" "^5.4.0"
"@ethersproject/basex" "^5.4.0"
"@ethersproject/bignumber" "^5.4.0"
"@ethersproject/bytes" "^5.4.0"
"@ethersproject/constants" "^5.4.0"
"@ethersproject/hash" "^5.4.0"
"@ethersproject/logger" "^5.4.0"
"@ethersproject/networks" "^5.4.0"
"@ethersproject/properties" "^5.4.0"
"@ethersproject/random" "^5.4.0"
"@ethersproject/rlp" "^5.4.0"
"@ethersproject/sha2" "^5.4.0"
"@ethersproject/strings" "^5.4.0"
"@ethersproject/transactions" "^5.4.0"
"@ethersproject/web" "^5.4.0"
bech32 "1.1.4"
ws "7.4.6"
"@ethersproject/providers@5.4.5":
version "5.4.5"
resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.4.5.tgz#eb2ea2a743a8115f79604a8157233a3a2c832928"
......
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