update dapp with new token names and extend faucet with burn functionality

Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
This commit is contained in:
Uncle Fatso 2025-07-25 00:34:58 +03:00
parent ad4b539961
commit 1143a3d491
Signed by: f4ts0
GPG Key ID: 565F4F2860226EBB
18 changed files with 257 additions and 59 deletions

View File

@ -1,7 +1,7 @@
{
"name": "ghost-dao-interface",
"private": true,
"version": "0.1.5",
"version": "0.1.6",
"type": "module",
"scripts": {
"dev": "vite",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -26,17 +26,25 @@ const StyledSvgIcon = styled(SvgIcon)(() => ({
const Token = ({ name, viewBox = "0 0 260 260", fontSize = "large", ...props }) => {
const parseKnownToken = (name) => {
let icon;
// TBD: should be extended on new tokens
switch (name?.toUpperCase()) {
case "FTSO":
icon = FtsoIcon;
break;
case "ECSPR":
icon = FtsoIcon;
break;
case "STNK":
icon = StnkIcon;
break;
case "SCSPR":
icon = StnkIcon;
break;
case "GHST":
icon = GhstIcon;
break;
case "CSPR":
icon = GhstIcon;
break;
case "GDAI":
icon = DaiIcon;
break;

View File

@ -1,32 +1,32 @@
import { NetworkId } from "../constants";
export const STAKING_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xb22Ad3b4a23EaEA8c06CD151D7C0e3758d0FB580",
[NetworkId.TESTNET_SEPOLIA]: "0x47b662aC17937938ff8938Fe9513beF38e60E40C",
[NetworkId.TESTNET_HOODI]: "0x25F62eDc6C89FF84E957C22336A35d2dfc861a86",
};
export const BOND_DEPOSITORY_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x8773AC3258b31D3ACfc99Ffd13768ccB170fcF9f",
[NetworkId.TESTNET_SEPOLIA]: "0xcBdad2E86a60fcfF4ecD88D7067D403710D82340",
[NetworkId.TESTNET_HOODI]: "0x6Ad50B1E293E68B2fC230c576220a93A9D311571",
};
export const DAO_TREASURY_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x2AAd1EA51044e69756880f580C13a92D910af238",
[NetworkId.TESTNET_SEPOLIA]: "0xed487AF8a6d1d1334e4b899FEb3f39402637Bc85",
[NetworkId.TESTNET_HOODI]: "0x1a1b29b18f714fac9dDabEf530dFc4f85b56A6e8",
};
export const FTSO_DAI_LP_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x64B19626bd074cf7B1019798846c363bbA8A0d53",
[NetworkId.TESTNET_HOODI]: "0xf7B2d44209E70782d93A70F7D8eC50010dF7ae50", // TBD
[NetworkId.TESTNET_SEPOLIA]: "0x0000000000000000000000000000000000000000", // TBD
[NetworkId.TESTNET_HOODI]: "0xf7B2d44209E70782d93A70F7D8eC50010dF7ae50",
};
export const FTSO_STNK_LP_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x29965676fc00C3eA9717B2A02739d294399a382e",
[NetworkId.TESTNET_SEPOLIA]: "0x0000000000000000000000000000000000000000", // TBD
[NetworkId.TESTNET_SEPOLIA]: "0x0000000000000000000000000000000000000000",
}
export const DAI_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xc7Afd3bC4c74f6E07880447b1759d5d639F2525F",
[NetworkId.TESTNET_SEPOLIA]: "0x1E392913CB9CeFAd0466D1525a9Ee144E74a233A",
[NetworkId.TESTNET_HOODI]: "0x80c6676c334BCcE60b3CC852085B72143379CE58",
};
@ -36,32 +36,32 @@ export const WETH_ADDRESSES = {
};
export const GHST_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x4643076087234d9B81974beF1eC9c25F3A0202B9",
[NetworkId.TESTNET_SEPOLIA]: "0xfd210d7ac18Bc7dcE7d72ffd99a20a9F3b44d4F4",
[NetworkId.TESTNET_HOODI]: "0xE98f7426457E6533B206e91B7EcA97aa8A258B46",
};
export const STNK_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x84060da636f5a83f2668ad238f09f8c667a1ec8b",
[NetworkId.TESTNET_SEPOLIA]: "0x0cC9868D981852C804C610176b519c48808C26a9",
[NetworkId.TESTNET_HOODI]: "0xF07e9303A9f16Afd82f4f57Fd6fca68Aa0AB6D7F",
};
export const FTSO_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x0eF2E888710E9f1d5E734f9ce30FAD40c832D5F3",
[NetworkId.TESTNET_SEPOLIA]: "0x730EEf44f2a676d4081a2F3B53D10d5e4c15C2Bc",
[NetworkId.TESTNET_HOODI]: "0xb184e423811b644A1924334E63985c259F5D0033",
};
export const DISTRIBUTOR_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xE433D078a555163dC6B53968E72418B6a1618f04",
[NetworkId.TESTNET_SEPOLIA]: "0x1A848562b86DB7Be5558C1fa8D85326b163c2fFA",
[NetworkId.TESTNET_HOODI]: "0xdF49dC81c457c6f92e26cf6d686C7a8715255842",
};
export const GHOST_GOVERNANCE_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xD40E6442Ee01c234CD8AaF335122CfbB2aec8548",
[NetworkId.TESTNET_SEPOLIA]: "0xc19f2680B1d64A507B7f3498E9B83B0A069C68Cc",
[NetworkId.TESTNET_HOODI]: "0x1B96B792840d4d19d5097ee007392Ed4d851e64F",
};
export const BONDING_CALCULATOR_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x29a6bb5De7a1049632E107544CaEF05e518451e7",
[NetworkId.TESTNET_SEPOLIA]: "0xAbE29450bAC493c6D25C467E0f809301084B6a27",
[NetworkId.TESTNET_HOODI]: "0x2635d526Ad24b98082563937f7b996075052c6Fd",
}

View File

@ -9,12 +9,21 @@ 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, useConversionRate, mintDai } from "../../hooks/tokens";
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)");
@ -22,10 +31,14 @@ const Faucet = ({ chainId, address, config, connect }) => {
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("");
@ -33,6 +46,11 @@ const Faucet = ({ chainId, address, config, connect }) => {
name: "",
url: "",
});
const [nativeInfo, setNativeInfo] = useState({
decimals: 18,
name: "",
symbol: "",
})
useEffect(() => {
ReactGA.send({ hitType: "pageview", page: "/faucet" });
@ -56,28 +74,63 @@ const Faucet = ({ chainId, address, config, connect }) => {
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);
return new DecimalBigNumber(amount, 18);
}, [amount, balance])
const decimals = isMint ? nativeInfo.decimals : 18;
return new DecimalBigNumber(amount, decimals);
}, [amount, balance, nativeInfo])
const estimatedAmount = useMemo(() => {
const estimatedAmountIn = useMemo(() => {
const rate = new DecimalBigNumber(daiConversionRate.toString(), 0);
const value = new DecimalBigNumber(amount, 18);
const value = new DecimalBigNumber(amount, nativeInfo.decimals);
return value.mul(rate);
}, [amount, daiConversionRate])
}, [amount, daiConversionRate, nativeInfo]);
const mintOrConnect = async () => {
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);
}
@ -105,7 +158,7 @@ const Faucet = ({ chainId, address, config, connect }) => {
<meta name="twitter:image" content="https://ghostchain.io/wp-content/uploads/2025/03/ghostFaucet-Featured_Image.png" />
</Helmet>
<PageTitle name={"gDAI Faucet"} subtitle="Swap Sepolia ETH for gDAI." />
<PageTitle name={`${faucetSymbol} Faucet`} subtitle={`Swap Sepolia ${nativeInfo.symbol} for ${faucetSymbol}.`} />
<Container
style={{
paddingLeft: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
@ -120,7 +173,18 @@ const Faucet = ({ chainId, address, config, connect }) => {
<Paper
headerContent={
<Box alignItems="center" justifyContent="space-between" display="flex" width="100%">
<Typography variant="h4">Get {faucetSymbol}</Typography>
<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]}`}
@ -133,16 +197,26 @@ const Faucet = ({ chainId, address, config, connect }) => {
fullWidth
>
<Box>
<SwapCard
{isMint && <SwapCard
id={`faucet-sepolia-eth`}
inputWidth={isVerySmallScreen ? "100px" : isSemiSmallScreen ? "180px" : "250px"}
tokenName={"ETH"}
token={<TokenStack tokens={["ETH"]} sx={{ fontSize: "21px" }} />}
info={`${isSemiSmallScreen ? "" : "Balance: "}${formatCurrency(balance.toString(), 4, "ETH")}`}
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"
@ -150,35 +224,56 @@ const Faucet = ({ chainId, address, config, connect }) => {
display="flex"
justifyContent="space-between"
>
{isMint && (
<>
<Box maxWidth="416px" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">ETH multiplier:</Typography>}
{!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(estimatedAmount, 5, faucetSymbol)}</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?.eq(new DecimalBigNumber(0, 18)) ||
balance?.lt(preparedAmount) ||
isPending
preparedAmount?._value === 0n ||
isPending ||
(isMint && balance?.lt(preparedAmount)) ||
(!isMint && daiBalance?.lt(preparedAmount))
)
}
loading={isPending}
onClick={() => mintOrConnect()}
onClick={() => actionOrConnect()}
>
{address === "" ?
"Connect"
:
"Mint"
isMint ? "Mint" : "Burn"
}
</PrimaryButton>
</Box>

View File

@ -28,21 +28,39 @@ const Stake = ({ chainId, address, isOpened, closeModal, connect }) => {
case (upperToken === "FTSO" && bottomToken === "STNK"):
setAction("STAKE")
break;
case (upperToken === "eCSPR" && bottomToken === "sCSPR"):
setAction("STAKE")
break;
case (upperToken === "FTSO" && bottomToken === "GHST"):
setAction("STAKE")
break;
case (upperToken === "eCSPR" && bottomToken === "CSPR"):
setAction("STAKE")
break;
case (upperToken === "STNK" && bottomToken === "FTSO"):
setAction("UNSTAKE")
break;
case (upperToken === "sCSPR" && bottomToken === "eCSPR"):
setAction("UNSTAKE")
break;
case (upperToken === "GHST" && bottomToken === "FTSO"):
setAction("UNSTAKE")
break;
case (upperToken === "CSPR" && bottomToken === "eCSPR"):
setAction("UNSTAKE")
break;
case (upperToken === "STNK" && bottomToken === "GHST"):
setAction("WRAP")
break;
case (upperToken === "sCSPR" && bottomToken === "CSPR"):
setAction("WRAP")
break;
case (upperToken === "GHST" && bottomToken === "STNK"):
setAction("UNWRAP")
break;
case (upperToken === "CSPR" && bottomToken === "sCSPR"):
setAction("UNWRAP")
break;
default:
setAction("STAKE")
}

View File

@ -22,7 +22,7 @@ const ClaimConfirmationModal = (props) => {
await forfeit(props.chainId, props.receiver);
break;
case "CLAIM":
await claim(props.chainId, props.receiver, props.outputToken === "STNK");
await claim(props.chainId, props.receiver, props.outputToken === "STNK" || props.outputToken === "sCSPR");
break;
default:
console.log("Unknown action")

View File

@ -185,8 +185,8 @@ export const StakeInputArea = ({
}
const SwapCardTemplate = (tokenName, tokenAmount, isUpper, handleModal) => {
const balance = tokenName === "STNK" ?
stnkBalance : tokenName === "FTSO" ?
const balance = tokenName === "STNK" || tokenName === "sCSPR" ?
stnkBalance : tokenName === "FTSO" || tokenName === "eCSPR" ?
ftsoBalance : ghstBalance;
let realTokenName = "";
@ -194,12 +194,21 @@ export const StakeInputArea = ({
case "FTSO":
realTokenName = ftsoSymbol;
break;
case "ECSPR":
realTokenName = ftsoSymbol;
break;
case "STNK":
realTokenName = stnkSymbol;
break;
case "SCSPR":
realTokenName = stnkSymbol;
break;
case "GHST":
realTokenName = ghstSymbol;
break;
case "CSPR":
realTokenName = ghstSymbol;
break;
}
return (

View File

@ -25,12 +25,21 @@ export const getTokenAbi = (name) => {
case "FTSO":
abi = FatsoAbi;
break;
case "ECSPR":
abi = FatsoAbi;
break;
case "STNK":
abi = StinkyAbi;
break;
case "SCSPR":
abi = StinkyAbi;
break;
case "GHST":
abi = GhostAbi;
break;
case "CSPR":
abi = GhostAbi;
break;
}
return abi;
}
@ -48,12 +57,21 @@ export const getTokenDecimals = (name) => {
case "FTSO":
decimals = 9;
break;
case "ECSPR":
decimals = 9;
break;
case "STNK":
decimals = 9;
break;
case "SCSPR":
decimals = 9;
break;
case "GHST":
decimals = 18;
break;
case "CSPR":
decimals = 18;
break;
}
return decimals;
}
@ -71,12 +89,21 @@ export const getTokenAddress = (chainId, name) => {
case "FTSO":
address = FTSO_ADDRESSES[chainId];
break;
case "ECSPR":
address = FTSO_ADDRESSES[chainId];
break;
case "STNK":
address = STNK_ADDRESSES[chainId];
break;
case "SCSPR":
address = STNK_ADDRESSES[chainId];
break;
case "GHST":
address = GHST_ADDRESSES[chainId];
break;
case "CSPR":
address = GHST_ADDRESSES[chainId];
break;
case "GDAI_FTSO":
address = FTSO_DAI_LP_ADDRESSES[chainId];
break;

View File

@ -105,6 +105,22 @@ export const useConversionRate = (chainId, name) => {
return rate;
}
export const useAccumulatedDonation = (chainId, name) => {
const contractAddress = getTokenAddress(chainId, name);
const { data: donationRaw } = useReadContract({
abi: getTokenAbi(name),
address: contractAddress,
functionName: "accumulatedDonation",
scopeKey: `accumulatedDonation-${contractAddress}-${chainId}`,
chainId: chainId,
});
const preparedDonationRaw = donationRaw ? donationRaw : 0n;
const accumulatedDonation = new DecimalBigNumber(preparedDonationRaw, 0);
return accumulatedDonation;
}
export const useCirculatingSupply = (chainId, name) => {
const contractAddress = getTokenAddress(chainId, name);
const { data: circulatingSupplyRaw } = useReadContract({
@ -188,3 +204,28 @@ export const mintDai = async (chainId, account, value) => {
toast.error("Minting gDAI from the faucet failed. Check logs for error detalization.")
}
}
export const burnDai = async (chainId, account, value) => {
try {
const { request } = await simulateContract(config, {
abi: getTokenAbi("GDAI"),
address: getTokenAddress(chainId, "GDAI"),
functionName: 'burn',
args: [value],
account: account,
chainId: chainId
});
const txHash = await writeContract(config, request);
await waitForTransactionReceipt(config, {
hash: txHash,
onReplaced: () => toast("Burn transaction was replaced. Wait for inclusion please."),
chainId
});
toast.success("gDAI successfully burned for native coins! Check your wallet balance.");
} catch (err) {
console.error(err);
toast.error("Burning gDAI from the faucet failed. Check logs for error detalization.")
}
}