306 lines
12 KiB
JavaScript
306 lines
12 KiB
JavaScript
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 (
|
|
<Box maxWidth="356px" display="flex" flexDirection="column" justifyContent="space-between" height="100%">
|
|
<SwapCollection
|
|
UpperSwapCard={
|
|
<SwapCard
|
|
id="from"
|
|
token={<TokenStack tokens={[tokenNameTop]} sx={{ fontSize: "21px" }} />}
|
|
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={
|
|
<SwapCard
|
|
id="to"
|
|
token={<TokenStack tokens={[tokenNameBottom]} sx={{ fontSize: "21px" }} />}
|
|
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 && <Box
|
|
m="10px 0"
|
|
display="flex"
|
|
flexDirection="column"
|
|
justifyContent="space-between"
|
|
alignItems="center"
|
|
gap="0px"
|
|
maxWidth="356px"
|
|
style={{ fontSize: "12px", color: theme.colors.gray[40] }}
|
|
>
|
|
<Box width="100%" display="flex" justifyContent="space-between">
|
|
<Typography fontSize="12px" lineHeight="15px">Current Balance:</Typography>
|
|
<Typography fontSize="12px" lineHeight="15px">{formatNumber(lpBalance, formatDecimals)} LP</Typography>
|
|
</Box>
|
|
<Box width="100%" display="flex" justifyContent="space-between">
|
|
<Typography fontSize="12px" lineHeight="15px">Total Supply:</Typography>
|
|
<Typography fontSize="12px" lineHeight="15px">{formatNumber(lpTotalSupply, formatDecimals)} LP</Typography>
|
|
</Box>
|
|
<Box width="100%" display="flex" justifyContent="space-between">
|
|
<Typography fontSize="12px" lineHeight="15px">Extra Balance:</Typography>
|
|
<Typography fontSize="12px" lineHeight="15px">~{formatNumber(estimatedAmountOut, formatDecimals)} LP</Typography>
|
|
</Box>
|
|
</Box>}
|
|
<TokenAllowanceGuard
|
|
spendAmount={new DecimalBigNumber(amountTop, balanceTop._decimals)}
|
|
tokenName={tokenNameTop}
|
|
owner={address}
|
|
spender={dexAddresses.router}
|
|
decimals={balanceTop._decimals}
|
|
approvalText={"Approve " + tokenNameTop}
|
|
approvalPendingText={"Approving..."}
|
|
connect={connect}
|
|
width="100%"
|
|
height="60px"
|
|
>
|
|
<TokenAllowanceGuard
|
|
spendAmount={new DecimalBigNumber(amountBottom, balanceBottom._decimals)}
|
|
tokenName={tokenNameBottom}
|
|
owner={address}
|
|
spender={dexAddresses.router}
|
|
decimals={balanceBottom._decimals}
|
|
approvalText={"Approve " + tokenNameBottom}
|
|
approvalPendingText={"Approving..."}
|
|
connect={connect}
|
|
width="100%"
|
|
height="60px"
|
|
>
|
|
<SecondaryButton
|
|
fullWidth
|
|
disabled={
|
|
address !== "" && (
|
|
(new DecimalBigNumber(amountTop, balanceTop._decimals)).eq(new DecimalBigNumber("0", 18)) ||
|
|
(new DecimalBigNumber(amountBottom, balanceBottom._decimals)).eq(new DecimalBigNumber("0", 18)) ||
|
|
balanceTop?.lt(new DecimalBigNumber(amountTop, balanceTop._decimals)) ||
|
|
balanceBottom?.lt(new DecimalBigNumber(amountBottom, balanceBottom._decimals)) ||
|
|
isPending
|
|
)
|
|
}
|
|
loading={isPending}
|
|
onClick={() => address === "" ? connect() : addLiquidityInner()}
|
|
>
|
|
{address === "" ?
|
|
"Connect"
|
|
:
|
|
pairAddress === "0x0000000000000000000000000000000000000000" ?
|
|
"Create Pool"
|
|
:
|
|
"Add Liquidity"
|
|
}
|
|
</SecondaryButton>
|
|
</TokenAllowanceGuard>
|
|
</TokenAllowanceGuard>
|
|
</Box>
|
|
)
|
|
}
|
|
|
|
export default PoolContainer;
|