UNPKG

@lifi/widget

Version:

LI.FI Widget for cross-chain bridging and swapping. It will drive your multi-chain strategy and attract new users from everywhere.

216 lines (215 loc) 9.19 kB
import { isAddress } from '@ethersproject/address'; import { LifiErrorCode } from '@lifi/sdk'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import Big from 'big.js'; import { useWatch } from 'react-hook-form'; import { v4 as uuidv4 } from 'uuid'; import { useDebouncedWatch, useGasRefuel, useToken } from '.'; import { FormKey, useLiFi, useWallet, useWidgetConfig } from '../providers'; import { useSettings } from '../stores'; import { useSwapOnly } from './useSwapOnly'; const refetchTime = 60000; export const useRoutes = ({ insurableRoute } = {}) => { const lifi = useLiFi(); const { subvariant, sdkConfig, insurance, contractTool } = useWidgetConfig(); const { account } = useWallet(); const queryClient = useQueryClient(); const swapOnly = useSwapOnly(); const { slippage, enabledBridges, enabledAutoRefuel, enabledExchanges, routePriority, } = useSettings([ 'slippage', 'routePriority', 'enabledAutoRefuel', 'enabledBridges', 'enabledExchanges', ]); const [fromTokenAmount] = useDebouncedWatch([FormKey.FromAmount], 320); const [fromChainId, fromTokenAddress, toAddress, toTokenAmount, toChainId, toContractAddress, toContractCallData, toContractGasLimit, toTokenAddress,] = useWatch({ name: [ FormKey.FromChain, FormKey.FromToken, FormKey.ToAddress, FormKey.ToAmount, FormKey.ToChain, FormKey.ToContractAddress, FormKey.ToContractCallData, FormKey.ToContractGasLimit, FormKey.ToToken, ], }); const { token: fromToken } = useToken(fromChainId, fromTokenAddress); const { token: toToken } = useToken(toChainId, toTokenAddress); const { enabled: enabledRefuel, fromAmount: gasRecommendationFromAmount } = useGasRefuel(); const hasAmount = (!isNaN(fromTokenAmount) && Number(fromTokenAmount) > 0) || (!isNaN(toTokenAmount) && Number(toTokenAmount) > 0); const contractCallQuoteEnabled = subvariant === 'nft' ? Boolean(toContractAddress && toContractCallData && toContractGasLimit) : true; const isEnabled = !isNaN(fromChainId) && !isNaN(toChainId) && Boolean(fromToken?.address) && Boolean(toToken?.address) && !Number.isNaN(slippage) && hasAmount && contractCallQuoteEnabled; const queryKey = [ 'routes', account.address, fromChainId, fromToken?.address, fromTokenAmount, toAddress, toChainId, toToken?.address, toTokenAmount, toContractAddress, toContractCallData, toContractGasLimit, slippage, swapOnly ? [] : enabledBridges, enabledExchanges, routePriority, subvariant, sdkConfig?.defaultRouteOptions?.allowSwitchChain, enabledRefuel && enabledAutoRefuel, gasRecommendationFromAmount, insurance, insurableRoute?.id, ]; const { data, isLoading, isFetching, isFetched, dataUpdatedAt, refetch } = useQuery(queryKey, async ({ queryKey: [_, fromAddress, fromChainId, fromTokenAddress, fromTokenAmount, toAddress, toChainId, toTokenAddress, toTokenAmount, toContractAddress, toContractCallData, toContractGasLimit, slippage, enabledBridges, enabledExchanges, routePriority, subvariant, allowSwitchChain, enabledRefuel, gasRecommendationFromAmount, insurance, insurableRouteId,], signal, }) => { let toWalletAddress; try { toWalletAddress = (await account.signer?.provider?.resolveName(toAddress)) ?? (isAddress(toAddress) ? toAddress : fromAddress); } catch { toWalletAddress = isAddress(toAddress) ? toAddress : fromAddress; } const fromAmount = Big(fromTokenAmount || 0) .mul(10 ** (fromToken?.decimals ?? 0)) .toFixed(0); const formattedSlippage = parseFloat(slippage) / 100; const allowedBridges = insurableRoute ? insurableRoute.steps.flatMap((step) => step.includedSteps .filter((includedStep) => includedStep.type === 'cross') .map((includedStep) => includedStep.toolDetails.key)) : enabledBridges; const allowedExchanges = insurableRoute ? insurableRoute.steps.flatMap((step) => step.includedSteps .filter((includedStep) => includedStep.type === 'swap') .map((includedStep) => includedStep.toolDetails.key)) : enabledExchanges; if (subvariant === 'nft') { const contractCallQuote = await lifi.getContractCallQuote({ fromAddress, fromChain: fromChainId, fromToken: fromTokenAddress, toAmount: toTokenAmount, toChain: toChainId, toToken: toTokenAddress, toContractAddress, toContractCallData, toContractGasLimit, allowBridges: allowedBridges, toFallbackAddress: toWalletAddress, slippage: formattedSlippage, }, { signal }); contractCallQuote.action.toToken = toToken; const customStep = subvariant === 'nft' ? contractCallQuote.includedSteps?.find((step) => step.type === 'custom') : undefined; if (customStep && contractTool) { const toolDetails = { key: contractTool.name, name: contractTool.name, logoURI: contractTool.logoURI, }; customStep.toolDetails = toolDetails; contractCallQuote.toolDetails = toolDetails; } const route = { id: uuidv4(), fromChainId: contractCallQuote.action.fromChainId, fromAmountUSD: contractCallQuote.estimate.fromAmountUSD || '', fromAmount: contractCallQuote.action.fromAmount, fromToken: contractCallQuote.action.fromToken, fromAddress: contractCallQuote.action.fromAddress, toChainId: contractCallQuote.action.toChainId, toAmountUSD: contractCallQuote.estimate.toAmountUSD || '', toAmount: toTokenAmount, toAmountMin: toTokenAmount, toToken: toToken, toAddress: toWalletAddress, gasCostUSD: contractCallQuote.estimate.gasCosts?.[0].amountUSD, steps: [contractCallQuote], insurance: { state: 'NOT_INSURABLE', feeAmountUsd: '0' }, }; return { routes: [route] }; } return lifi.getRoutes({ fromChainId, fromAmount, fromTokenAddress, toChainId, toTokenAddress, fromAddress, toAddress: toWalletAddress, fromAmountForGas: enabledRefuel && gasRecommendationFromAmount ? gasRecommendationFromAmount : undefined, options: { slippage: formattedSlippage, bridges: { allow: allowedBridges, }, exchanges: { allow: allowedExchanges, }, order: routePriority, allowSwitchChain: subvariant === 'refuel' ? false : allowSwitchChain, insurance: insurance ? Boolean(insurableRoute) : undefined, }, }, { signal }); }, { enabled: isEnabled, staleTime: refetchTime, cacheTime: refetchTime, refetchInterval(data, query) { return Math.min(Math.abs(refetchTime - (Date.now() - query.state.dataUpdatedAt)), refetchTime); }, retry(failureCount, error) { if (error?.code === LifiErrorCode.NotFound) { return false; } return true; }, onSuccess(data) { if (data.routes[0]) { // Update local tokens cache to keep priceUSD in sync const { fromToken, toToken } = data.routes[0]; [fromToken, toToken].forEach((token) => { queryClient.setQueriesData(['token-balances', account.address, token.chainId], (data) => { if (data) { const clonedData = [...data]; const index = clonedData.findIndex((dataToken) => dataToken.address === token.address); clonedData[index] = { ...clonedData[index], ...token, }; return clonedData; } }); }); } }, }); return { routes: data?.routes, isLoading: isEnabled && isLoading, isFetching, isFetched, dataUpdatedAt, refetchTime, refetch, }; };