From b6ad5d74d435d109b801f760ab7b143375c02815 Mon Sep 17 00:00:00 2001 From: mintdart <96025197+mintdart@users.noreply.github.com> Date: Thu, 16 May 2024 07:56:18 -0500 Subject: [PATCH 1/3] auto alippage --- src/components/Aggregator/ConnectButton.tsx | 1 + src/components/Aggregator/index.tsx | 54 +++++++++++---- src/components/HistoryModal/index.tsx | 3 +- src/components/Slippage/index.tsx | 73 +++++++++++---------- src/queries/useGetAutoSlippage.tsx | 43 ++++++++++++ src/queries/useGetRoutes.tsx | 28 ++++---- 6 files changed, 141 insertions(+), 61 deletions(-) create mode 100644 src/queries/useGetAutoSlippage.tsx diff --git a/src/components/Aggregator/ConnectButton.tsx b/src/components/Aggregator/ConnectButton.tsx index 9a849857..ed82b3a7 100644 --- a/src/components/Aggregator/ConnectButton.tsx +++ b/src/components/Aggregator/ConnectButton.tsx @@ -7,6 +7,7 @@ const Wrapper = styled.div` right: 0px; z-index: 100; display: flex; + align-items: center; gap: 8px; `; diff --git a/src/components/Aggregator/index.tsx b/src/components/Aggregator/index.tsx index 435ce950..14039575 100644 --- a/src/components/Aggregator/index.tsx +++ b/src/components/Aggregator/index.tsx @@ -70,6 +70,7 @@ import { ArrowBackIcon, ArrowForwardIcon, RepeatIcon, SettingsIcon } from '@chak import { Settings } from './Settings'; import { formatAmount } from '~/utils/formatAmount'; import { RefreshIcon } from '../RefreshIcon'; +import { useGetAutoSlippage } from '~/queries/useGetAutoSlippage'; /* Integrated: @@ -320,7 +321,7 @@ export function AggregatorContainer({ tokenList, sandwichList }) { const [isPrivacyEnabled, setIsPrivacyEnabled] = useLocalStorage('llamaswap-isprivacyenabled', false); const [[amount, amountOut], setAmount] = useState<[number | string, number | string]>(['10', '']); - const [slippage, setSlippage] = useLocalStorage('llamaswap-slippage', '0.5'); + const [slippage, setSlippage] = useLocalStorage('llamaswap-slippage', 'auto'); const [lastOutputValue, setLastOutputValue] = useState(null); const [disabledAdapters, setDisabledAdapters] = useLocalStorage('llamaswap-disabledadapters', []); const [isDegenModeEnabled, _] = useLocalStorage('llamaswap-degenmode', false); @@ -409,6 +410,25 @@ export function AggregatorContainer({ tokenList, sandwichList }) { return { finalSelectedFromToken, finalSelectedToToken }; }, [fromToken2, selectedChain?.id, toToken2, selectedFromToken, selectedToToken]); + const { data: autoSlippage, isLoading: fetchingAutoSlippage } = useGetAutoSlippage({ + chainId: selectedChain?.chainId, + fromToken: finalSelectedFromToken?.value, + toToken: finalSelectedToToken?.value, + disabled: slippage !== 'auto', + onError: () => { + setSlippage('0.5'); + } + }); + + useEffect(() => { + // auto slippage is only supported on ethereum + if (chainOnWallet?.id !== 1 && slippage === 'auto') { + setSlippage('0.5'); + } + }, [chainOnWallet, slippage]); + + const finalSlippage = autoSlippage ?? slippage; + // format input amount of selected from token const amountWithDecimals = BigNumber(debouncedAmount && debouncedAmount !== '' ? debouncedAmount : '0') .times(BigNumber(10).pow(finalSelectedFromToken?.decimals || 18)) @@ -475,10 +495,11 @@ export function AggregatorContainer({ tokenList, sandwichList }) { amount: debouncedAmount, fromToken: finalSelectedFromToken, toToken: finalSelectedToToken, - slippage, + slippage: finalSlippage, isPrivacyEnabled, amountOut: amountOutWithDecimals - } + }, + disabled: fetchingAutoSlippage || finalSlippage === 'auto' }); const { data: gasData, isLoading: isGasDataLoading } = useEstimateGas({ @@ -678,7 +699,7 @@ export function AggregatorContainer({ tokenList, sandwichList }) { ? +selectedRoute?.fromAmount > +balance.data.value : false; - const slippageIsWorng = Number.isNaN(Number(slippage)) || slippage === ''; + const slippageIsWorng = !!finalSlippage || Number.isNaN(Number(finalSlippage)); const forceRefreshTokenBalance = () => { if (chainOnWallet && address) { @@ -690,9 +711,9 @@ export function AggregatorContainer({ tokenList, sandwichList }) { // approve/swap tokens const amountToApprove = - amountOut && amountOut !== '' + !!finalSlippage && amountOut && amountOut !== '' ? BigNumber(selectedRoute?.fromAmount) - .times(100 + Number(slippage) * 2) + .times(100 + Number(finalSlippage) * 2) .div(100) .toFixed(0) : selectedRoute?.fromAmount; @@ -800,7 +821,7 @@ export function AggregatorContainer({ tokenList, sandwichList }) { amount: String(variables.amountIn), amountUsd: +fromTokenPrice * +variables.amountIn || 0, errorData: data, - slippage, + slippage: finalSlippage, routePlace: String(variables?.index), route: variables.route }); @@ -838,7 +859,7 @@ export function AggregatorContainer({ tokenList, sandwichList }) { amount: String(variables.amountIn), amountUsd: +fromTokenPrice * +variables.amountIn || 0, errorData: {}, - slippage, + slippage: finalSlippage, routePlace: String(variables?.index), route: variables.route }); @@ -890,7 +911,7 @@ export function AggregatorContainer({ tokenList, sandwichList }) { amount: String(variables.amountIn), amountUsd: +fromTokenPrice * +variables.amountIn || 0, errorData: {}, - slippage, + slippage: finalSlippage, routePlace: String(variables?.index), route: variables.route, reportedOutput: Number(variables.amount) || 0, @@ -915,7 +936,7 @@ export function AggregatorContainer({ tokenList, sandwichList }) { amount: String(variables.amountIn), amountUsd: +fromTokenPrice * +variables.amountIn || 0, errorData: err, - slippage, + slippage: finalSlippage, routePlace: String(variables?.index), route: variables.route }); @@ -939,7 +960,7 @@ export function AggregatorContainer({ tokenList, sandwichList }) { to: finalSelectedToToken.value, signer, signTypedDataAsync, - slippage, + slippage: finalSlippage, adapter: selectedRoute.name, rawQuote: selectedRoute.price.rawQuote, tokens: { fromToken: finalSelectedFromToken, toToken: finalSelectedToToken }, @@ -979,7 +1000,9 @@ export function AggregatorContainer({ tokenList, sandwichList }) { const warnings = [ aggregator === 'CowSwap' ? ( <> - {finalSelectedFromToken.value === ethers.constants.AddressZero && Number(slippage) < 2 ? ( + {!!finalSlippage && + finalSelectedFromToken.value === ethers.constants.AddressZero && + Number(finalSlippage) < 2 ? ( Swaps from {finalSelectedFromToken.symbol} on CowSwap need to have slippage higher than 2%. @@ -1141,10 +1164,11 @@ export function AggregatorContainer({ tokenList, sandwichList }) { setSlippage={setSlippage} fromToken={finalSelectedFromToken?.symbol} toToken={finalSelectedToToken?.symbol} + finalSlippage={finalSlippage} /> @@ -1396,6 +1420,8 @@ export function AggregatorContainer({ tokenList, sandwichList }) { finalSelectedToToken && !(disabledAdapters.length === adaptersNames.length) ? ( + ) : fetchingAutoSlippage ? ( + ) : (!debouncedAmount && !debouncedAmountOut) || !finalSelectedFromToken || !finalSelectedToToken || diff --git a/src/components/HistoryModal/index.tsx b/src/components/HistoryModal/index.tsx index 415abbf2..59e78134 100644 --- a/src/components/HistoryModal/index.tsx +++ b/src/components/HistoryModal/index.tsx @@ -98,8 +98,7 @@ function HistoryModal({ tokensUrlMap, tokensSymbolsMap }) { ))} { diff --git a/src/queries/useGetAutoSlippage.tsx b/src/queries/useGetAutoSlippage.tsx new file mode 100644 index 00000000..76fa6719 --- /dev/null +++ b/src/queries/useGetAutoSlippage.tsx @@ -0,0 +1,43 @@ +import { useQuery } from '@tanstack/react-query'; +import { getAddress } from 'ethers/lib/utils.js'; + +interface IGetAutoSlippage { + fromToken: string; + toToken: string; + chainId: number; + disabled: boolean; + onError?: () => void; +} + +export async function getAutoSlippage({ fromToken, toToken, chainId, disabled }: IGetAutoSlippage) { + if (chainId !== 1 || disabled || !fromToken || !toToken) { + return null; + } + + const data = await fetch(`https://slippage.llama.fi/inference`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ from_address: getAddress(fromToken), to_address: getAddress(toToken) }) + }).then((res) => res.json()); + + if (data.error) { + throw new Error(data.error); + } + + return data.predictedSlippage; +} + +export function useGetAutoSlippage({ fromToken, toToken, chainId, disabled, onError }: IGetAutoSlippage) { + return useQuery( + ['auto-slippage', fromToken, toToken, chainId, disabled], + () => getAutoSlippage({ fromToken, toToken, chainId, disabled }), + { + refetchOnWindowFocus: false, + refetchIntervalInBackground: false, + retry: false, + onError + } + ); +} diff --git a/src/queries/useGetRoutes.tsx b/src/queries/useGetRoutes.tsx index 81e990db..9a590412 100644 --- a/src/queries/useGetRoutes.tsx +++ b/src/queries/useGetRoutes.tsx @@ -13,6 +13,7 @@ interface IGetListRoutesProps { extra?: any; disabledAdapters?: Array; customRefetchInterval?: number; + disabled?: boolean; } export interface IRoute { @@ -133,20 +134,23 @@ export function useGetRoutes({ amount, extra = {}, disabledAdapters = [], - customRefetchInterval + customRefetchInterval, + disabled }: IGetListRoutesProps) { const res = useQueries({ - queries: adapters - .filter((adap) => adap.chainToId[chain] !== undefined && !disabledAdapters.includes(adap.name)) - .map>((adapter) => { - return { - queryKey: ['routes', adapter.name, chain, from, to, amount, JSON.stringify(omit(extra, 'amount'))], - queryFn: () => getAdapterRoutes({ adapter, chain, from, to, amount, extra }), - refetchInterval: customRefetchInterval || REFETCH_INTERVAL, - refetchOnWindowFocus: false, - refetchIntervalInBackground: false - }; - }) + queries: disabled + ? [] + : adapters + .filter((adap) => adap.chainToId[chain] !== undefined && !disabledAdapters.includes(adap.name)) + .map>((adapter) => { + return { + queryKey: ['routes', adapter.name, chain, from, to, amount, JSON.stringify(omit(extra, 'amount'))], + queryFn: () => getAdapterRoutes({ adapter, chain, from, to, amount, extra }), + refetchInterval: customRefetchInterval || REFETCH_INTERVAL, + refetchOnWindowFocus: false, + refetchIntervalInBackground: false + }; + }) }); const data = res?.filter((r) => r.status === 'success') ?? []; const resData = res?.filter((r) => r.status === 'success' && !!r.data && r.data.price) ?? []; From f4273acd99a99fbee9cac3f0660e420c87b75305 Mon Sep 17 00:00:00 2001 From: mintdart <96025197+mintdart@users.noreply.github.com> Date: Thu, 16 May 2024 07:58:36 -0500 Subject: [PATCH 2/3] fix --- src/components/Aggregator/index.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/Aggregator/index.tsx b/src/components/Aggregator/index.tsx index 14039575..20abaa88 100644 --- a/src/components/Aggregator/index.tsx +++ b/src/components/Aggregator/index.tsx @@ -699,7 +699,7 @@ export function AggregatorContainer({ tokenList, sandwichList }) { ? +selectedRoute?.fromAmount > +balance.data.value : false; - const slippageIsWorng = !!finalSlippage || Number.isNaN(Number(finalSlippage)); + const slippageIsWrong = finalSlippage === '' || Number.isNaN(Number(finalSlippage)); const forceRefreshTokenBalance = () => { if (chainOnWallet && address) { @@ -945,7 +945,7 @@ export function AggregatorContainer({ tokenList, sandwichList }) { }); const handleSwap = () => { - if (selectedRoute && selectedRoute.price && !slippageIsWorng) { + if (selectedRoute && selectedRoute.price && !slippageIsWrong) { if (hasMaxPriceImpact && !isDegenModeEnabled) { toast({ title: 'Price impact is too high!', @@ -1287,7 +1287,7 @@ export function AggregatorContainer({ tokenList, sandwichList }) { !(finalSelectedFromToken && finalSelectedToToken) || insufficientBalance || !selectedRoute || - slippageIsWorng || + slippageIsWrong || !isAmountSynced || isApproveInfiniteLoading } @@ -1296,7 +1296,7 @@ export function AggregatorContainer({ tokenList, sandwichList }) { ? 'Select Aggregator' : isApproved ? `Swap via ${selectedRoute.name}` - : slippageIsWorng + : slippageIsWrong ? 'Set Slippage' : 'Approve'} @@ -1548,7 +1548,7 @@ export function AggregatorContainer({ tokenList, sandwichList }) { isApproveLoading || isApproveResetLoading || !selectedRoute || - slippageIsWorng || + slippageIsWrong || !isAmountSynced } > @@ -1556,7 +1556,7 @@ export function AggregatorContainer({ tokenList, sandwichList }) { ? 'Select Aggregator' : isApproved ? `Swap via ${selectedRoute?.name}` - : slippageIsWorng + : slippageIsWrong ? 'Set Slippage' : 'Approve'} From 9a5b2e9e2f3db132be540d87dc0f2097fded8444 Mon Sep 17 00:00:00 2001 From: mintdart <96025197+mintdart@users.noreply.github.com> Date: Thu, 30 May 2024 06:28:46 -0500 Subject: [PATCH 3/3] fix --- src/components/Aggregator/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/Aggregator/index.tsx b/src/components/Aggregator/index.tsx index 20abaa88..1e68e731 100644 --- a/src/components/Aggregator/index.tsx +++ b/src/components/Aggregator/index.tsx @@ -324,7 +324,7 @@ export function AggregatorContainer({ tokenList, sandwichList }) { const [slippage, setSlippage] = useLocalStorage('llamaswap-slippage', 'auto'); const [lastOutputValue, setLastOutputValue] = useState(null); const [disabledAdapters, setDisabledAdapters] = useLocalStorage('llamaswap-disabledadapters', []); - const [isDegenModeEnabled, _] = useLocalStorage('llamaswap-degenmode', false); + const [isDegenModeEnabled] = useLocalStorage('llamaswap-degenmode', false); const [isSettingsModalOpen, setSettingsModalOpen] = useState(false); // mobile states @@ -422,10 +422,10 @@ export function AggregatorContainer({ tokenList, sandwichList }) { useEffect(() => { // auto slippage is only supported on ethereum - if (chainOnWallet?.id !== 1 && slippage === 'auto') { + if (isConnected && chainOnWallet?.id !== 1 && slippage === 'auto') { setSlippage('0.5'); } - }, [chainOnWallet, slippage]); + }, [isConnected, chainOnWallet, slippage]); const finalSlippage = autoSlippage ?? slippage;