Skip to content

Commit

Permalink
feat: update widget with client side SOR (#3210)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
ianlapham authored Feb 2, 2022
1 parent d060782 commit 05b2711
Show file tree
Hide file tree
Showing 12 changed files with 271 additions and 114 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@
"@uniswap/governance": "^1.0.2",
"@uniswap/liquidity-staker": "^1.0.2",
"@uniswap/merkle-distributor": "1.0.1",
"@uniswap/smart-order-router": "^2.5.10",
"@uniswap/v2-core": "1.0.0",
"@uniswap/v2-periphery": "^1.1.0-beta.0",
"@uniswap/v3-core": "1.0.0",
Expand Down Expand Up @@ -162,7 +161,7 @@
"@ethersproject/constants": "^5.4.0",
"@ethersproject/contracts": "^5.4.1",
"@ethersproject/hash": "^5.4.0",
"@ethersproject/providers": "^5.4.5",
"@ethersproject/providers": "5.4.0",
"@ethersproject/solidity": "^5.4.0",
"@ethersproject/strings": "^5.4.0",
"@ethersproject/units": "^5.4.0",
Expand All @@ -176,6 +175,7 @@
"@uniswap/redux-multicall": "^1.0.0",
"@uniswap/router-sdk": "^1.0.3",
"@uniswap/sdk-core": "^3.0.1",
"@uniswap/smart-order-router": "^2.5.10",
"@uniswap/token-lists": "^1.0.0-beta.27",
"@uniswap/v2-sdk": "^3.0.1",
"@uniswap/v3-sdk": "^3.7.1",
Expand Down
2 changes: 1 addition & 1 deletion src/components/Settings/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useAutoRouterSupported.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
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()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
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'

Expand All @@ -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,
Expand All @@ -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())
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
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'
Expand All @@ -8,7 +9,7 @@ import { buildDependencies } from './dependencies'

const routerParamsByChain = buildDependencies()

export async function getQuote(
async function getQuote(
{
type,
chainId,
Expand Down Expand Up @@ -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 }
)
}
119 changes: 119 additions & 0 deletions src/lib/hooks/routing/useClientSideSmartOrderRouterTrade.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
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])
}
41 changes: 41 additions & 0 deletions src/lib/hooks/routing/useRoutingAPIArguments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
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]
)
}
8 changes: 3 additions & 5 deletions src/lib/hooks/swap/useSwapInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
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'
Expand All @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit 05b2711

Please sign in to comment.