import { useState, 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 } from "../../hooks/tokens"; import { useUniswapV2Pair, useUniswapV2PairReserves, swapExactTokensForTokens } from "../../hooks/uniswapv2"; const SwapContainer = ({ tokenNameTop, tokenNameBottom, address, chainId, dexAddresses, connect, onCardsSwap, setTopTokenListOpen, setBottomTokenListOpen, slippage, secondsToWait, setIsSwap, formatDecimals }) => { 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, } = useBalance(chainId, tokenNameTop, address); const { balance: balanceBottom, refetch: balanceRefetchBottom, contractAddress: addressBottom, } = 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(() => { 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 topAddress = getTokenAddress(chainId, tokenNameTop); const bottomAddress = getTokenAddress(chainId, tokenNameBottom); const amountIn = addressTop.toUpperCase() === tokenAddresses.token0.toUpperCase() ? pairReserves.reserve0 : pairReserves.reserve1; const amountOut = addressBottom.toUpperCase() === tokenAddresses.token1.toUpperCase() ? pairReserves.reserve1 : pairReserves.reserve0; if (amountIn.eq(zero)) { setCurrentPrice(""); } else { setCurrentPrice(amountIn.div(amountOut).toString()); } if (amountIn.eq(zero) || amountInWithFee.eq(zero)) { setAmountBottom(""); setNextPrice(""); } else { const nominator = amountOut.mul(amountIn); const denominator = amountIn.add(amountInWithFee); const newAmountOut = nominator.div(denominator); setAmountBottom(amountOut.sub(newAmountOut).toString()); setNextPrice(denominator.div(newAmountOut).toString()) } }, [amountTop, addressTop]); const swapTokens = async () => { setIsPending(true); const deadline = Math.floor(Date.now() / 1000) + secondsToWait; const destination = address; 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; await swapExactTokensForTokens( chainId, amountADesired, amountBMin, [tokenNameTop, tokenNameBottom], destination, deadline ); 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} /> {!isSmallScreen && Current price: {formatCurrency(currentPrice, formatDecimals)} Next price: {formatCurrency(nextPrice === "" ? currentPrice : nextPrice, formatDecimals)} Transaction deadline: ~{prettifySecondsInDays(secondsToWait)} } address === "" ? connect() : pairAddress === "0x0000000000000000000000000000000000000000" ? setIsSwap(false) : swapTokens() } > {address === "" ? "Connect" : pairAddress === "0x0000000000000000000000000000000000000000" ? "Create Pool" : "Swap" } ) } export default SwapContainer;