import { useState, useMemo, useEffect } from "react"; import { Box, Typography, useTheme, useMediaQuery } from "@mui/material"; import toast from "react-hot-toast"; import TokenStack from "../../components/TokenStack/TokenStack"; import SwapCard from "../../components/Swap/SwapCard"; import SwapCollection from "../../components/Swap/SwapCollection"; import { TokenAllowanceGuard } from "../../components/TokenAllowanceGuard/TokenAllowanceGuard"; import { SecondaryButton } from "../../components/Button"; import { formatCurrency } from "../../helpers"; import { DecimalBigNumber } from "../../helpers/DecimalBigNumber"; import { prettifySecondsInDays } from "../../helpers/timeUtil"; import { getTokenAddress } from "../../hooks/helpers"; import { useBalance, depositNative, withdrawWeth } from "../../hooks/tokens"; import { useUniswapV2Pair, useUniswapV2PairReserves, swapExactTokensForTokens, swapExactETHForTokens, swapExactTokensForETH, } from "../../hooks/uniswapv2"; import { EMPTY_ADDRESS } from "../../constants/addresses"; const SwapContainer = ({ tokenNameTop, tokenNameBottom, address, chainId, dexAddresses, connect, onCardsSwap, setTopTokenListOpen, setBottomTokenListOpen, slippage, destination, secondsToWait, setIsSwap, formatDecimals, isWrapping, isUnwrapping, }) => { const theme = useTheme(); const isSmallScreen = useMediaQuery("(max-width: 456px)"); const [isPending, setIsPending] = useState(false); const [amountBottom, setAmountBottom] = useState(""); const [amountTop, setAmountTop] = useState(""); const [currentPrice, setCurrentPrice] = useState(new DecimalBigNumber(0n, 0)); const [nextPrice, setNextPrice] = useState(new DecimalBigNumber(0n, 0)); const { balance: balanceTop, refetch: balanceRefetchTop, contractAddress: addressTop, isNative: topIsNative, } = useBalance(chainId, tokenNameTop, address); const { balance: balanceBottom, refetch: balanceRefetchBottom, contractAddress: addressBottom, isNative: bottomIsNative, } = useBalance(chainId, tokenNameBottom, address); const { pairAddress, } = useUniswapV2Pair(chainId, dexAddresses.factory, addressTop, addressBottom); const { reserves: pairReserves, tokens: tokenAddresses, refetch: pairReservesRefetch, } = useUniswapV2PairReserves( chainId, pairAddress, ); const onSwap = () => { setAmountTop(""); setAmountBottom(""); onCardsSwap(); } const setMax = () => setAmountTop(balanceTop.toString()); useEffect(() => { if (isWrapping || isUnwrapping) { setAmountBottom(amountTop.toString()); setNextPrice("1"); setCurrentPrice("1"); return; } const zero = new DecimalBigNumber(0n, 0); const raw = new DecimalBigNumber(amountTop, balanceTop._decimals); const amountInRaw = new DecimalBigNumber(raw._value.toBigInt(), balanceTop._decimals); const amountInWithFee = amountInRaw.mul(new DecimalBigNumber(997n, 3)); const amountIn = addressTop.toUpperCase() === tokenAddresses.token0.toUpperCase() ? pairReserves.reserve0 : pairReserves.reserve1; const amountOut = addressBottom.toUpperCase() === tokenAddresses.token1.toUpperCase() ? pairReserves.reserve1 : pairReserves.reserve0; if (amountOut.eq(zero)) { setCurrentPrice(""); } else { setCurrentPrice(amountIn.div(amountOut).toString()); } if (amountOut.eq(zero) || amountInWithFee.eq(zero)) { setAmountBottom(""); setNextPrice(""); } else { const nominator = amountOut.mul(amountInWithFee); const denominator = amountIn.add(amountInWithFee); const newAmountOut = nominator.div(denominator); const newReserveIn = amountIn.add(amountInWithFee); const newReserveOut = amountOut.sub(newAmountOut); const nextPrice = newReserveIn.div(newReserveOut); setAmountBottom(newAmountOut.toString()); setNextPrice(nextPrice.toString()); } }, [pairReserves, addressBottom, amountTop, addressTop, isWrapping, isUnwrapping]); const minReceived = useMemo(() => { const decimals = 7; const shares = Math.pow(10, decimals); const one = BigInt(shares * 100); const floatSlippage = slippage === "" ? 0 : parseFloat(slippage); const bigIntSlippage = one - BigInt(Math.round(floatSlippage * shares)); const slippageDecimalBigNumber = new DecimalBigNumber(bigIntSlippage, 2); const bigIntAmount = BigInt(Math.round(amountBottom * shares)); const amountDecimalBigNumber = new DecimalBigNumber(bigIntAmount, decimals); const tmpResult = amountDecimalBigNumber.mul(slippageDecimalBigNumber); const result = new DecimalBigNumber(tmpResult?._value, tmpResult?._decimals + decimals); return result?.toString(); }, [amountBottom, amountBottom, slippage, balanceBottom]); const buttonText = useMemo(() => { let text = "Swap"; if (isWrapping) text = "Wrap"; else if (isUnwrapping) text = "Unwrap"; else if (pairAddress === EMPTY_ADDRESS) text = "Create Pool"; return text; }, [isWrapping, isUnwrapping, pairAddress]); const swapTokens = async () => { setIsPending(true); const deadline = Math.floor(Date.now() / 1000) + secondsToWait; const shares = 100000; const one = BigInt(shares * 100); const floatSlippage = slippage === "" ? 0 : parseFloat(slippage); const bigIntSlippage = one - BigInt(Math.round(floatSlippage * shares)); if (floatSlippage < 3) toast("Slippage is too low, transaction highly likely will fail."); const amountADesired = BigInt(Math.round(parseFloat(amountTop) * Math.pow(10, balanceTop._decimals))); const amountBDesired = BigInt(Math.round(parseFloat(amountBottom) * Math.pow(10, balanceBottom._decimals))); const amountBMin = amountBDesired * bigIntSlippage / one; if (isWrapping) { await depositNative(chainId, address, amountADesired); } else if (isUnwrapping) { await withdrawWeth(chainId, address, amountADesired); } else { const params = { chainId, amountADesired, amountBMin, tokenNameTop, tokenNameBottom, destination, address, deadline }; if (topIsNative) { await swapExactETHForTokens(params) } else if (bottomIsNative) { await swapExactTokensForETH(params) } else { await swapExactTokensForTokens(params); } } await balanceRefetchTop(); await balanceRefetchBottom(); await pairReservesRefetch(); setAmountTop(""); setIsPending(false); } return ( } tokenName={tokenNameTop} info={ (!isSmallScreen ? "Balance: " : "") + formatCurrency(balanceTop ? balanceTop : "0", formatDecimals, tokenNameTop) } endString="Max" endStringOnClick={setMax} value={amountTop} onChange={event => setAmountTop(event.currentTarget.value)} tokenOnClick={() => setTopTokenListOpen(true)} inputProps={{ "data-testid": "fromInput" }} /> } LowerSwapCard={ } tokenName={tokenNameBottom} value={amountBottom} inputProps={{ "data-testid": "toInput" }} tokenOnClick={() => setBottomTokenListOpen(true)} info={ (!isSmallScreen ? "Balance: " : "") + formatCurrency(balanceBottom ? balanceBottom : "0", formatDecimals, tokenNameBottom) } /> } arrowOnClick={onSwap} /> {`1 ${tokenNameBottom} (Current)`} {formatCurrency(currentPrice, formatDecimals, tokenNameTop)} {`1 ${tokenNameBottom} (Next)`} {formatCurrency(nextPrice === "" ? currentPrice : nextPrice, formatDecimals, tokenNameTop)} Min. Receive: {formatCurrency(minReceived, formatDecimals, tokenNameBottom)} Tx. Deadline: ~{prettifySecondsInDays(secondsToWait)} address === "" ? connect() : (!isWrapping && !isUnwrapping) && pairAddress === EMPTY_ADDRESS ? setIsSwap(false) : swapTokens() } > {address === "" ? "Connect" : buttonText } ) } export default SwapContainer;