change dashboard table

Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
This commit is contained in:
Uncle Fatso 2026-04-10 15:56:28 +03:00
parent be5b102522
commit e102d819a0
Signed by: f4ts0
GPG Key ID: 565F4F2860226EBB
5 changed files with 182 additions and 3 deletions

View File

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

File diff suppressed because one or more lines are too long

View File

@ -12,6 +12,7 @@ import {
CurrentIndex,
} from "./components/Metric";
import TokenInfo from "./components/TokenInfo";
import ProtocolDetails from "./components/ProtocolDetails";
import Paper from "../../components/Paper/Paper";
import PageTitle from "../../components/PageTitle/PageTitle";
@ -60,7 +61,7 @@ const MetricsDashboard = ({ chainId }) => {
</Paper>
<Paper style={{ padding: "0px" }} fullWidth={true} >
<TokenInfo isMobileScreen={isMobileScreen} chainId={chainId} />
<ProtocolDetails theme={theme} isMobileScreen={isMobileScreen} chainId={chainId} />
</Paper>
</Grid>
</Grid>

View File

@ -0,0 +1,137 @@
import { useMemo } from "react";
import { Grid, Box, Typography } from "@mui/material";
import { useConfig } from "wagmi";
import { useNavigate, createSearchParams } from "react-router-dom";
import { formatNumber } from "../../../helpers";
import { useEpoch, useGatekeeperApy } from "../../../hooks/staking";
import { useTokenSymbol, useBalance, useCirculatingSupply } from "../../../hooks/tokens";
import { SecondaryButton } from "../../../components/Button";
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
import { GATEKEEPER_ADDRESSES, EMPTY_ADDRESS } from "../../../constants/addresses";
const ProtocolDetail = ({ isMobileScreen, theme, name, sideName, url, urlParams, description }) => {
const navigate = useNavigate();
return (
<Box position="relative" width={`${isMobileScreen ? "100%" : "48%"}`}>
<Box
borderRadius="9px"
padding="12px"
sx={{ backgroundColor: theme.colors.paper.card }}
display="flex"
flexDirection="column"
justifyContent="space-between"
>
<Box display="flex" gap={"3px"} alignItems="center" justifyContent="space-between">
<Typography fontSize="20px" fontWeight="bold" lineHeight="33px">
{name}
</Typography>
<Typography color={theme.colors.primary[300]} fontSize="20px" fontWeight="bold" lineHeight="33px">
{sideName}
</Typography>
</Box>
<>
<Box my="18px">
<Typography color={theme.colors.gray[40]} mt="9px">
{description}
</Typography>
</Box>
<Box display="flex" justifyContent="center" width="100%">
<SecondaryButton
onClick={() => urlParams
? navigate({
pathname: url,
search: urlParams.toString()
})
: navigate(url, { replace: true })
}
fullWidth
>
{name}
</SecondaryButton>
</Box>
</>
</Box>
</Box>
)
}
const ProtocolDetails = ({ chainId, isMobileScreen, theme, }) => {
const config = useConfig();
const nativeSymbol = config?.getClient()?.chain?.nativeCurrency?.symbol;
const networkName = config?.getClient()?.chain?.name;
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
const { symbol: csprSymbol } = useTokenSymbol(chainId, "CSPR");
const { contractAddress: ftsoAddress } = useBalance(chainId, "FTSO", EMPTY_ADDRESS);
const circulatingSupply = useCirculatingSupply(chainId, "STNK");
const gatekeepedApy = useGatekeeperApy(chainId);
const { epoch } = useEpoch(chainId);
const apyInner = useMemo(() => {
let apy = Infinity;
if (circulatingSupply._value > 0n) {
const value = epoch.distribute.div(circulatingSupply);
apy = 100 * (Math.pow(1 + parseFloat(value.toString()), 1095) - 1);
if (apy === 0) apy = Infinity;
}
return apy;
}, [circulatingSupply, epoch]);
const bridgeNumbers = useMemo(() => {
const connectedNetworks = Object.keys(GATEKEEPER_ADDRESSES).length;
const number = 1 + connectedNetworks * 3;
return `(${number}, ${number})`;
}, [chainId]);
return (
<Grid container spacing={0} justifyContent={"center"}>
<Box display="flex" flexWrap="wrap" justifyContent="space-between" mt="10px" gap="25px">
<ProtocolDetail
isMobileScreen={isMobileScreen}
theme={theme}
url={`/${networkName.toLowerCase()}/dex/uniswap`}
urlParams={createSearchParams({
from: EMPTY_ADDRESS,
to: `${ftsoAddress}`,
})}
name="(3, 3) Swap"
sideName="Unlock Magic"
description={`Buying strategy expands bond capacity triggering a positive loop to strengthen the Treasury and attract more stakers. Swap ${nativeSymbol} for ${ftsoSymbol} to unlock (3, 3) Stake.`}
/>
<ProtocolDetail
isMobileScreen={isMobileScreen}
theme={theme}
url={`/${networkName.toLowerCase()}/bonds`}
name="(1, 1) Bond"
sideName="Up to 40% Discount"
description={`Bonding strategy grows Treasury and builds Protocol Owned Liquidity (POL) by allowing usersto acquire ${csprSymbol} at a discount. Take advantage of the next available bond.`}
/>
<ProtocolDetail
isMobileScreen={isMobileScreen}
theme={theme}
url={`/${networkName.toLowerCase()}/stake`}
name="(3, 3) Stake"
sideName={`${formatNumber(apyInner, 0)}% APY`}
description={`Staking enables (3, 3) coordination by aligning long-term incentives, sustainably backed (1, 1) Bonds, LP fees, and Farming. Stake ${ftsoSymbol} to earn rewards and unlock ${bridgeNumbers} Stake\u00B2.`}
/>
<ProtocolDetail
isMobileScreen={isMobileScreen}
theme={theme}
url={`/${networkName.toLowerCase()}/bridge`}
name={`${bridgeNumbers} Stake\u00B2`}
sideName={`${formatNumber(apyInner * gatekeepedApy, 0)}% APY`}
description={`Staking\u00B2 strategy further deepens long-term incentives powered by sustainable cross-chain bridging revenue. ${bridgeNumbers} Stake\u00B2 your ${csprSymbol} to receive organic APY with no warmup and dillution.`}
/>
</Box>
</Grid>
)
}
export default ProtocolDetails;

View File

@ -5,12 +5,52 @@ import toast from "react-hot-toast";
import { config } from "../../config";
import { isNetworkLegacyType } from "../../constants";
import { STAKING_ADDRESSES } from "../../constants/addresses";
import { STAKING_ADDRESSES, GATEKEEPER_ADDRESSES } from "../../constants/addresses";
import { abi as StakingAbi } from "../../abi/GhostStaking.json";
import { abi as GatekeeperAbi } from "../../abi/GhostGatekeeper.json";
import { shorten } from "../../helpers";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
export const useGatekeeperApy = (chainId) => {
const { data: gatekeeper, refetch } = useReadContract({
abi: StakingAbi,
address: STAKING_ADDRESSES[chainId],
functionName: "gatekeeper",
scopeKey: `gatekeeper-${chainId}`,
chainId: chainId,
});
const { data: metadata, error } = useReadContract({
abi: GatekeeperAbi,
address: GATEKEEPER_ADDRESSES[chainId],
functionName: "gatekeeperMetadata",
scopeKey: `gatekeeperMetadata-${chainId}`,
chainId: chainId,
});
const amountIn = new DecimalBigNumber(metadata?.amountIn ?? 0n, 18);
const amountOut = new DecimalBigNumber(metadata?.amountOut ?? 0n, 18);
const deployedAt = metadata?.deployedAt ?? 0;
const unixSeconds = Math.floor(Date.now() / 1000);
const power = 365 * 86400 / (unixSeconds - deployedAt);
const feeIn = new DecimalBigNumber(6900n, 2);
const feeOut = new DecimalBigNumber(6900n, 2);
const stakeRatio = new DecimalBigNumber(69000n, 3);
const numerator = amountIn.mul(feeIn).add(amountOut.mul(feeOut));
const denominator = amountIn.mul(stakeRatio).sub(amountOut.mul(stakeRatio));
if (denominator?.toString() === "0") {
return 1;
}
const result = Number(numerator.div(denominator).toString());
return Math.pow(1 + result, power);
}
export const useCurrentIndex = (chainId) => {
const { data: index, refetch } = useReadContract({
abi: StakingAbi,