parent
90f72e05b9
commit
dfe50b4bee
@ -2,7 +2,6 @@ import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateFlag } from 'fe
|
||||
import { DetailsV2Variant, useDetailsV2Flag } from 'featureFlags/flags/nftDetails'
|
||||
import { useRoutingAPIForPriceFlag } from 'featureFlags/flags/priceRoutingApi'
|
||||
import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc'
|
||||
import { UnifiedRouterVariant, useRoutingAPIV2Flag } from 'featureFlags/flags/unifiedRouter'
|
||||
import { useWalletConnectFallbackFlag } from 'featureFlags/flags/walletConnectPopover'
|
||||
import { useWalletConnectV2Flag } from 'featureFlags/flags/walletConnectV2'
|
||||
import { useUpdateAtom } from 'jotai/utils'
|
||||
@ -211,12 +210,6 @@ export default function FeatureFlagModal() {
|
||||
featureFlag={FeatureFlag.detailsV2}
|
||||
label="Use the new details page for nfts"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={UnifiedRouterVariant}
|
||||
value={useRoutingAPIV2Flag()}
|
||||
featureFlag={FeatureFlag.uraEnabled}
|
||||
label="Enable the Unified Routing API"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={BaseVariant}
|
||||
value={useRoutingAPIForPriceFlag()}
|
||||
|
@ -1,11 +0,0 @@
|
||||
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
|
||||
|
||||
export function useRoutingAPIV2Flag(): BaseVariant {
|
||||
return useBaseFlag(FeatureFlag.uraEnabled)
|
||||
}
|
||||
|
||||
export function useRoutingAPIV2Enabled(): boolean {
|
||||
return useRoutingAPIV2Flag() === BaseVariant.Enabled
|
||||
}
|
||||
|
||||
export { BaseVariant as UnifiedRouterVariant }
|
@ -10,7 +10,6 @@ export enum FeatureFlag {
|
||||
permit2 = 'permit2',
|
||||
fiatOnRampButtonOnSwap = 'fiat_on_ramp_button_on_swap_page',
|
||||
detailsV2 = 'details_v2',
|
||||
uraEnabled = 'ura_enabled',
|
||||
debounceSwapQuote = 'debounce_swap_quote',
|
||||
nativeUsdcArbitrum = 'web_usdc_arbitrum',
|
||||
routingAPIPrice = 'routing_api_price',
|
||||
|
@ -6,8 +6,7 @@ import { isTestEnv } from 'utils/env'
|
||||
import { updateVersion } from './global/actions'
|
||||
import { sentryEnhancer } from './logging'
|
||||
import reducer from './reducer'
|
||||
import { routingApi } from './routing/slice'
|
||||
import { routingApiV2 } from './routing/v2Slice'
|
||||
import { routingApiV2 } from './routing/slice'
|
||||
|
||||
const PERSISTED_KEYS: string[] = ['user', 'transactions', 'lists']
|
||||
|
||||
@ -21,10 +20,9 @@ const store = configureStore({
|
||||
// meta.arg and meta.baseQueryMeta are defaults. payload.trade is a nonserializable return value, but that's ok
|
||||
// because we are not adding it into any persisted store that requires serialization (e.g. localStorage)
|
||||
ignoredActionPaths: ['meta.arg', 'meta.baseQueryMeta', 'payload.trade'],
|
||||
ignoredPaths: [routingApi.reducerPath, routingApiV2.reducerPath],
|
||||
ignoredPaths: [routingApiV2.reducerPath],
|
||||
},
|
||||
})
|
||||
.concat(routingApi.middleware)
|
||||
.concat(routingApiV2.middleware)
|
||||
.concat(save({ states: PERSISTED_KEYS, debounce: 1000 })),
|
||||
preloadedState: load({ states: PERSISTED_KEYS, disableWarnings: isTestEnv() }),
|
||||
|
@ -7,8 +7,7 @@ import lists from './lists/reducer'
|
||||
import logs from './logs/slice'
|
||||
import mint from './mint/reducer'
|
||||
import mintV3 from './mint/v3/reducer'
|
||||
import { routingApi } from './routing/slice'
|
||||
import { routingApiV2 } from './routing/v2Slice'
|
||||
import { routingApiV2 } from './routing/slice'
|
||||
import transactions from './transactions/reducer'
|
||||
import user from './user/reducer'
|
||||
import wallets from './wallets/reducer'
|
||||
@ -25,6 +24,5 @@ export default {
|
||||
multicall: multicall.reducer,
|
||||
lists,
|
||||
logs,
|
||||
[routingApi.reducerPath]: routingApi.reducer,
|
||||
[routingApiV2.reducerPath]: routingApiV2.reducer,
|
||||
}
|
||||
|
@ -4,30 +4,11 @@ import { TradeType } from '@uniswap/sdk-core'
|
||||
import { ChainId } from '@uniswap/sdk-core'
|
||||
import { getClientSideQuote } from 'lib/hooks/routing/clientSideSmartOrderRouter'
|
||||
import ms from 'ms.macro'
|
||||
import qs from 'qs'
|
||||
import { trace } from 'tracing/trace'
|
||||
|
||||
import { QuoteData, QuoteMethod, TradeResult } from './types'
|
||||
import { QuoteMethod, QuoteReponse, QuoteState, TradeResult } from './types'
|
||||
import { getRouter, isExactInput, shouldUseAPIRouter, transformRoutesToTrade } from './utils'
|
||||
|
||||
export enum RouterPreference {
|
||||
AUTO = 'auto',
|
||||
API = 'api',
|
||||
CLIENT = 'client',
|
||||
}
|
||||
|
||||
// This is excluded from `RouterPreference` enum because it's only used
|
||||
// internally for token -> USDC trades to get a USD value.
|
||||
export const INTERNAL_ROUTER_PREFERENCE_PRICE = 'price' as const
|
||||
|
||||
// routing API quote params: https://github.com/Uniswap/routing-api/blob/main/lib/handlers/quote/schema/quote-schema.ts
|
||||
const API_QUERY_PARAMS = {
|
||||
protocols: 'v2,v3,mixed',
|
||||
}
|
||||
const CLIENT_PARAMS = {
|
||||
protocols: [Protocol.V2, Protocol.V3, Protocol.MIXED],
|
||||
}
|
||||
|
||||
export interface GetQuoteArgs {
|
||||
tokenInAddress: string
|
||||
tokenInChainId: ChainId
|
||||
@ -43,21 +24,36 @@ export interface GetQuoteArgs {
|
||||
isRoutingAPIPrice?: boolean
|
||||
}
|
||||
|
||||
enum QuoteState {
|
||||
SUCCESS = 'Success',
|
||||
NOT_FOUND = 'Not found',
|
||||
export enum RouterPreference {
|
||||
AUTO = 'auto',
|
||||
API = 'api',
|
||||
CLIENT = 'client',
|
||||
}
|
||||
|
||||
export const routingApi = createApi({
|
||||
reducerPath: 'routingApi',
|
||||
// This is excluded from `RouterPreference` enum because it's only used
|
||||
// internally for token -> USDC trades to get a USD value.
|
||||
export const INTERNAL_ROUTER_PREFERENCE_PRICE = 'price' as const
|
||||
|
||||
const CLIENT_PARAMS = {
|
||||
protocols: [Protocol.V2, Protocol.V3, Protocol.MIXED],
|
||||
}
|
||||
|
||||
// routing API quote query params: https://github.com/Uniswap/routing-api/blob/main/lib/handlers/quote/schema/quote-schema.ts
|
||||
const CLASSIC_SWAP_QUERY_PARAMS = {
|
||||
...CLIENT_PARAMS,
|
||||
routingType: 'CLASSIC',
|
||||
}
|
||||
|
||||
export const routingApiV2 = createApi({
|
||||
reducerPath: 'routingApiV2',
|
||||
baseQuery: fetchBaseQuery({
|
||||
baseUrl: 'https://api.uniswap.org/v1/',
|
||||
baseUrl: 'https://api.uniswap.org/v2/',
|
||||
}),
|
||||
endpoints: (build) => ({
|
||||
getQuote: build.query<TradeResult, GetQuoteArgs>({
|
||||
async onQueryStarted(args: GetQuoteArgs, { queryFulfilled }) {
|
||||
trace(
|
||||
'quote',
|
||||
'quote-v2',
|
||||
async ({ setTraceError, setTraceStatus }) => {
|
||||
try {
|
||||
await queryFulfilled
|
||||
@ -83,28 +79,39 @@ export const routingApi = createApi({
|
||||
}
|
||||
)
|
||||
},
|
||||
async queryFn(args, _api, _extraOptions, fetch) {
|
||||
const fellBack = false
|
||||
async queryFn(args: GetQuoteArgs, _api, _extraOptions, fetch) {
|
||||
let fellBack = false
|
||||
if (shouldUseAPIRouter(args)) {
|
||||
fellBack = true
|
||||
try {
|
||||
const { tokenInAddress, tokenInChainId, tokenOutAddress, tokenOutChainId, amount, tradeType } = args
|
||||
const type = isExactInput(tradeType) ? 'exactIn' : 'exactOut'
|
||||
const query = qs.stringify({
|
||||
...API_QUERY_PARAMS,
|
||||
tokenInAddress,
|
||||
const type = isExactInput(tradeType) ? 'EXACT_INPUT' : 'EXACT_OUTPUT'
|
||||
|
||||
const requestBody = {
|
||||
tokenInChainId,
|
||||
tokenOutAddress,
|
||||
tokenIn: tokenInAddress,
|
||||
tokenOutChainId,
|
||||
tokenOut: tokenOutAddress,
|
||||
amount,
|
||||
type,
|
||||
configs: [CLASSIC_SWAP_QUERY_PARAMS],
|
||||
}
|
||||
|
||||
const response = await fetch({
|
||||
method: 'POST',
|
||||
url: '/quote',
|
||||
body: JSON.stringify(requestBody),
|
||||
})
|
||||
const response = await fetch(`quote?${query}`)
|
||||
|
||||
if (response.error) {
|
||||
try {
|
||||
// cast as any here because we do a runtime check on it being an object before indexing into .errorCode
|
||||
const errorData = response.error.data as any
|
||||
// NO_ROUTE should be treated as a valid response to prevent retries.
|
||||
if (typeof errorData === 'object' && errorData?.errorCode === 'NO_ROUTE') {
|
||||
if (
|
||||
typeof errorData === 'object' &&
|
||||
(errorData?.errorCode === 'NO_ROUTE' || errorData?.detail === 'No quotes available')
|
||||
) {
|
||||
return { data: { state: QuoteState.NOT_FOUND } }
|
||||
}
|
||||
} catch {
|
||||
@ -112,12 +119,13 @@ export const routingApi = createApi({
|
||||
}
|
||||
}
|
||||
|
||||
const quoteData = response.data as QuoteData
|
||||
const tradeResult = transformRoutesToTrade(args, quoteData)
|
||||
const quoteData = response.data as QuoteReponse
|
||||
const tradeResult = transformRoutesToTrade(args, quoteData.quote)
|
||||
|
||||
return { data: { ...tradeResult, method: QuoteMethod.ROUTING_API } }
|
||||
} catch (error: any) {
|
||||
console.warn(
|
||||
`GetQuote failed on routing API, falling back to client: ${error?.message ?? error?.detail ?? error}`
|
||||
`GetQuote failed on API v2, falling back to client: ${error?.message ?? error?.detail ?? error}`
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -136,11 +144,8 @@ export const routingApi = createApi({
|
||||
}
|
||||
},
|
||||
keepUnusedDataFor: ms`10s`,
|
||||
extraOptions: {
|
||||
maxRetries: 0,
|
||||
},
|
||||
}),
|
||||
}),
|
||||
})
|
||||
|
||||
export const { useGetQuoteQuery } = routingApi
|
||||
export const { useGetQuoteQuery } = routingApiV2
|
||||
|
@ -76,7 +76,7 @@ export interface QuoteData {
|
||||
routeString: string
|
||||
}
|
||||
|
||||
export type QuoteDataV2 = {
|
||||
export type QuoteReponse = {
|
||||
routing: RouterPreference.API
|
||||
quote: QuoteData
|
||||
}
|
||||
|
@ -3,12 +3,11 @@ import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
|
||||
import { IMetric, MetricLoggerUnit, setGlobalMetric } from '@uniswap/smart-order-router'
|
||||
import { sendTiming } from 'components/analytics'
|
||||
import { AVERAGE_L1_BLOCK_TIME } from 'constants/chainInfo'
|
||||
import { useRoutingAPIV2Enabled } from 'featureFlags/flags/unifiedRouter'
|
||||
import { useRoutingAPIArguments } from 'lib/hooks/routing/useRoutingAPIArguments'
|
||||
import ms from 'ms.macro'
|
||||
import { useMemo } from 'react'
|
||||
import { INTERNAL_ROUTER_PREFERENCE_PRICE, RouterPreference, useGetQuoteQuery } from 'state/routing/slice'
|
||||
import { useGetQuoteQuery as useGetQuoteQueryV2 } from 'state/routing/v2Slice'
|
||||
import { INTERNAL_ROUTER_PREFERENCE_PRICE, RouterPreference } from 'state/routing/slice'
|
||||
import { useGetQuoteQuery as useGetQuoteQueryV2 } from 'state/routing/slice'
|
||||
|
||||
import { InterfaceTrade, QuoteMethod, QuoteState, TradeState } from './types'
|
||||
|
||||
@ -48,34 +47,16 @@ export function useRoutingAPITrade<TTradeType extends TradeType>(
|
||||
routerPreference,
|
||||
})
|
||||
|
||||
const shouldUseRoutingApiV2 = useRoutingAPIV2Enabled()
|
||||
|
||||
const {
|
||||
isError: isLegacyAPIError,
|
||||
data: legacyAPITradeResult,
|
||||
currentData: currentLegacyAPITradeResult,
|
||||
} = useGetQuoteQuery(shouldUseRoutingApiV2 ? skipToken : queryArgs ?? skipToken, {
|
||||
isError,
|
||||
data: tradeResult,
|
||||
currentData: currentTradeResult,
|
||||
} = useGetQuoteQueryV2(queryArgs ?? skipToken, {
|
||||
// Price-fetching is informational and costly, so it's done less frequently.
|
||||
pollingInterval: routerPreference === INTERNAL_ROUTER_PREFERENCE_PRICE ? ms`1m` : AVERAGE_L1_BLOCK_TIME,
|
||||
// If latest quote from cache was fetched > 2m ago, instantly repoll for another instead of waiting for next poll period
|
||||
refetchOnMountOrArgChange: 2 * 60,
|
||||
})
|
||||
|
||||
const {
|
||||
isError: isV2APIError,
|
||||
data: v2TradeResult,
|
||||
currentData: currentV2TradeResult,
|
||||
} = useGetQuoteQueryV2(!shouldUseRoutingApiV2 ? skipToken : queryArgs ?? skipToken, {
|
||||
// Price-fetching is informational and costly, so it's done less frequently.
|
||||
pollingInterval: routerPreference === INTERNAL_ROUTER_PREFERENCE_PRICE ? ms`1m` : AVERAGE_L1_BLOCK_TIME,
|
||||
// If latest quote from cache was fetched > 2m ago, instantly repoll for another instead of waiting for next poll period
|
||||
refetchOnMountOrArgChange: 2 * 60,
|
||||
})
|
||||
|
||||
const [tradeResult, currentTradeResult, isError] = shouldUseRoutingApiV2
|
||||
? [v2TradeResult, currentV2TradeResult, isV2APIError]
|
||||
: [legacyAPITradeResult, currentLegacyAPITradeResult, isLegacyAPIError]
|
||||
|
||||
const isCurrent = currentTradeResult === tradeResult
|
||||
|
||||
return useMemo(() => {
|
||||
|
@ -1,125 +0,0 @@
|
||||
import { createApi, fetchBaseQuery, FetchBaseQueryError } from '@reduxjs/toolkit/query/react'
|
||||
import { Protocol } from '@uniswap/router-sdk'
|
||||
import { getClientSideQuote } from 'lib/hooks/routing/clientSideSmartOrderRouter'
|
||||
import ms from 'ms.macro'
|
||||
import { trace } from 'tracing/trace'
|
||||
|
||||
import { GetQuoteArgs, INTERNAL_ROUTER_PREFERENCE_PRICE, RouterPreference } from './slice'
|
||||
import { QuoteDataV2, QuoteMethod, QuoteState, TradeResult } from './types'
|
||||
import { getRouter, isExactInput, shouldUseAPIRouter, transformRoutesToTrade } from './utils'
|
||||
|
||||
const CLIENT_PARAMS = {
|
||||
protocols: [Protocol.V2, Protocol.V3, Protocol.MIXED],
|
||||
}
|
||||
|
||||
// routing API quote query params: https://github.com/Uniswap/routing-api/blob/main/lib/handlers/quote/schema/quote-schema.ts
|
||||
const CLASSIC_SWAP_QUERY_PARAMS = {
|
||||
...CLIENT_PARAMS,
|
||||
routingType: 'CLASSIC',
|
||||
}
|
||||
|
||||
export const routingApiV2 = createApi({
|
||||
reducerPath: 'routingApiV2',
|
||||
baseQuery: fetchBaseQuery({
|
||||
baseUrl: 'https://api.uniswap.org/v2/',
|
||||
}),
|
||||
endpoints: (build) => ({
|
||||
getQuote: build.query<TradeResult, GetQuoteArgs>({
|
||||
async onQueryStarted(args: GetQuoteArgs, { queryFulfilled }) {
|
||||
trace(
|
||||
'quote-v2',
|
||||
async ({ setTraceError, setTraceStatus }) => {
|
||||
try {
|
||||
await queryFulfilled
|
||||
} catch (error: unknown) {
|
||||
if (error && typeof error === 'object' && 'error' in error) {
|
||||
const queryError = (error as Record<'error', FetchBaseQueryError>).error
|
||||
if (typeof queryError.status === 'number') {
|
||||
setTraceStatus(queryError.status)
|
||||
}
|
||||
setTraceError(queryError)
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
data: {
|
||||
...args,
|
||||
isPrice: args.routerPreference === INTERNAL_ROUTER_PREFERENCE_PRICE,
|
||||
isAutoRouter:
|
||||
args.routerPreference === RouterPreference.AUTO || args.routerPreference === RouterPreference.API,
|
||||
},
|
||||
}
|
||||
)
|
||||
},
|
||||
async queryFn(args: GetQuoteArgs, _api, _extraOptions, fetch) {
|
||||
let fellBack = false
|
||||
if (shouldUseAPIRouter(args)) {
|
||||
fellBack = true
|
||||
try {
|
||||
const { tokenInAddress, tokenInChainId, tokenOutAddress, tokenOutChainId, amount, tradeType } = args
|
||||
const type = isExactInput(tradeType) ? 'EXACT_INPUT' : 'EXACT_OUTPUT'
|
||||
|
||||
const requestBody = {
|
||||
tokenInChainId,
|
||||
tokenIn: tokenInAddress,
|
||||
tokenOutChainId,
|
||||
tokenOut: tokenOutAddress,
|
||||
amount,
|
||||
type,
|
||||
configs: [CLASSIC_SWAP_QUERY_PARAMS],
|
||||
}
|
||||
|
||||
const response = await fetch({
|
||||
method: 'POST',
|
||||
url: '/quote',
|
||||
body: JSON.stringify(requestBody),
|
||||
})
|
||||
|
||||
if (response.error) {
|
||||
try {
|
||||
// cast as any here because we do a runtime check on it being an object before indexing into .errorCode
|
||||
const errorData = response.error.data as any
|
||||
// NO_ROUTE should be treated as a valid response to prevent retries.
|
||||
if (
|
||||
typeof errorData === 'object' &&
|
||||
(errorData?.errorCode === 'NO_ROUTE' || errorData?.detail === 'No quotes available')
|
||||
) {
|
||||
return { data: { state: QuoteState.NOT_FOUND } }
|
||||
}
|
||||
} catch {
|
||||
throw response.error
|
||||
}
|
||||
}
|
||||
|
||||
const quoteData = response.data as QuoteDataV2
|
||||
const tradeResult = transformRoutesToTrade(args, quoteData.quote)
|
||||
|
||||
return { data: { ...tradeResult, method: QuoteMethod.ROUTING_API } }
|
||||
} catch (error: any) {
|
||||
console.warn(
|
||||
`GetQuote failed on API v2, falling back to client: ${error?.message ?? error?.detail ?? error}`
|
||||
)
|
||||
}
|
||||
}
|
||||
try {
|
||||
const method = fellBack ? QuoteMethod.CLIENT_SIDE_FALLBACK : QuoteMethod.CLIENT_SIDE
|
||||
const router = getRouter(args.tokenInChainId)
|
||||
const quoteResult = await getClientSideQuote(args, router, CLIENT_PARAMS)
|
||||
if (quoteResult.state === QuoteState.SUCCESS) {
|
||||
return { data: { ...transformRoutesToTrade(args, quoteResult.data), method } }
|
||||
} else {
|
||||
return { data: quoteResult }
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.warn(`GetQuote failed on client: ${error}`)
|
||||
return { error: { status: 'CUSTOM_ERROR', error: error?.detail ?? error?.message ?? error } }
|
||||
}
|
||||
},
|
||||
keepUnusedDataFor: ms`10s`,
|
||||
}),
|
||||
}),
|
||||
})
|
||||
|
||||
export const { useGetQuoteQuery } = routingApiV2
|
Loading…
Reference in New Issue
Block a user