import { useState, useEffect, useMemo } 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 { DecimalBigNumber } from "../../helpers/DecimalBigNumber"; import { formatNumber, formatCurrency } from "../../helpers"; import { useBalance, useTotalSupply } from "../../hooks/tokens"; import { useUniswapV2Pair, useUniswapV2PairReserves, addLiquidity } from "../../hooks/uniswapv2"; const PoolContainer = ({ tokenNameTop, tokenNameBottom, onCardsSwap, address, chainId, dexAddresses, connect, slippage, secondsToWait, setTopTokenListOpen, setBottomTokenListOpen, formatDecimals }) => { const theme = useTheme(); const isSmallScreen = useMediaQuery("(max-width: 456px)"); const [isPending, setIsPending] = useState(false); const [amountTop, setAmountTop] = useState(""); const [amountBottom, setAmountBottom] = useState(""); const { balance: balanceTop, refetch: balanceRefetchTop, contractAddress: addressTop, } = useBalance(chainId, tokenNameTop, address); const { balance: balanceBottom, refetch: balanceRefetchBottom, contractAddress: addressBottom, } = useBalance(chainId, tokenNameBottom, address); const { pairAddress, refetch: pairAddressRefetch, } = useUniswapV2Pair(chainId, dexAddresses.factory, addressTop, addressBottom); const { balance: lpBalance, refetch: lpRefetch, } = useBalance(chainId, pairAddress, address); const { totalSupply: lpTotalSupply, refetch: lpTotalSupplyRefetch } = useTotalSupply(chainId, pairAddress); const { reserves: pairReserves, refetch: pairReservesRefetch, } = useUniswapV2PairReserves( chainId, pairAddress, balanceTop._decimals, balanceBottom._decimals, tokenNameTop, tokenNameBottom, ); const onSwap = () => { const oldAmountTop = amountTop; const oldAmountBottom = amountBottom; setAmountBottom(oldAmountTop); setAmountTop(oldAmountBottom); onCardsSwap(); } const setMaxTop = () => setAmountTop(balanceTop.toString()); const setMaxBottom = () => setAmountBottom(balanceBottom.toString()); const bigIntSqrt = (n) => { if (n < 0n) { throw new Error("Cannot compute the square root of a negative number."); } if (n < 2n) { return n; // The square root of 0 or 1 is the number itself } let low = 0n; let high = n; let mid; while (low <= high) { mid = (low + high) / 2n; const midSquared = mid * mid; if (midSquared === n) { return mid; // Found the exact square root } else if (midSquared < n) { low = mid + 1n; // Move to the right half } else { high = mid - 1n; // Move to the left half } } return high; // The integer part of the square root } const estimatedAmountOut = useMemo(() => { const zero = new DecimalBigNumber(0n, 0); const value0 = new DecimalBigNumber(amountTop, balanceTop._decimals) const value1 = new DecimalBigNumber(amountBottom, balanceBottom._decimals) const amountToAddA = new DecimalBigNumber(value0._value.toBigInt(), balanceTop._decimals); const amountToAddB = new DecimalBigNumber(value1._value.toBigInt(), balanceBottom._decimals); if ( pairReserves.reserve0.gt(zero) && pairReserves.reserve1.gt(zero) && lpTotalSupply.gt(new DecimalBigNumber(0n, 0)) ) { const lpTokensFromA = (amountToAddA.mul(lpTotalSupply).div(pairReserves.reserve0)); const lpTokensFromB = (amountToAddB.mul(lpTotalSupply).div(pairReserves.reserve1)); const lpTokensToMint = lpTokensFromA.gt(lpTokensFromB) ? lpTokensFromB : lpTokensFromA; return lpTokensToMint; } else { const tokens = bigIntSqrt(BigInt(amountToAddA.mul(amountToAddB)._value)); const lpTokensToMint = new DecimalBigNumber(tokens, 18); return lpTokensToMint; } }, [pairReserves, amountTop, amountBottom, balanceTop, balanceBottom]); useEffect(() => { if (pairReserves && pairReserves.reserve1.gt(new DecimalBigNumber(0n, 0))) { const value = new DecimalBigNumber(amountTop, balanceTop._decimals) const amountToAdd = new DecimalBigNumber(value._value.toBigInt(), balanceTop._decimals); const amount = amountToAdd.mul(pairReserves.reserve0).div(pairReserves.reserve1); setAmountBottom(amount.toString()); } }, [amountTop, pairReserves]) useEffect(() => { setAmountTop(""); setAmountBottom(""); }, [tokenNameTop, tokenNameBottom]) const addLiquidityInner = 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 amountAMin = amountADesired * bigIntSlippage / one; const amountBMin = amountBDesired * bigIntSlippage / one; await addLiquidity( chainId, tokenNameTop, tokenNameBottom, amountADesired, amountBDesired, amountAMin, amountBMin, destination, deadline, ); await balanceRefetchTop(); await balanceRefetchBottom(); await pairAddressRefetch(); await lpRefetch(); await lpTotalSupplyRefetch(); await pairReservesRefetch(); setAmountTop(""); setAmountBottom(""); setIsPending(false); } const emptyPool = useMemo(() => { return pairAddress === "" || pairAddress === "0x0000000000000000000000000000000000000000" }, [pairAddress]) return ( } tokenName={tokenNameTop} info={(!isSmallScreen ? "Balance: " : "") + formatCurrency(balanceTop, formatDecimals, tokenNameTop)} endString="Max" endStringOnClick={setMaxTop} value={amountTop} onChange={event => setAmountTop(event.currentTarget.value)} tokenOnClick={() => setTopTokenListOpen(true)} inputProps={{ "data-testid": "fromInput" }} /> } LowerSwapCard={ } tokenName={tokenNameBottom} value={amountBottom } onChange={event => emptyPool ? setAmountBottom(event.currentTarget.value) : {}} endString={emptyPool ? "Max" : undefined} endStringOnClick={emptyPool ? setMaxBottom : {}} inputProps={{ "data-testid": "toInput" }} tokenOnClick={() => setBottomTokenListOpen(true)} info={(!isSmallScreen ? "Balance: " : "") + formatCurrency(balanceBottom, formatDecimals, tokenNameBottom)} /> } arrowOnClick={onSwap} /> {!isSmallScreen && Current Balance: {formatNumber(lpBalance, formatDecimals)} LP Total Supply: {formatNumber(lpTotalSupply, formatDecimals)} LP Extra Balance: ~{formatNumber(estimatedAmountOut, formatDecimals)} LP } address === "" ? connect() : addLiquidityInner()} > {address === "" ? "Connect" : pairAddress === "0x0000000000000000000000000000000000000000" ? "Create Pool" : "Add Liquidity" } ) } export default PoolContainer;