diff --git a/package.json b/package.json index da2b43f62..20831a0fc 100644 --- a/package.json +++ b/package.json @@ -128,6 +128,7 @@ "@semantic-release/git": "^10.0.1", "@svgr/rollup": "^6.2.1", "@testing-library/react": "^12", + "@testing-library/react-hooks": "^8.0.1", "@testing-library/user-event": "^14.2.0", "@typechain/ethers-v5": "^7.0.0", "@types/jest": "^25.2.1", @@ -185,7 +186,7 @@ "react-cosmos": "^5.6.6", "react-dom": "^17.0.1", "react-redux": "^7.2.2", - "react-test-renderer": "^18.0.0", + "react-test-renderer": "^17.0.1", "redux": "^4.1.2", "rollup": "^2.63.0", "rollup-plugin-dts": "^4.2.1", diff --git a/src/hooks/routing/clientSideSmartOrderRouter.ts b/src/hooks/routing/clientSideSmartOrderRouter.ts index 00f1de7fa..227970445 100644 --- a/src/hooks/routing/clientSideSmartOrderRouter.ts +++ b/src/hooks/routing/clientSideSmartOrderRouter.ts @@ -4,6 +4,7 @@ import { BigintIsh, CurrencyAmount, Token, TradeType } from '@uniswap/sdk-core' // eslint-disable-next-line no-restricted-imports import { AlphaRouter, AlphaRouterConfig, AlphaRouterParams, ChainId } from '@uniswap/smart-order-router' import JSBI from 'jsbi' +import { QuoteArguments } from 'state/routing/slice' import { GetQuoteResult } from 'state/routing/types' import { transformSwapRouteToGetQuoteResult } from './transformSwapRouteToGetQuoteResult' @@ -53,20 +54,9 @@ async function getQuote( if (!swapRoute) throw new Error(`Failed to generate client side quote from ${currencyIn.symbol} to ${currencyOut.symbol}`) - return { data: transformSwapRouteToGetQuoteResult(type, amount, swapRoute) } -} - -interface QuoteArguments { - tokenInAddress: string - tokenInChainId: ChainId - tokenInDecimals: number - tokenInSymbol?: string - tokenOutAddress: string - tokenOutChainId: ChainId - tokenOutDecimals: number - tokenOutSymbol?: string - amount: string - type: 'exactIn' | 'exactOut' + const data = transformSwapRouteToGetQuoteResult(type, amount, swapRoute) + data.isApiResult = false + return { data } } export async function getClientSideQuote( @@ -80,9 +70,9 @@ export async function getClientSideQuote( tokenOutDecimals, tokenOutSymbol, amount, + provider, type, }: QuoteArguments, - provider: JsonRpcProvider, routerConfig: Partial ) { return getQuote( diff --git a/src/hooks/routing/useRouterTrade.test.ts b/src/hooks/routing/useRouterTrade.test.ts new file mode 100644 index 000000000..e0fcd1247 --- /dev/null +++ b/src/hooks/routing/useRouterTrade.test.ts @@ -0,0 +1,363 @@ +/** + * @jest-environment hardhat/dist/jsdom + */ + +import { JsonRpcProvider } from '@ethersproject/providers' +import 'jest-environment-hardhat' + +import { renderHook, WrapperComponent } from '@testing-library/react-hooks' +import { CurrencyAmount, TradeType } from '@uniswap/sdk-core' +import { AlphaRouterConfig } from '@uniswap/smart-order-router' +import { DAI, USDC_MAINNET } from 'constants/tokens' +import useDebounce from 'hooks/useDebounce' +import useIsWindowVisible from 'hooks/useIsWindowVisible' +import { getRouterApiQuote, QuoteArguments } from 'state/routing/slice' +import { GetQuoteResult, TradeState, V3PoolInRoute } from 'state/routing/types' +import { Provider as ReduxProvider } from 'react-redux' +import { store } from 'state' +import useActiveWeb3React, { ActiveWeb3Provider } from 'hooks/useActiveWeb3React' +import { BlockNumberProvider } from 'hooks/useBlockNumber' + +import { getClientSideQuote, isAutoRouterSupportedChain } from './clientSideSmartOrderRouter' +import { useRouterTrade } from './useRouterTrade' +import { getRouterApiQuote, QuoteArguments } from 'state/routing/slice' + +import useDebounce from 'hooks/useDebounce' +import useIsWindowVisible from 'hooks/useIsWindowVisible' +import { Trade } from 'components/Swap/Toolbar/Caption' +import { AlphaRouterConfig } from '@uniswap/smart-order-router' +import { EIP1193 } from '@web3-react/eip1193' +import { initializeConnector } from '@web3-react/core' +import JsonRpcConnector from 'utils/JsonRpcConnector' +import { waitFor } from '@testing-library/react' + +const ROUTER_URL = 'https://api.uniswap.org/v1/' + +const USDCAmount = CurrencyAmount.fromRawAmount(USDC_MAINNET, '1') +const DAIAmount = CurrencyAmount.fromRawAmount(DAI, '1') + +jest.mock('./clientSideSmartOrderRouter') +jest.mock('hooks/useDebounce') +jest.mock('hooks/useIsWindowVisible') +// jest.mock('state/') +jest.mock('hooks/useActiveWeb3React') + +const mockUseDebounce = useDebounce as jest.MockedFunction +const mockIsAutoRouterSupportedChain = isAutoRouterSupportedChain as jest.MockedFunction< + typeof isAutoRouterSupportedChain +> +const mockUseIsWindowVisible = useIsWindowVisible as jest.MockedFunction +const mockUseActiveWeb3React = useActiveWeb3React as jest.MockedFunction + +const mockGetRouterApiQuote = getRouterApiQuote as unknown as jest.MockInstance< + Promise<{ data: GetQuoteResult; error?: unknown }>, + [QuoteArguments, Partial] +> + +const mockGetClientSideQuote = getClientSideQuote as unknown as jest.MockInstance< + Promise<{ data: GetQuoteResult; error?: unknown }>, + [QuoteArguments, Partial] +> + +const v3route: V3PoolInRoute[] = [ + { + address: '0x5777d92f208679DB4b9778590Fa3CAB3aC9e2168', + amountIn: '1000000', + amountOut: '999864608834353719', + fee: '100', + liquidity: '4860835302628184189362918', + sqrtRatioX96: '79229564680073347405847', + tickCurrent: '-276324', + tokenIn: { + address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + chainId: 1, + decimals: 6, + symbol: 'USDC', + }, + tokenOut: { + address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', + chainId: 1, + decimals: 18, + symbol: 'DAI', + }, + type: 'v3-pool', + }, +] +const validQuoteResult = (isApiResult: boolean) => { + return { + amount: '1000000', + amountDecimals: '1', + blockNumber: '15265125', + gasPriceWei: '27119565267', + gasUseEstimate: '113000', + gasUseEstimateQuote: '5032683228533210957', + gasUseEstimateQuoteDecimals: '5.032683228533210957', + gasUseEstimateUSD: '5.026105', + isApiResult, + quote: '999864608834353719', + quoteDecimals: '0.999864608834353719', + quoteGasAdjusted: '-4032818619698857238', + quoteGasAdjustedDecimals: '-4.032818619698857238', + quoteId: 'eb0ed', + route: [v3route], + routeString: '[V3] 100.00% = USDC -- 0.01% [0x5777d92f208679DB4b9778590Fa3CAB3aC9e2168] --> DAI', + } +} +const error = new Error('Error') + +const expectRouterApiMock = (data?: GetQuoteResult, error?: Error) => { + if (data) mockGetRouterApiQuote.mockResolvedValue({ data }) + if (error) mockGetRouterApiQuote.mockRejectedValue({ error }) +} + +const expectClientSideMock = (data?: GetQuoteResult, error?: Error) => { + if (data) mockGetClientSideQuote.mockResolvedValue({ data }) + if (error) mockGetClientSideQuote.mockRejectedValue({ error }) +} + +const [connector] = initializeConnector((actions) => new JsonRpcConnector(actions, hardhat.provider)) +mockUseActiveWeb3React.mockReturnValue({ + chainId: 1, + library: hardhat.provider, + connector, +}) + +const wrapper = ({ children }) => ( + + {children} + +) + +beforeEach(() => { + // ignore debounced value + mockUseDebounce.mockImplementation((value) => value) + + mockUseIsWindowVisible.mockReturnValue(true) + mockIsAutoRouterSupportedChain.mockReturnValue(true) +}) + +describe('integrator provides bad router URL', () => { + it('computes clientside route trade when routerUrl not provided', async () => { + expectRouterApiMock() + expectClientSideMock(validQuoteResult(false)) + + const { result } = renderHook(() => useRouterTrade(TradeType.EXACT_INPUT, undefined, USDCAmount, DAI), { wrapper }) + + await waitFor(() => expect(result.current.state).not.toBe(TradeState.LOADING)) + + expect(result.current.trade).toBeDefined() + expect(result.current.isApiResult).toBeFalsy() + expect(result.current.state).toEqual(TradeState.VALID) + }) + + it('computes clientside route trade when routerUrl is empty', async () => { + expectRouterApiMock() + expectClientSideMock(validQuoteResult(false)) + + const { result } = renderHook(() => useRouterTrade(TradeType.EXACT_INPUT, '', USDCAmount, DAI), { wrapper }) + + await waitFor(() => expect(result.current.state).not.toBe(TradeState.LOADING)) + + expect(result.current.trade).toBeDefined() + expect(result.current.isApiResult).toBeFalsy() + expect(result.current.state).toEqual(TradeState.VALID) + }) + + it('computes clientside route trade when routerUrl is bad', async () => { + expectRouterApiMock(undefined, error) + expectClientSideMock(validQuoteResult(false)) + + const { result } = renderHook(() => useRouterTrade(TradeType.EXACT_INPUT, 'google.com', USDCAmount, DAI), { + wrapper, + }) + + await waitFor(() => expect(result.current.state).not.toBe(TradeState.LOADING)) + + expect(result.current.trade).toBeDefined() + expect(result.current.isApiResult).toBeFalsy() + expect(result.current.state).toEqual(TradeState.VALID) + }) +}) + +describe('routing API not used', () => { + it('computes client side v3 trade if routing api errors', async () => { + expectRouterApiMock(error) + expectClientSideMock(validQuoteResult(false)) + + const { result } = renderHook(() => useRouterTrade(TradeType.EXACT_INPUT, ROUTER_URL, USDCAmount, DAI), { + wrapper, + }) + + await waitFor(() => expect(result.current.state).not.toBe(TradeState.LOADING)) + + expect(result.current.trade).toBeDefined() + expect(result.current.isApiResult).toBeFalsy() + expect(result.current.state).toEqual(TradeState.VALID) + }) + + // it('does not compute routing api trade when window is not focused', async () => { + // mockUseIsWindowVisible.mockReturnValue(false) + // expectRouterApiMock() + // expectClientSideMock(validQuoteResult(false)) + + // const { result } = renderHook(() => useRouterTrade(TradeType.EXACT_INPUT, ROUTER_URL, USDCAmount, DAI), { wrapper }) + // console.log('WINDOW NOT FOCUSED', result.current) + // await waitFor(() => expect(result.current.state).not.toBe(TradeState.LOADING)) + + // expect(result.current.trade).toBeDefined() + // expect(result.current.isApiResult).toBeFalsy() + // expect(result.current.state).toEqual(TradeState.VALID) + // }) + + // it('does not compute routing api trade when routing API is not supported', () => { + // mockIsAutoRouterSupportedChain.mockReturnValue(false) + // expectRouterApiMock() + // expectClientSideMock(validQuoteResult(false)) + + // const wrapper: + // | WrapperComponent<{ + // provider: JsonRpcProvider + // jsonRpcEndpoint: string + // }> + // | undefined = ({ children, provider, jsonRpcEndpoint }) => ( + // + // {children} + // + // ) + + // expect( + // renderHook(() => useRouterTrade(TradeType.EXACT_INPUT, ROUTER_URL, USDCAmount, DAI), { + // wrapper, + // initialProps: { provider: hardhat.provider, jsonRpcEndpoint: hardhat.url }, + // }) + // ).toThrow() + // }) +}) + +// it('does not compute routing api trade when window is not focused', () => { +// mockUseIsWindowVisible.mockReturnValue(false) +// expectRouterApiMock(TradeState.NO_ROUTE_FOUND) +// expectClientSideMock(TradeState.VALID) + +// const { result } = renderHook(() => useRouterTrade(TradeType.EXACT_INPUT, ROUTER_URL, USDCAmount, DAI)) +// expect(result.current).toEqual({ isApiResult: false, state: TradeState.VALID, trade: undefined }) +// }) + +// describe('when routing api is in non-error state', () => { +// it('does not compute client side v3 trade if routing api is LOADING', () => { +// expectRouterApiMock(TradeState.LOADING) +// const { result } = renderHook(() => useRouterTrade(TradeType.EXACT_INPUT, ROUTER_URL, USDCAmount, DAI)) +// expect(result.current).toEqual({ isApiResult: true, state: TradeState.LOADING, trade: undefined }) +// }) + +// it('does not compute client side v3 trade if routing api is VALID', () => { +// expectRouterApiMock(TradeState.VALID) +// const { result } = renderHook(() => useRouterTrade(TradeType.EXACT_INPUT, ROUTER_URL, USDCAmount, DAI)) +// expect(result.current).toEqual({ isApiResult: true, state: TradeState.VALID, trade: undefined }) +// }) + +// it('does not compute client side v3 trade if routing api is SYNCING', () => { +// expectRouterApiMock(TradeState.SYNCING) + +// const { result } = renderHook(() => useRouterTrade(TradeType.EXACT_INPUT, ROUTER_URL, USDCAmount, DAI)) +// expect(result.current).toEqual({ isApiResult: true, state: TradeState.SYNCING, trade: undefined }) +// }) +// }) + +// describe('when routing api is in error state', () => { +// it('computes client side v3 trade if routing api is INVALID', () => { +// expectRouterApiMock(TradeState.INVALID) +// expectClientSideMock(TradeState.VALID) + +// const { result } = renderHook(() => useRouterTrade(TradeType.EXACT_INPUT, ROUTER_URL, USDCAmount, DAI)) +// expect(result.current).toEqual({ isApiResult: false, state: TradeState.VALID, trade: undefined }) +// }) + +// it('computes client side v3 trade if routing api is NO_ROUTE_FOUND', () => { +// expectRouterApiMock(TradeState.NO_ROUTE_FOUND) +// expectClientSideMock(TradeState.VALID) + +// const { result } = renderHook(() => useRouterTrade(TradeType.EXACT_INPUT, ROUTER_URL, USDCAmount, DAI)) +// expect(result.current).toEqual({ isApiResult: false, state: TradeState.VALID, trade: undefined }) +// }) +// }) +// }) + +// describe('#useRouterTrade ExactOut', () => { +// it('does not compute routing api trade when routing API is not supported', () => { +// mockIsAutoRouterSupportedChain.mockReturnValue(false) +// expectRouterApiMock(TradeState.INVALID) +// expectClientSideMock(TradeState.VALID) + +// const { result } = renderHook(() => useRouterTrade(TradeType.EXACT_OUTPUT, ROUTER_URL, DAIAmount, USDC_MAINNET)) +// expect(result.current).toEqual({ isApiResult: false, state: TradeState.VALID, trade: undefined }) +// }) + +// it('does not compute routing api trade when window is not focused', () => { +// mockUseIsWindowVisible.mockReturnValue(false) +// expectRouterApiMock(TradeState.NO_ROUTE_FOUND) +// expectClientSideMock(TradeState.VALID) + +// const { result } = renderHook(() => useRouterTrade(TradeType.EXACT_OUTPUT, ROUTER_URL, DAIAmount, USDC_MAINNET)) +// expect(result.current).toEqual({ isApiResult: false, state: TradeState.VALID, trade: undefined }) +// }) +// describe('when routing api is in non-error state', () => { +// it('does not compute client side v3 trade if routing api is LOADING', () => { +// expectRouterApiMock(TradeState.LOADING) + +// const { result } = renderHook(() => useRouterTrade(TradeType.EXACT_OUTPUT, ROUTER_URL, DAIAmount, USDC_MAINNET)) +// expect(result.current).toEqual({ isApiResult: true, state: TradeState.LOADING, trade: undefined }) +// }) + +// it('does not compute client side v3 trade if routing api is VALID', () => { +// expectRouterApiMock(TradeState.VALID) + +// const { result } = renderHook(() => useRouterTrade(TradeType.EXACT_OUTPUT, ROUTER_URL, DAIAmount, USDC_MAINNET)) +// expect(result.current).toEqual({ isApiResult: true, state: TradeState.VALID, trade: undefined }) +// }) + +// it('does not compute client side v3 trade if routing api is SYNCING', () => { +// expectRouterApiMock(TradeState.SYNCING) + +// const { result } = renderHook(() => useRouterTrade(TradeType.EXACT_OUTPUT, ROUTER_URL, DAIAmount, USDC_MAINNET)) +// expect(result.current).toEqual({ isApiResult: true, state: TradeState.SYNCING, trade: undefined }) +// }) +// }) + +// describe('when routing api is in error state', () => { +// it('computes client side v3 trade if routing api is INVALID', () => { +// expectRouterApiMock(TradeState.INVALID) +// expectClientSideMock(TradeState.VALID) + +// const { result } = renderHook(() => useRouterTrade(TradeType.EXACT_OUTPUT, ROUTER_URL, DAIAmount, USDC_MAINNET)) +// expect(result.current).toEqual({ isApiResult: false, state: TradeState.VALID, trade: undefined }) +// }) + +// it('computes client side v3 trade if routing api is NO_ROUTE_FOUND', () => { +// expectRouterApiMock(TradeState.NO_ROUTE_FOUND) +// expectClientSideMock(TradeState.VALID) + +// const { result } = renderHook(() => useRouterTrade(TradeType.EXACT_OUTPUT, ROUTER_URL, DAIAmount, USDC_MAINNET)) +// expect(result.current).toEqual({ isApiResult: false, state: TradeState.VALID, trade: undefined }) +// }) +// }) +// }) + +// describe('integrator provides bad router URL', () => { +// it('computes clientside route when routerUrl is undefined', () => { +// const { result } = renderHook(() => useRouterTrade(TradeType.EXACT_INPUT, undefined, USDCAmount, DAI)) +// expect(result.current).toEqual({ isApiResult: false, state: TradeState.VALID, trade: undefined }) +// }) + +// it('computes clientside route when routerUrl is empty', () => { +// const { result } = renderHook(() => useRouterTrade(TradeType.EXACT_INPUT, '', USDCAmount, DAI)) +// expect(result.current).toEqual({ isApiResult: false, state: TradeState.VALID, trade: undefined }) +// }) + +// it('computes clientside route when routerUrl is bad', () => { +// const { result } = renderHook(() => +// useRouterTrade(TradeType.EXACT_INPUT, 'https://broken-url.com', USDCAmount, DAI) +// ) +// expect(result.current).toEqual({ isApiResult: false, state: TradeState.VALID, trade: undefined }) +// }) +// }) diff --git a/src/hooks/routing/useRouterTrade.ts b/src/hooks/routing/useRouterTrade.ts index af0e66849..93efcace8 100644 --- a/src/hooks/routing/useRouterTrade.ts +++ b/src/hooks/routing/useRouterTrade.ts @@ -33,12 +33,15 @@ export function useRouterTrade( amountSpecified?: CurrencyAmount, otherCurrency?: Currency ): { + isApiResult?: boolean state: TradeState trade: InterfaceTrade | undefined } { const { chainId, library } = useActiveWeb3React() const autoRouterSupported = isAutoRouterSupportedChain(chainId) const isWindowVisible = useIsWindowVisible() + const useRouterApi = autoRouterSupported && isWindowVisible + // Debounce is used to prevent excessive requests to SOR, as it is data intensive. // Fast user actions (ie updating the input) should be debounced, but currency changes should not. const [debouncedAmount, debouncedOtherCurrency] = useDebounce( @@ -46,14 +49,13 @@ export function useRouterTrade( 200 ) const isDebouncing = amountSpecified !== debouncedAmount && otherCurrency === debouncedOtherCurrency - const debouncedAmountSpecified = autoRouterSupported && isWindowVisible ? debouncedAmount : undefined const [currencyIn, currencyOut]: [Currency | undefined, Currency | undefined] = useMemo( () => tradeType === TradeType.EXACT_INPUT - ? [debouncedAmountSpecified?.currency, debouncedOtherCurrency] - : [debouncedOtherCurrency, debouncedAmountSpecified?.currency], - [debouncedAmountSpecified, debouncedOtherCurrency, tradeType] + ? [debouncedAmount?.currency, debouncedOtherCurrency] + : [debouncedOtherCurrency, debouncedAmount?.currency], + [debouncedAmount, debouncedOtherCurrency, tradeType] ) const currencyChainId = currencyIn?.chainId as ChainId @@ -64,9 +66,9 @@ export function useRouterTrade( const queryArgs = useRouterArguments({ tokenIn: currencyIn, tokenOut: currencyOut, - amount: debouncedAmountSpecified, + amount: debouncedAmount, tradeType, - routerUrl, + routerUrl: useRouterApi ? routerUrl : undefined, provider: library as JsonRpcProvider, }) @@ -74,6 +76,7 @@ export function useRouterTrade( pollingInterval: ms`15s`, refetchOnFocus: true, }) + console.log('data', data) const quoteResult: GetQuoteResult | undefined = useIsValidBlock(Number(data?.blockNumber) || 0) ? data : undefined @@ -97,7 +100,7 @@ export function useRouterTrade( } }, [gasUseEstimateUSD, route, tradeType]) - return useMemo(() => { + const result = useMemo(() => { if (!currencyIn || !currencyOut) return INVALID_TRADE if (!trade && !isError) { @@ -124,4 +127,5 @@ export function useRouterTrade( } return INVALID_TRADE }, [currencyIn, currencyOut, quoteResult, trade, tradeType, isError, route, queryArgs, isDebouncing, isSyncing]) + return { isApiResult: quoteResult?.isApiResult, ...result } } diff --git a/src/state/routing/slice.ts b/src/state/routing/slice.ts index bbcef4174..95bc63b8b 100644 --- a/src/state/routing/slice.ts +++ b/src/state/routing/slice.ts @@ -17,6 +17,41 @@ const DEFAULT_QUERY_PARAMS = { protocols: protocols.map((p) => p.toLowerCase()).join(','), } +export interface QuoteArguments { + tokenInAddress: string + tokenInChainId: ChainId + tokenInDecimals: number + tokenInSymbol?: string + tokenOutAddress: string + tokenOutChainId: ChainId + tokenOutDecimals: number + tokenOutSymbol?: string + amount: string + routerUrl?: string + provider: JsonRpcProvider + type: 'exactIn' | 'exactOut' +} + +export async function getRouterApiQuote(args: QuoteArguments): Promise<{ data: GetQuoteResult; error?: unknown }> { + const { tokenInAddress, tokenInChainId, tokenOutAddress, tokenOutChainId, amount, routerUrl, type } = args + const query = qs.stringify({ + ...DEFAULT_QUERY_PARAMS, + tokenInAddress, + tokenInChainId, + tokenOutAddress, + tokenOutChainId, + amount, + type, + }) + const response = await global.fetch(`${routerUrl}quote?${query}`) + if (!response.ok) { + throw new Error(`${response.statusText}: could not get quote from auto-router API`) + } + const data = await response.json() + data.isApiResult = true + return { data } +} + const serializeRoutingCacheKey = ({ endpointName, queryArgs }: { endpointName: string; queryArgs: any }) => { // same as default serializeQueryArgs, but we add extra case if key is our non-serializable JsonRpcProvider return `${endpointName}(${JSON.stringify(queryArgs, (key, value) => { @@ -43,61 +78,30 @@ export const routing = createApi({ }, serializeQueryArgs: serializeRoutingCacheKey, // need to write custom cache key fxn to handle non-serializable JsonRpcProvider provider endpoints: (build) => ({ - getQuote: build.query< - GetQuoteResult, - { - tokenInAddress: string - tokenInChainId: ChainId - tokenInDecimals: number - tokenInSymbol?: string - tokenOutAddress: string - tokenOutChainId: ChainId - tokenOutDecimals: number - tokenOutSymbol?: string - amount: string - routerUrl?: string - provider: JsonRpcProvider - type: 'exactIn' | 'exactOut' - } - >({ + getQuote: build.query({ async queryFn(args, _api, _extraOptions) { - const { tokenInAddress, tokenInChainId, tokenOutAddress, tokenOutChainId, amount, routerUrl, provider, type } = - args - - async function getClientSideQuote() { + async function getClientSideQuote(args: QuoteArguments) { // Lazy-load the clientside router to improve initial pageload times. return await ( await import('../../hooks/routing/clientSideSmartOrderRouter') - ).getClientSideQuote(args, provider, { protocols }) + ).getClientSideQuote(args, { protocols }) } + const { routerUrl } = args let result if (Boolean(routerUrl)) { // Try routing API, fallback to clientside SOR try { - const query = qs.stringify({ - ...DEFAULT_QUERY_PARAMS, - tokenInAddress, - tokenInChainId, - tokenOutAddress, - tokenOutChainId, - amount, - type, - }) - const response = await global.fetch(`${routerUrl}quote?${query}`) - if (!response.ok) { - throw new Error(`${response.statusText}: could not get quote from auto-router API`) - } - const data = await response.json() - result = { data } + result = await getRouterApiQuote(args) } catch (e) { console.warn(e) - result = await getClientSideQuote() + result = await getClientSideQuote(args) } } else { // If integrator did not provide a routing API URL param, use clientside SOR - result = await getClientSideQuote() + result = await getClientSideQuote(args) } + console.log(result) if (result?.error) return { error: result.error as FetchBaseQueryError } return { data: result?.data as GetQuoteResult } }, diff --git a/src/state/routing/types.ts b/src/state/routing/types.ts index a32f30105..0fbf0d38c 100644 --- a/src/state/routing/types.ts +++ b/src/state/routing/types.ts @@ -66,6 +66,7 @@ export interface GetQuoteResult { quoteGasAdjustedDecimals: string route: Array routeString: string + isApiResult?: boolean } export class InterfaceTrade< diff --git a/yarn.lock b/yarn.lock index 17ce0add4..7eca29734 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3022,6 +3022,14 @@ lz-string "^1.4.4" pretty-format "^27.0.2" +"@testing-library/react-hooks@^8.0.1": + version "8.0.1" + resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-8.0.1.tgz#0924bbd5b55e0c0c0502d1754657ada66947ca12" + integrity sha512-Aqhl2IVmLt8IovEVarNDFuJDVWVvhnr9/GCU6UUnrYXwgDFF9h2L2o2P9KBni1AST5sT6riAyoukFLyjQUgD/g== + dependencies: + "@babel/runtime" "^7.12.5" + react-error-boundary "^3.1.0" + "@testing-library/react@^12": version "12.1.5" resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-12.1.5.tgz#bb248f72f02a5ac9d949dea07279095fa577963b" @@ -11507,6 +11515,13 @@ react-element-to-jsx-string@^14.3.2: "@base2/pretty-print-object" "1.0.0" is-plain-object "3.0.1" +react-error-boundary@^3.1.0: + version "3.1.4" + resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-3.1.4.tgz#255db92b23197108757a888b01e5b729919abde0" + integrity sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA== + dependencies: + "@babel/runtime" "^7.12.5" + react-error-overlay@6.0.9: version "6.0.9" resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a" @@ -11529,7 +11544,7 @@ react-is@^16.12.0, react-is@^16.13.1, react-is@^16.7.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -"react-is@^16.12.0 || ^17.0.0 || ^18.0.0", react-is@^18.1.0: +"react-is@^16.12.0 || ^17.0.0 || ^18.0.0": version "18.1.0" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.1.0.tgz#61aaed3096d30eacf2a2127118b5b41387d32a67" integrity sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg== @@ -11559,7 +11574,7 @@ react-redux@^7.2.2: prop-types "^15.7.2" react-is "^16.13.1" -react-shallow-renderer@^16.15.0: +react-shallow-renderer@^16.13.1: version "16.15.0" resolved "https://registry.yarnpkg.com/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz#48fb2cf9b23d23cde96708fe5273a7d3446f4457" integrity sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA== @@ -11567,14 +11582,15 @@ react-shallow-renderer@^16.15.0: object-assign "^4.1.1" react-is "^16.12.0 || ^17.0.0 || ^18.0.0" -react-test-renderer@^18.0.0: - version "18.1.0" - resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-18.1.0.tgz#35b75754834cf9ab517b6813db94aee0a6b545c3" - integrity sha512-OfuueprJFW7h69GN+kr4Ywin7stcuqaYAt1g7airM5cUgP0BoF5G5CXsPGmXeDeEkncb2fqYNECO4y18sSqphg== +react-test-renderer@^17.0.1: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-17.0.2.tgz#4cd4ae5ef1ad5670fc0ef776e8cc7e1231d9866c" + integrity sha512-yaQ9cB89c17PUb0x6UfWRs7kQCorVdHlutU1boVPEsB8IDZH6n9tHxMacc3y0JoXOJUsZb/t/Mb8FUWMKaM7iQ== dependencies: - react-is "^18.1.0" - react-shallow-renderer "^16.15.0" - scheduler "^0.22.0" + object-assign "^4.1.1" + react-is "^17.0.2" + react-shallow-renderer "^16.13.1" + scheduler "^0.20.2" react-virtualized-auto-sizer@^1.0.2: version "1.0.5" @@ -12093,13 +12109,6 @@ scheduler@^0.20.2: loose-envify "^1.1.0" object-assign "^4.1.1" -scheduler@^0.22.0: - version "0.22.0" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.22.0.tgz#83a5d63594edf074add9a7198b1bae76c3db01b8" - integrity sha512-6QAm1BgQI88NPYymgGQLCZgvep4FyePDWFpXVK+zNSUgHwlqpJy8VEh8Et0KxTACS4VWwMousBElAZOH9nkkoQ== - dependencies: - loose-envify "^1.1.0" - schema-utils@^2.6.5: version "2.7.1" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7"