288 lines
15 KiB
JavaScript
288 lines
15 KiB
JavaScript
import { useState, useEffect, useMemo } from "react";
|
|
import { Box, Container, Typography, useMediaQuery } from "@mui/material";
|
|
import { useConfig, useBalance } from "wagmi";
|
|
import { Helmet } from "react-helmet";
|
|
import ReactGA from "react-ga4";
|
|
|
|
import PageTitle from "../../components/PageTitle/PageTitle";
|
|
import Paper from "../../components/Paper/Paper";
|
|
import SwapCard from "../../components/Swap/SwapCard";
|
|
import TokenStack from "../../components/TokenStack/TokenStack";
|
|
import { PrimaryButton } from "../../components/Button";
|
|
import { Tab, Tabs } from "../../components/Tabs/Tabs";
|
|
|
|
import { DAI_ADDRESSES } from "../../constants/addresses";
|
|
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
|
import { formatCurrency, formatNumber } from "../../helpers";
|
|
|
|
import {
|
|
useBalance as useTokenBalance,
|
|
useTokenSymbol,
|
|
useTotalSupply,
|
|
useConversionRate,
|
|
useAccumulatedDonation,
|
|
mintDai,
|
|
burnDai
|
|
} from "../../hooks/tokens";
|
|
|
|
const Faucet = ({ chainId, address, config, connect }) => {
|
|
const isSmallScreen = useMediaQuery("(max-width: 650px)");
|
|
const isSemiSmallScreen = useMediaQuery("(max-width: 480px)");
|
|
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
|
|
|
|
const daiConversionRate = useConversionRate(chainId, "GDAI");
|
|
const accumulatedDonation = useAccumulatedDonation(chainId, "GDAI");
|
|
const { balance: daiBalance, refetch: daiBalanceRefetch } = useTokenBalance(chainId, "GDAI", address);
|
|
const { data: nativeBalance, refetch: balanceRefetch } = useBalance({ address });
|
|
const { data: contractBalance, refetch: contractBalanceRefetch } = useBalance({ address });
|
|
const { totalSupply: reserveTotalSupply, refetch: refetchReserveTotalSupply } = useTotalSupply(chainId, "GDAI");
|
|
const { symbol: faucetSymbol } = useTokenSymbol(chainId, "GDAI");
|
|
|
|
const [isMint, setIsMint] = useState(true);
|
|
const [isPending, setIsPending] = useState(false);
|
|
const [balance, setBalance] = useState(new DecimalBigNumber(0, 0));
|
|
const [amount, setAmount] = useState("");
|
|
const [scanInfo, setScanInfo] = useState({
|
|
name: "",
|
|
url: "",
|
|
});
|
|
const [nativeInfo, setNativeInfo] = useState({
|
|
decimals: 18,
|
|
name: "",
|
|
symbol: "",
|
|
})
|
|
|
|
useEffect(() => {
|
|
ReactGA.send({ hitType: "pageview", page: "/faucet" });
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
const value = nativeBalance ? nativeBalance.value : 0n;
|
|
const decimals = nativeBalance ? nativeBalance.decimals : 18;
|
|
setBalance(new DecimalBigNumber(value, decimals));
|
|
}, [nativeBalance]);
|
|
|
|
useEffect(() => {
|
|
let scanName = "";
|
|
let scanUrl = "";
|
|
|
|
const client = config?.getClient();
|
|
scanName = client?.chain?.blockExplorers?.default?.name;
|
|
scanUrl = client?.chain?.blockExplorers?.default?.url;
|
|
|
|
setScanInfo({
|
|
name: scanName,
|
|
url: scanUrl,
|
|
})
|
|
|
|
setNativeInfo(client?.chain?.nativeCurrency)
|
|
}, [chainId]);
|
|
|
|
const changeIsMinted = (value) => {
|
|
if (accumulatedDonation) {
|
|
setAmount("");
|
|
setIsMint(value);
|
|
}
|
|
}
|
|
|
|
const preparedAmount = useMemo(() => {
|
|
if (address === "") new DecimalBigNumber("0", 0);
|
|
const decimals = isMint ? nativeInfo.decimals : 18;
|
|
return new DecimalBigNumber(amount, decimals);
|
|
}, [amount, balance, nativeInfo])
|
|
|
|
const estimatedAmountIn = useMemo(() => {
|
|
const rate = new DecimalBigNumber(daiConversionRate.toString(), 0);
|
|
const value = new DecimalBigNumber(amount, nativeInfo.decimals);
|
|
return value.mul(rate);
|
|
}, [amount, daiConversionRate, nativeInfo]);
|
|
|
|
const contractBalanceFree = useMemo(() => {
|
|
const realContractBalance = contractBalance ? contractBalance : 0n;
|
|
const preparedContractBalance = new DecimalBigNumber(realContractBalance, nativeInfo.decimals);
|
|
const preparedAccumulatedDonation = new DecimalBigNumber(
|
|
accumulatedDonation._value,
|
|
accumulatedDonation._decimals + nativeInfo.decimals - 18
|
|
);
|
|
return preparedContractBalance.sub(preparedAccumulatedDonation);
|
|
}, [contractBalance, accumulatedDonation]);
|
|
|
|
const estimatedAmountOut = useMemo(() => {
|
|
const value = new DecimalBigNumber(amount, nativeInfo.decimals);
|
|
if (reserveTotalSupply._value > 0n) {
|
|
return value.mul(contractBalanceFree).div(reserveTotalSupply);
|
|
}
|
|
return new DecimalBigNumber(0n, nativeInfo.decimals);
|
|
}, [amount, contractBalanceFree, reserveTotalSupply, nativeInfo]);
|
|
|
|
const actionOrConnect = async () => {
|
|
if (address === "") {
|
|
connect();
|
|
} else {
|
|
setIsPending(true);
|
|
|
|
if (isMint) {
|
|
await mintDai(chainId, address, preparedAmount._value.toString());
|
|
} else {
|
|
await burnDai(chainId, address, preparedAmount._value.toString());
|
|
}
|
|
|
|
await balanceRefetch();
|
|
await daiBalanceRefetch();
|
|
await contractBalanceRefetch();
|
|
await refetchReserveTotalSupply();
|
|
setAmount("");
|
|
setIsPending(false);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Box height="calc(100vh - 43px)">
|
|
<Helmet>
|
|
<title>ghostFaucet | gDAI Faucet</title>
|
|
<meta name="description" content="ghostFaucet is a No KYC gDAI Faucet powered by GHOST. Get gDAI sent directly to your wallet." />
|
|
<meta name="keywords" content="ghostFaucet, web3 faucet, gDAI, ethereum, sepolia, polygon, bnb, bsc, AVAX" />
|
|
|
|
<meta property="og:image" content="https://ghostchain.io/wp-content/uploads/2025/03/ghostFaucet-Featured_Image.png" />
|
|
<meta property="og:title" content="ghostDAO | The DeFi 2.0 cross-chain reserve currency" />
|
|
<meta property="og:image:type" content="image/png" />
|
|
<meta property="og:image:width" content="1200" />
|
|
<meta property="og:image:height" content="630" />
|
|
<meta property="og:type" content="website" />
|
|
<meta property="og:description" content="ghostFaucet is a No KYC gDAI Faucet powered by GHOST. Get gDAI sent directly to your wallet." />
|
|
|
|
<meta name="twitter:card" content="summary" />
|
|
<meta name="twitter:site" content="@realGhostChain" />
|
|
<meta name="twitter:title" content="ghostDAO | The DeFi 2.0 cross-chain reserve currency" />
|
|
<meta name="twitter:description" content="ghostFaucet is a No KYC gDAI Faucet powered by GHOST. Get gDAI sent directly to your wallet." />
|
|
<meta name="twitter:image" content="https://ghostchain.io/wp-content/uploads/2025/03/ghostFaucet-Featured_Image.png" />
|
|
</Helmet>
|
|
|
|
<PageTitle name={`${faucetSymbol} Faucet`} subtitle={`Swap Sepolia ${nativeInfo.symbol} for ${faucetSymbol}.`} />
|
|
<Container
|
|
style={{
|
|
paddingLeft: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
|
|
paddingRight: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
|
|
display: "flex",
|
|
justifyContent: "center",
|
|
alignItems: "center",
|
|
height: "calc(100vh - 153px)"
|
|
}}
|
|
>
|
|
<Box width="100%" maxWidth="476px" display="flex" alignItems="center" justifyContent="center" flexDirection="column">
|
|
<Paper
|
|
headerContent={
|
|
<Box alignItems="center" justifyContent="space-between" display="flex" width="100%">
|
|
<Tabs
|
|
centered
|
|
textColor="primary"
|
|
indicatorColor="primary"
|
|
value={isMint ? 0 : 1}
|
|
aria-label="Faucet menu"
|
|
onChange={(_, view) => changeIsMinted(view === 0)}
|
|
TabIndicatorProps={{ style: { display: "none" } }}
|
|
>
|
|
<Tab aria-label="faucet-mint-button" label="Mint" style={{ fontSize: "1.5rem" }} />
|
|
{accumulatedDonation && <Tab aria-label="faucet-burn-button" label="Burn" style={{ fontSize: "1.5rem" }} />}
|
|
</Tabs>
|
|
{!isSemiSmallScreen && <PrimaryButton
|
|
variant="text"
|
|
href={`${scanInfo.url}/token/${DAI_ADDRESSES[chainId]}`}
|
|
>
|
|
Check on {scanInfo.name}
|
|
</PrimaryButton>}
|
|
</Box>
|
|
}
|
|
enableBackground
|
|
fullWidth
|
|
>
|
|
<Box>
|
|
{isMint && <SwapCard
|
|
id={`faucet-sepolia-eth`}
|
|
inputWidth={isVerySmallScreen ? "100px" : isSemiSmallScreen ? "180px" : "250px"}
|
|
tokenName={nativeInfo.symbol}
|
|
token={<TokenStack tokens={[nativeInfo.symbol]} sx={{ fontSize: "21px" }} />}
|
|
info={`${isSemiSmallScreen ? "" : "Balance: "}${formatCurrency(balance.toString(), 4, nativeInfo.symbol)}`}
|
|
value={amount}
|
|
onChange={event => setAmount(event.currentTarget.value)}
|
|
inputProps={{ "data-testid": "fromInput" }}
|
|
/>}
|
|
{!isMint && <SwapCard
|
|
id={`faucet-sepolia-eth`}
|
|
inputWidth={isVerySmallScreen ? "100px" : isSemiSmallScreen ? "180px" : "250px"}
|
|
tokenName={faucetSymbol}
|
|
token={<TokenStack tokens={[faucetSymbol]} sx={{ fontSize: "21px" }} />}
|
|
info={`${isSemiSmallScreen ? "" : "Balance: "}${formatCurrency(daiBalance.toString(), 4, faucetSymbol)}`}
|
|
value={amount}
|
|
onChange={event => setAmount(event.currentTarget.value)}
|
|
inputProps={{ "data-testid": "fromInput" }}
|
|
/>}
|
|
<Box
|
|
mb="20px"
|
|
mt="20px"
|
|
flexDirection="column"
|
|
display="flex"
|
|
justifyContent="space-between"
|
|
>
|
|
{isMint && (
|
|
<>
|
|
<Box maxWidth="416px" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
|
|
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">{nativeInfo.symbol} multiplier:</Typography>}
|
|
<Typography fontSize="12px" lineHeight="15px">{formatNumber(daiConversionRate, 2)}</Typography>
|
|
</Box>
|
|
<Box maxWidth="416px" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
|
|
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">You will get:</Typography>}
|
|
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(estimatedAmountIn, 5, faucetSymbol)}</Typography>
|
|
</Box>
|
|
<Box display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
|
|
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">Your {faucetSymbol} balance:</Typography>}
|
|
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(daiBalance, 5, faucetSymbol)}</Typography>
|
|
</Box>
|
|
</>
|
|
)}
|
|
{!isMint && (
|
|
<>
|
|
<Box maxWidth="416px" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
|
|
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">Accumulated {nativeInfo.symbol}:</Typography>}
|
|
<Typography fontSize="12px" lineHeight="15px">{formatNumber(contractBalanceFree, 5)}</Typography>
|
|
</Box>
|
|
<Box maxWidth="416px" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
|
|
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">You will get:</Typography>}
|
|
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(estimatedAmountOut, 5, nativeInfo.symbol)}</Typography>
|
|
</Box>
|
|
<Box display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
|
|
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">Your {nativeInfo.symbol} balance:</Typography>}
|
|
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(balance, 5, nativeInfo.symbol)}</Typography>
|
|
</Box>
|
|
</>
|
|
)}
|
|
</Box>
|
|
<PrimaryButton
|
|
fullWidth
|
|
disabled={
|
|
address !== "" && (
|
|
preparedAmount?._value === 0n ||
|
|
isPending ||
|
|
(isMint && balance?.lt(preparedAmount)) ||
|
|
(!isMint && daiBalance?.lt(preparedAmount))
|
|
)
|
|
}
|
|
loading={isPending}
|
|
onClick={() => actionOrConnect()}
|
|
>
|
|
{address === "" ?
|
|
"Connect"
|
|
:
|
|
isMint ? "Mint" : "Burn"
|
|
}
|
|
</PrimaryButton>
|
|
</Box>
|
|
</Paper>
|
|
</Box>
|
|
</Container>
|
|
</Box>
|
|
)
|
|
}
|
|
|
|
export default Faucet;
|