governance rev.5

Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
This commit is contained in:
Uncle Fatso 2026-02-18 11:17:08 +03:00
parent 1239b81889
commit ad55c04525
Signed by: f4ts0
GPG Key ID: 565F4F2860226EBB
20 changed files with 1075 additions and 273 deletions

View File

@ -1,7 +1,7 @@
{
"name": "ghost-dao-interface",
"private": true,
"version": "0.5.14",
"version": "0.5.15",
"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

View File

@ -319,6 +319,7 @@ const Bridge = ({ chainId, address, config, connect }) => {
handleButtonProceed={handleButtonProceed}
/>
<BridgeModal
providerDetail={providerDetail}
currentRecord={currentRecord}
activeTxIndex={activeTxIndex}
setActiveTxIndex={setActiveTxIndex}

View File

@ -19,12 +19,13 @@ import ContentPasteIcon from '@mui/icons-material/ContentPaste';
import InfoTooltip from "../../components/Tooltip/InfoTooltip";
import Modal from "../../components/Modal/Modal";
import GhostStyledIcon from "../../components/Icon/GhostIcon";
import { PrimaryButton } from "../../components/Button";
import { PrimaryButton, TertiaryButton } from "../../components/Button";
import { formatCurrency } from "../../helpers";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
export const BridgeModal = ({
providerDetail,
currentRecord,
activeTxIndex,
setActiveTxIndex,
@ -88,7 +89,15 @@ export const BridgeModal = ({
minHeight={"100px"}
>
<Box display="flex" gap="1.5rem" flexDirection="column" marginTop=".8rem">
<Box display="flex" flexDirection="row" justifyContent="space-between" alignItems="center">
{!providerDetail && <Box display="flex" flexDirection="row" justifyContent="space-between" alignItems="center">
<TertiaryButton
fullWidth
onClick={() => window.open('https://git.ghostchain.io/ghostchain/ghost-extension-wallet/releases', '_blank', 'noopener,noreferrer')}
>
Get GHOST Connect
</TertiaryButton>
</Box>}
{providerDetail && <Box display="flex" flexDirection="row" justifyContent="space-between" alignItems="center">
{currentRecord?.finalization > 0 && (
<>
<Box
@ -276,7 +285,7 @@ export const BridgeModal = ({
</Box>
</>
)}
</Box>
</Box>}
<Box display="flex" flexDirection="column" gap="5px" padding="0.6rem 0">
<Box display="flex" flexDirection="row" justifyContent="space-between">

View File

@ -105,11 +105,11 @@ export const ValidatorTable = ({
<Box width="100%" height="340px" display="flex" flexDirection="column" justifyContent="space-between" gap="10px">
{!providerDetail && <Box sx={{ borderRadius: "15px", background: theme.colors.paper.background }} width="100%" height="100%" display="flex" justifyContent="center">
<Box padding="20px 30px" display="flex" flexDirection="column" justifyContent="space-around" alignItems="center">
<Typography sx={{ textAlign: "center" }} variant="h6">GHOST Wallet is not detected on your browser!</Typography>
<Typography sx={{ textAlign: "center" }} variant="body2">Download GHOST Wallet Extension for real-time visibility into validator status and related transaction risks.</Typography>
<Typography sx={{ textAlign: "center" }} variant="body2"><b>Important:</b> The GHOST Wallet Extension is optional, but be aware that your bridge transaction will succeed or fail irreversibly based on the condition of the validators.</Typography>
<Typography sx={{ textAlign: "center" }} variant="h6">GHOST Connect is not detected on your browser!</Typography>
<Typography sx={{ textAlign: "center" }} variant="body2">Download GHOST Connect browser extension for real-time visibility into validator status and related transaction risks.</Typography>
<Typography sx={{ textAlign: "center" }} variant="body2"><b>Important:</b> The GHOST Connect is optional, but be aware that your bridge transaction will succeed or fail irreversibly based on the condition of the validators.</Typography>
<PrimaryButton onClick={() => window.open('https://git.ghostchain.io/ghostchain/ghost-extension-wallet/releases', '_blank', 'noopener,noreferrer')}>
Get GHOST Extension
Get GHOST Connect
</PrimaryButton>
</Box>
</Box>}

View File

@ -46,6 +46,7 @@ const SwapContainer = ({
refetch: balanceRefetchTop,
contractAddress: addressTop,
} = useBalance(chainId, tokenNameTop, address);
const {
balance: balanceBottom,
refetch: balanceRefetchBottom,

View File

@ -67,11 +67,7 @@ const Governance = ({ connect, config, address, chainId }) => {
</Grid>
</Grid>
<Divider sx={{ marginTop: "30px" }} />
<Box display="flex" justifyContent="center">Redeem Collateral</Box>
<Divider />
<Box mt="15px" display="flex" flexDirection="column" alignItems="center" justifyContent="center">
<Box mt="45px" display="flex" flexDirection="column" alignItems="center" justifyContent="center">
<PrimaryButton
fullWidth
onClick={() => navigate(`/governance/create`)}
@ -84,7 +80,7 @@ const Governance = ({ connect, config, address, chainId }) => {
</Box>
</Box>
</Paper>
<ProposalsList config={config} chainId={chainId} />
<ProposalsList address={address} config={config} chainId={chainId} />
</Box>
</Container>
</Box>

View File

@ -1,5 +1,4 @@
import { useState, useMemo } from "react";
import toast from "react-hot-toast";
import { useState, useMemo, useCallback, useEffect } from "react";
import {
Box,
@ -21,14 +20,20 @@ import {
import GhostStyledIcon from "../../components/Icon/GhostIcon";
import ArrowUpIcon from "../../assets/icons/arrow-up.svg?react";
import { GHOST_GOVERNANCE_ADDRESSES, GHST_ADDRESSES } from "../../constants/addresses";
import Paper from "../../components/Paper/Paper";
import PageTitle from "../../components/PageTitle/PageTitle";
import { PrimaryButton, TertiaryButton } from "../../components/Button";
import { useTokenSymbol } from "../../hooks/tokens";
import { TokenAllowanceGuard } from "../../components/TokenAllowanceGuard/TokenAllowanceGuard";
import { useTokenSymbol, useBalance } from "../../hooks/tokens";
import { useProposalThreshold, useProposalHash, propose } from "../../hooks/governance";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import ProposalModal from "./components/ProposalModal";
import { parseFunctionCalldata } from "./components/functions/index";
import { MY_PROPOSALS_PREFIX, VOTED_PROPOSALS_PREFIX } from "./helpers";
const NewProposal = ({ config, address, connect, chainId }) => {
const isSemiSmallScreen = useMediaQuery("(max-width: 745px)");
@ -37,23 +42,51 @@ const NewProposal = ({ config, address, connect, chainId }) => {
const theme = useTheme();
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
const myStoredProposals = localStorage.getItem(MY_PROPOSALS_PREFIX);
const [myProposals, setMyProposals] = useState(
myStoredProposals ? JSON.parse(myStoredProposals).map(id => BigInt(id)) : []
);
const [isPending, setIsPending] = useState(false);
const [isModalOpened, setIsModalOpened] = useState(false);
const [proposalFunctions, setProposalFunctions] = useState([]);
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
const { balance: ghstBalance } = useBalance(chainId, ghstSymbol, address)
const { threshold } = useProposalThreshold(chainId, ghstSymbol);
const {
proposalHash,
proposalDescription
} = useProposalHash(chainId, proposalFunctions);
useEffect(() => {
const toStore = JSON.stringify(myProposals.map(id => id.toString()));
localStorage.setItem(MY_PROPOSALS_PREFIX, toStore);
}, [myProposals]);
const addCalldata = (calldata) => setProposalFunctions(prev => [...prev, calldata]);
const removeCalldata = (index) => setProposalFunctions(prev => prev.filter((_, i) => i !== index));
const storeProposal = (proposalId) => setMyProposals(prev => [...prev, proposalId]);
const removeProposal = (proposalId) => setMyProposals(prev => prev.filter(item => item !== proposalId));
const nativeCurrency = useMemo(() => {
const client = config?.getClient();
return client?.chain?.nativeCurrency?.symbol;
}, [config]);
const submitProposal = () => {
toast.success("Coming soon! It's already connected to the chain data!", { duration: 5000 });
setProposalFunctions([]);
}
const submitProposal = useCallback(async () => {
setIsPending(true);
const result = await propose(chainId, address, proposalFunctions, proposalDescription);
if (result) {
storeProposal(proposalHash);
setProposalFunctions([]);
}
setIsPending(false);
}, [chainId, address, proposalHash, proposalFunctions, proposalDescription]);
return (
<>
@ -88,6 +121,11 @@ const NewProposal = ({ config, address, connect, chainId }) => {
</Typography>
</Box>
}
topRight={
<PrimaryButton variant="text" href="http://ghostchain.io/governance">
Explore Governance
</PrimaryButton>
}
>
<Box>
<Box>
@ -101,21 +139,45 @@ const NewProposal = ({ config, address, connect, chainId }) => {
marginTop="25px"
>
<Typography variant="subtitle1">
Create new proposal by adding functions below
Create new proposal by adding one or more of the functions below.
</Typography>
<PrimaryButton
variant="text"
href="https://forum.ghostchain.io"
>
Learn more
</PrimaryButton>
</Box>}
</Box>
<Box display="flex" flexDirection="column" alignItems="center">
<TertiaryButton sx={{ maxWidth: isSemiSmallScreen ? "100%" : "350px" }} fullWidth onClick={() => setIsModalOpened(true)}>Add New</TertiaryButton>
<PrimaryButton sx={{ maxWidth: isSemiSmallScreen ? "100%" : "350px" }} fullWidth onClick={() => submitProposal()}>Submit Proposal</PrimaryButton>
<Box sx={{ width: isSemiSmallScreen ? "100%" : "350px" }}>
<TokenAllowanceGuard
spendAmount={threshold}
tokenName={ghstSymbol}
owner={address}
spender={GHOST_GOVERNANCE_ADDRESSES[chainId]}
decimals={ghstBalance._decimals}
approvalText={`Approve ${ghstSymbol}`}
approvalPendingText={"Approving..."}
connect={connect}
isVertical
>
<PrimaryButton
disabled={
proposalFunctions.length === 0 ||
ghstBalance.lt(threshold) ||
isPending
}
fullWidth
onClick={() => submitProposal()}
>
{isPending ? "Submitting..." : "Submit Proposal"}
</PrimaryButton>
</TokenAllowanceGuard>
</Box>
<TertiaryButton
sx={{ maxWidth: isSemiSmallScreen ? "100%" : "350px" }}
fullWidth
disabled={isPending}
onClick={() => setIsModalOpened(true)}
>
Add New
</TertiaryButton>
</Box>
</Box>
</Paper>

View File

@ -1,35 +1,57 @@
import { useEffect, useState, useMemo } from "react";
import ReactGA from "react-ga4";
import { useParams } from 'react-router-dom';
import { useBlock, useBlockNumber } from 'wagmi'
import { Box, Container, Typography, Link, useMediaQuery, useTheme } from "@mui/material";
import {
Box,
Container,
Typography,
Link,
TableContainer,
Table,
TableHead,
TableRow,
TableCell,
TableBody,
useMediaQuery,
useTheme
} from "@mui/material";
import Paper from "../../components/Paper/Paper";
import PageTitle from "../../components/PageTitle/PageTitle";
import LinearProgressBar from "../../components/Progress/LinearProgressBar";
import InfoTooltip from "../../components/Tooltip/InfoTooltip";
import Chip from "../../components/Chip/Chip";
import { SecondaryButton } from "../../components/Button";
import { PrimaryButton, SecondaryButton } from "../../components/Button";
import GhostStyledIcon from "../../components/Icon/GhostIcon";
import ArrowUpIcon from "../../assets/icons/arrow-up.svg?react";
import { formatNumber } from "../../helpers";
import { formatNumber, shorten } from "../../helpers";
import { prettifySecondsInDays } from "../../helpers/timeUtil";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { DecimalBigNumber, } from "../../helpers/DecimalBigNumber";
import { convertStatusToTemplate } from "./helpers";
import { convertStatusToTemplate, convertStatusToLabel } from "./helpers";
import { parseFunctionCalldata } from "./components/functions";
import { useTokenSymbol, useTotalSupply, useBalance } from "../../hooks/tokens";
import { networkAvgBlockSpeed } from "../../constants";
import { formatCurrency } from "../../helpers";
import { useTokenSymbol, usePastTotalSupply, usePastVotes, useBalance } from "../../hooks/tokens";
import {
useProposalStatus,
useProposalState,
useProposalProposer,
useProposalLocked,
useProposalQuorum,
useProposalVoteOf,
useProposalVotes,
useProposalDetails,
useProposalSnapshot,
useProposalDeadline,
useProposalVotingDelay
useProposalVotingDelay,
castVote,
executeProposal,
releaseLocked
} from "../../hooks/governance";
///////////////////////////////////////////////////////
@ -48,8 +70,11 @@ import RepeatIcon from '@mui/icons-material/Repeat';
const HUNDRED = new DecimalBigNumber(100n, 0);
const ProposalDetails = ({ chainId, address }) => {
const ProposalDetails = ({ chainId, address, connect, config }) => {
const { id } = useParams();
const proposalId = BigInt(id);
const [isPending, setIsPending] = useState(false);
const [selectedDiscussionUrl, setSelectedDiscussionUrl] = useState(undefined);
const isSemiSmallScreen = useMediaQuery("(max-width: 745px)");
@ -60,22 +85,24 @@ const ProposalDetails = ({ chainId, address }) => {
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
const { balance } = useBalance(chainId, "GHST", address);
const { totalSupply } = useTotalSupply(chainId, "GHST"); // TODO: revisit
const { status: proposalStatus } = useProposalStatus(chainId, id);
const { proposer: proposalProposer } = useProposalProposer(chainId, id);
const { locked: proposalLocked } = useProposalLocked(chainId, id);
const { quorum: proposalQuorum } = useProposalQuorum(chainId, id);
const { forVotes, againstVotes, totalVotes } = useProposalVotes(chainId, id);
const { state: proposalState } = useProposalState(chainId, proposalId);
const { proposer: proposalProposer } = useProposalProposer(chainId, proposalId);
const { locked: proposalLocked } = useProposalLocked(chainId, proposalId);
const { quorum: proposalQuorum } = useProposalQuorum(chainId, proposalId);
const { voteOf } = useProposalVoteOf(chainId, proposalId, address);
const { forVotes, againstVotes, totalVotes } = useProposalVotes(chainId, proposalId);
const { proposalDetails } = useProposalDetails(chainId, proposalId);
const { snapshot: proposalSnapshot } = useProposalSnapshot(chainId, proposalId);
const { pastTotalSupply: totalSupply } = usePastTotalSupply(chainId, "GHST", proposalSnapshot);
const { pastVotes } = usePastVotes(chainId, "GHST", proposalSnapshot, address);
useEffect(() => {
ReactGA.send({ hitType: "pageview", page: `/governance/${id}` });
}, []);
const isDiscussionModalOpened = useMemo(() => {
return selectedDiscussionUrl !== undefined;
}, [selectedDiscussionUrl]);
const quorumPercentage = useMemo(() => {
if (totalSupply._value === 0n) return 0;
return proposalQuorum / totalSupply * HUNDRED;
@ -83,17 +110,78 @@ const ProposalDetails = ({ chainId, address }) => {
const votePercentage = useMemo(() => {
if (totalSupply._value === 0n) return 0;
return totalVotes / totalSupply * HUNDRED;
const value = (totalVotes?._value ?? 0n) * 100n / (totalSupply?._value ?? 1n);
return new DecimalBigNumber(value, 0);
}, [totalVotes, totalSupply]);
const voteWeightPercentage = useMemo(() => {
if (totalSupply._value === 0n) return 0;
return balance / totalSupply * HUNDRED;
}, [balance, totalSupply]);
const value = (pastVotes?._value ?? 0n) * 100n / (totalSupply?._value ?? 1n);
return new DecimalBigNumber(value, 0);
}, [pastVotes, totalSupply]);
const voteValue = useMemo(() => {
if (totalVotes?._value == 0n) {
return 0;
}
return Number(forVotes._value * 100n / totalVotes._value);
}, [forVotes, totalVotes]);
const voteTarget = useMemo(() => {
const first = (5n * againstVotes._value + forVotes._value);
const second = BigInt(Math.sqrt(Number(totalVotes._value)));
const bias = 3n * first + second;
const denominator = totalVotes._value + bias;
if (denominator === 0n) {
return 10;
}
return Number(totalVotes?._value / denominator) + 1;
}, [againstVotes, forVotes, totalVotes]);
const nativeCurrency = useMemo(() => {
const client = config?.getClient();
return client?.chain?.nativeCurrency?.symbol;
}, [config]);
const etherscanLink = useMemo(() => {
const client = config.getClient();
let url = client?.chain?.blockExplorers?.default?.url;
if (url) {
url = url + `/address/${proposalProposer}`;
}
return url;
}, [proposalProposer, config]);
const handleVote = async (against) => {
setIsPending(true);
const support = against ? 0 : 1;
await castVote(chainId, address, proposalId, support);
setIsPending(true);
}
const handleExecute = async () => {
setIsPending(true);
await executeProposal(chainId, address, proposalId);
setIsPending(true);
}
const handleRelease = async (proposalId) => {
setIsPending(true);
await releaseLocked(chainId, address, proposalId);
setIsPending(true);
}
return (
<Box>
<PageTitle name={`GBP: ${id} - NAME`} subtitle={`By: ${proposalProposer} | BONDED: $${proposalLocked} ${ghstSymbol}`} />
<PageTitle
name={`GBP-${id.slice(-5)}`}
subtitle={
<Typography component="span">
Proposal details, need more in-depth description
</Typography>
}
/>
<Container
style={{
paddingLeft: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
@ -116,31 +204,18 @@ const ProposalDetails = ({ chainId, address }) => {
</Typography>
<Chip
sx={{ marginTop: "4px", width: "88px" }}
label={proposalStatus}
template={convertStatusToTemplate(proposalStatus)}
label={convertStatusToLabel(proposalState)}
template={convertStatusToTemplate(proposalState)}
/>
</Box>
}
topRight={
<Link
color={theme.colors.primary[300]}
href="https://forum.ghostchain.io"
target="_blank"
rel="noopener noreferrer"
display="flex"
alignItems="center"
>
View Forum&nbsp;
<GhostStyledIcon
style={{ marginTop: "7px" }}
viewBox="0 0 30 30"
className="external-site-link-icon"
component={ArrowUpIcon}
/>
</Link>
<PrimaryButton sx={{ padding: "0 !important"}} variant="text" href={"https://forum.ghostchain.io"} >
View Forum
</PrimaryButton>
}
>
<Box height="200px" display="flex" flexDirection="column" justifyContent="space-between" gap="20px">
<Box height="280px" display="flex" flexDirection="column" justifyContent="space-between" gap="20px">
<Box display="flex" flexDirection="column">
<Box display="flex" justifyContent="space-between">
<Typography sx={{ textShadow: "0 0 black", fontWeight: 600 }} variant="body1" color={theme.colors.feedback.success}>
@ -154,12 +229,25 @@ const ProposalDetails = ({ chainId, address }) => {
barColor={theme.colors.feedback.success}
barBackground={theme.colors.feedback.error}
variant="determinate"
value={69}
target={Math.floor(Math.random() * 101)}
value={voteValue}
target={voteTarget}
/>
</Box>
<Box display="flex" flexDirection="column">
<Box display="flex" justifyContent="space-between">
<Typography>Proposer</Typography>
<Link href={etherscanLink} target="_blank" rel="noopener noreferrer">
{`${shorten(proposalProposer)}`}
</Link>
</Box>
<Box display="flex" justifyContent="space-between">
<Typography>Locked</Typography>
<Typography>{formatCurrency(proposalLocked, 4, ghstSymbol)}</Typography>
</Box>
<hr width="100%" />
<Box display="flex" justifyContent="space-between">
<Box display="flex" flexDirection="row">
<Typography>Quorum</Typography>
@ -181,13 +269,37 @@ const ProposalDetails = ({ chainId, address }) => {
<Typography>Votes</Typography>
<InfoTooltip message={`Your voting power, as percentage of total $${ghstSymbol} at the time when proposal was created`} />
</Box>
<Typography>{formatNumber(balance.toString(), 4)} ({formatNumber(voteWeightPercentage, 1)}%)</Typography>
<Typography>{formatNumber(pastVotes.toString(), 4)} ({formatNumber(voteWeightPercentage, 1)}%)</Typography>
</Box>
</Box>
<Box display="flex" gap="20px">
<SecondaryButton fullWidth onClick={() => alert("For vote casted")}>For</SecondaryButton>
<SecondaryButton fullWidth onClick={() => alert("Against vote casted")}>Against</SecondaryButton>
{address === undefined || address === ""
? <PrimaryButton fullWidth onClick={() => connect()}>Connect</PrimaryButton>
: voteOf === 0n
? <>
<SecondaryButton
fullWidth
disabled={proposalState !== 1 || pastVotes?._value === 0n || isPending}
onClick={() => handleVote(1)}
>
For
</SecondaryButton>
<SecondaryButton
fullWidth
disabled={proposalState !== 1 || pastVotes?._value === 0n || isPending}
onClick={() => handleVote(0)}
>
Against
</SecondaryButton>
</>
: <SecondaryButton
fullWidth
disabled
>
{`Voted ${voteOf === 1n ? "Against" : "For"}`}
</SecondaryButton>
}
</Box>
</Box>
</Paper>
@ -196,14 +308,24 @@ const ProposalDetails = ({ chainId, address }) => {
fullWidth
enableBackground
headerContent={
<Box display="flex" alignItems="center" flexDirection="row" gap="5px">
<Box height="48px" display="flex" alignItems="center" flexDirection="row" gap="5px">
<Typography variant="h6">
Timeline
</Typography>
</Box>
}
>
<VotingTimeline chainId={chainId} proposalId={id} />
<VotingTimeline
proposalLocked={proposalLocked}
connect={connect}
handleExecute={handleExecute}
handleRelease={handleRelease}
state={proposalState}
address={address}
isProposer={proposalProposer === address}
chainId={chainId}
proposalId={id}
/>
</Paper>
</Box>
</Box>
@ -219,14 +341,28 @@ const ProposalDetails = ({ chainId, address }) => {
</Box>
}
>
Here will be a list of decoded calldatas
<TableContainer>
<Table aria-label="Available bonds" style={{ tableLayout: "fixed" }}>
<TableHead>
<TableRow>
<TableCell align="center" style={{ padding: "8px 0" }}>Function</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>Target</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>Calldata</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>Value</TableCell>
</TableRow>
</TableHead>
<TableBody>{proposalDetails?.map((metadata, index) => {
return parseFunctionCalldata(metadata, index, chainId, nativeCurrency);
})}</TableBody>
</Table>
</TableContainer>
</Paper>
</Container>
</Box>
)
}
const VotingTimeline = ({ proposalId, chainId }) => {
const VotingTimeline = ({ connect, handleExecute, handleRelease, proposalLocked, proposalId, chainId, state, address, isProposer }) => {
const { delay: propsalVotingDelay } = useProposalVotingDelay(chainId, proposalId);
const { snapshot: proposalSnapshot } = useProposalSnapshot(chainId, proposalId);
const { deadline: proposalDeadline } = useProposalDeadline(chainId, proposalId);
@ -235,28 +371,69 @@ const VotingTimeline = ({ proposalId, chainId }) => {
if (proposalSnapshot && propsalVotingDelay) {
return proposalSnapshot > propsalVotingDelay ? proposalSnapshot - propsalVotingDelay : 0;
}
return 0;
return 0n;
}, [proposalSnapshot, propsalVotingDelay]);
return (
<Timeline sx={{ margin: 0, padding: 0 }}>
<VotingTimelineItem time={voteStarted} message="Proposed on:" isFirst />
<VotingTimelineItem time={proposalSnapshot} message="Voting started:" />
<VotingTimelineItem time={proposalDeadline} message="Voting ends:" isLast />
</Timeline>
<Box height="280px" display="flex" flexDirection="column" justifyContent="space-between" gap="20px">
<Timeline sx={{ margin: 0, padding: 0 }}>
<VotingTimelineItem chainId={chainId} occured={voteStarted} message="Proposed on" isFirst />
<VotingTimelineItem chainId={chainId} occured={proposalSnapshot} message="Voting started" />
<VotingTimelineItem chainId={chainId} occured={proposalDeadline} message="Voting ends" />
</Timeline>
<Box width="100%" display="flex" gap="10px">
{isProposer && <SecondaryButton
fullWidth
disabled={(proposalLocked?._value ?? 0n) === 0n || state < 2}
onClick={() => address === "" ? connect() : handleRelease()}
>
{address === "" ? "Connect" : "Release"}
</SecondaryButton>}
<SecondaryButton
fullWidth
disabled={state !== 4}
onClick={() => address === "" ? connect() : handleExecute()}
>
{address === "" ? "Connect" : "Execute"}
</SecondaryButton>
</Box>
</Box>
)
}
const VotingTimelineItem = ({ isFirst, isLast, time, message }) => {
const VotingTimelineItem = ({ position, isFirst, isLast, chainId, occured, message }) => {
const { data: blockNumber } = useBlockNumber({ chainId, watch: true });
const { data: blockInfo } = useBlock({
chainId: chainId,
blockNumber: occured,
query: {
enabled: Boolean(occured)
}
});
const timestamp = useMemo(() => {
if (blockInfo && blockNumber > occured) {
const timestamp = Number(blockInfo?.timestamp ?? 0n) * 1000;
return new Date(timestamp).toLocaleString();
}
const blocksRemaining = Number(occured) - Number(blockNumber);
const secondsRemaining = blocksRemaining * Number(networkAvgBlockSpeed(chainId));
const predictedTimestamp = Math.floor(Date.now() / 1000) + secondsRemaining;
return new Date(predictedTimestamp * 1000).toLocaleString();
}, [chainId, occured, blockInfo, blockNumber]);
return (
<TimelineItem>
<TimelineOppositeContent
sx={{
maxWidth: "120px",
textAlign: "left",
paddingLeft: "0",
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
padding: "0 16px",
minWidth: "140px",
flex: 0,
}}
>
<Typography>{message}</Typography>
@ -272,11 +449,11 @@ const VotingTimelineItem = ({ isFirst, isLast, time, message }) => {
sx={{
display: "flex",
alignItems: "center",
justifyContent: "flex-start",
padding: "0 26px",
}}
>
<Typography>
{new Date(time * 1000).toLocaleString()}
</Typography>
<Typography>{timestamp}</Typography>
</TimelineContent>
</TimelineItem>
)

View File

@ -55,7 +55,7 @@ export const ProposalsCount = props => {
const _props = {
...props,
label: `Proposal Count`,
tooltip: `Total proposals created`,
tooltip: `Total proposals created from current governor of ghostDAO`,
};
if (proposalCount || proposalCount === 0n) _props.metric = `${formatNumber(proposalCount.toString(), 0)}`;

View File

@ -22,7 +22,7 @@ import GhostStyledIcon from "../../../components/Icon/GhostIcon";
import ArrowUpIcon from "../../../assets/icons/arrow-up.svg?react";
import { networkAvgBlockSpeed } from "../../../constants";
import { prettifySecondsInDays } from "../../../helpers/timeUtil";
import { prettifySecondsInDays, prettifySeconds } from "../../../helpers/timeUtil";
import Chip from "../../../components/Chip/Chip";
import Modal from "../../../components/Modal/Modal";
@ -31,7 +31,12 @@ import LinearProgressBar from "../../../components/Progress/LinearProgressBar";
import { PrimaryButton, TertiaryButton } from "../../../components/Button";
import ProposalInfoText from "./ProposalInfoText";
import { convertStatusToTemplate } from "../helpers";
import {
convertStatusToTemplate,
convertStatusToLabel,
MY_PROPOSALS_PREFIX,
VOTED_PROPOSALS_PREFIX
} from "../helpers";
import { useScreenSize } from "../../../hooks/useScreenSize";
@ -41,29 +46,40 @@ import {
const MAX_PROPOSALS_TO_SHOW = 10;
const ProposalsList = ({ chainId, config }) => {
const ProposalsList = ({ chainId, address, config }) => {
const isSmallScreen = useScreenSize("md");
const navigate = useNavigate();
const theme = useTheme();
const [blockNumber, setBlockNumber] = useState(0n);
const [proposalsFilter, setProposalFilter] = useState("active");
const { proposals } = useProposals(chainId, MAX_PROPOSALS_TO_SHOW);
const myStoredProposals = localStorage.getItem(MY_PROPOSALS_PREFIX);
const [myProposals, setMyProposals] = useState(
myStoredProposals ? JSON.parse(myStoredProposals).map(id => BigInt(id)) : []
);
const storedVotedProposals = localStorage.getItem(VOTED_PROPOSALS_PREFIX);
const [votedProposals, setVotedProposals] = useState(
storedVotedProposals ? JSON.parse(storedVotedProposals).map(id => BigInt(id)) : []
);
const searchedIndexes = useMemo(() => {
switch (proposalsFilter) {
case "voted":
return votedProposals;
case "created":
return myProposals;
default:
return undefined;
}
}, [proposalsFilter]);
const [blockNumber, setBlockNumber] = useState(0n);
const { proposals } = useProposals(chainId, MAX_PROPOSALS_TO_SHOW, searchedIndexes);
getBlockNumber(config).then(block => setBlockNumber(block));
const filteredProposals = useMemo(() => {
switch (proposalsFilter) {
case "voted":
return proposals.filter(obj => obj.status === "Succeeded" || obj.status === "Defeated");
case "created":
return proposals.filter(obj => obj.status === "Executed");
default:
return proposals;
}
}, [proposals, proposalsFilter]);
if (proposals?.length === 0) {
if (proposals?.length === 0 && proposalsFilter === "active") {
return (
<Box display="flex" justifyContent="center">
<Typography variant="h4">No proposals yet</Typography>
@ -85,13 +101,13 @@ const ProposalsList = ({ chainId, config }) => {
}
>
<Box display="flex" flexDirection="column" gap="40px">
{filteredProposals?.map(proposal => (
{proposals?.map(proposal => (
<ProposalCard
key={proposal.id}
key={proposal.hashes.short}
proposal={proposal}
blockNumber={blockNumber}
chainId={chainId}
openProposal={() => navigate(`/governance/${proposal.id}`)}
openProposal={() => navigate(`/governance/${proposal.hashes.full}`)}
/>
))}
</Box>
@ -132,13 +148,13 @@ const ProposalsList = ({ chainId, config }) => {
<ProposalFilterTrigger trigger={proposalsFilter} setTrigger={setProposalFilter} />
<ProposalTable>
{filteredProposals?.map(proposal => (
{proposals?.map(proposal => (
<ProposalRow
key={proposal.id}
key={proposal.hashes.short}
proposal={proposal}
blockNumber={blockNumber}
chainId={chainId}
openProposal={() => navigate(`/governance/${proposal.id}`)}
openProposal={() => navigate(`/governance/${proposal.hashes.full}`)}
/>
))}
</ProposalTable>
@ -155,8 +171,8 @@ const ProposalTable = ({ children }) => (
<Table aria-label="Available bonds" style={{ tableLayout: "fixed" }}>
<TableHead>
<TableRow>
<TableCell align="center" style={{ padding: "8px 0" }}>Proposal ID</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>Status</TableCell>
<TableCell align="center" style={{ width: "130px", padding: "8px 0" }}>Proposal ID</TableCell>
<TableCell align="center" style={{ width: "130px", padding: "8px 0" }}>Status</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>Vote Ends</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>Voting Stats</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}></TableCell>
@ -171,25 +187,48 @@ const ProposalTable = ({ children }) => (
const ProposalRow = ({ proposal, blockNumber, openProposal, chainId }) => {
const theme = useTheme();
const voteValue = useMemo(() => {
const againstVotes = proposal?.votes?.at(0)?._value ?? 0n;
const forVotes = proposal?.votes?.at(1)?._value ?? 0n;
const totalVotes = againstVotes + forVotes;
if (totalVotes == 0) {
return 0;
}
return Number(forVotes * 100n / totalVotes);
}, [proposal]);
const voteTarget = useMemo(() => {
const againstVotes = proposal?.votes?.at(0)?._value ?? 0n;
const forVotes = proposal?.votes?.at(1)?._value ?? 0n;
const totalVotes = againstVotes + forVotes;
const bias = 3n * (5n * againstVotes + forVotes) + BigInt(Math.sqrt(Number(totalVotes)));
const denominator = totalVotes + bias;
if (denominator === 0n) {
return 80;
}
return Number(totalVotes / denominator);
}, [proposal]);
return (
<TableRow id={proposal.id + `--proposal`} data-testid={proposal.id + `--proposal`}>
<TableRow id={proposal.hashes.short + `--proposal`} data-testid={proposal.hashes.short + `--proposal`}>
<TableCell align="center" style={{ padding: "8px 0" }}>
<Typography>GDP-{proposal.id}</Typography>
<Typography>GDP-{proposal.hashes.short}</Typography>
</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>
<Chip
sx={{ width: "100px" }}
label={proposal.status}
template={convertStatusToTemplate(proposal.status)}
label={convertStatusToLabel(proposal.state)}
template={convertStatusToTemplate(proposal.state)}
/>
</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>
<Typography>
{convertVoteEnds(
proposal.id % 2n === 0n,
proposal.voteEnds,
{convertDeadline(
proposal.deadline,
blockNumber,
chainId
)}
@ -202,24 +241,24 @@ const ProposalRow = ({ proposal, blockNumber, openProposal, chainId }) => {
barColor={theme.colors.feedback.success}
barBackground={theme.colors.feedback.error}
variant="determinate"
value={69}
target={Math.floor(Math.random() * 101)}
value={voteValue}
target={voteTarget}
/>
</Box>
</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>
{(proposal.status === "Active" || proposal.status === "Succeeded") && <PrimaryButton
{(proposal.state === "Active" || proposal.state === "Succeeded") && <PrimaryButton
fullWidth
onClick={() => openProposal()}
sx={{ maxWidth: "130px" }}
>
{proposal.status === "Succeeded" ? "Execute" : "Vote"}
{proposal.state === "Succeeded" ? "Execute" : "Vote"}
</PrimaryButton>}
{(proposal.status !== "Active" && proposal.status !== "Succeeded") && <TertiaryButton
{(proposal.state !== "Active" && proposal.state !== "Succeeded") && <TertiaryButton
fullWidth
onClick={() => openProposal()}
sx={{ maxWidth: "130px" }}
sx={{ alignSelf: "right", maxWidth: "130px" }}
>
View
</TertiaryButton>}
@ -233,21 +272,20 @@ const ProposalCard = ({ proposal, blockNumber, openProposal, chainId }) => {
const isSmallScreen = useMediaQuery('(max-width: 450px)');
return (
<Box id={proposal.id + `--proposal`} data-testid={proposal.id + `--proposal`}>
<Box id={proposal.hashes.short + `--proposal`} data-testid={proposal.hashes.short + `--proposal`}>
<Box display="flex" flexDirection={isSmallScreen ? "column" : "row"} justifyContent="space-between">
<Box display="flex" flexDirection="column" width="100%">
<Box display="flex" flexDirection="row" alignItems="center" width="100%" gap="10px">
<Typography variant="h3">GIP-{proposal.id}</Typography>
<Typography variant="h3">GIP-{proposal.hashes.short}</Typography>
<Chip
sx={{ width: "88px" }}
label={proposal.status}
template={convertStatusToTemplate(proposal.status)}
label={convertStatusToLabel(proposal.state)}
template={convertStatusToTemplate(proposal.state)}
/>
</Box>
<Typography>
{convertVoteEnds(
proposal.id % 2n === 0n,
proposal.voteEnds,
{convertDeadline(
proposal.deadline,
blockNumber,
chainId
)}
@ -265,13 +303,13 @@ const ProposalCard = ({ proposal, blockNumber, openProposal, chainId }) => {
/>
</Box>
<Box marginBottom="20px">
{(proposal.status === "Active" || proposal.status === "Succeeded") && <PrimaryButton
{(proposal.state === "Active" || proposal.state === "Succeeded") && <PrimaryButton
fullWidth
onClick={() => openProposal()}
>
{proposal.status === "Succeeded" ? "Execute" : "Vote"}
{proposal.state === "Succeeded" ? "Execute" : "Vote"}
</PrimaryButton>}
{(proposal.status !== "Active" && proposal.status !== "Succeeded") && <TertiaryButton
{(proposal.state !== "Active" && proposal.state !== "Succeeded") && <TertiaryButton
fullWidth
onClick={() => openProposal()}
>
@ -300,11 +338,11 @@ const ProposalFilterTrigger = ({ trigger, setTrigger }) => {
)
}
const convertVoteEnds = (tmp, voteEnds, blockNumber, chainId) => {
const tmpVoteSeconds = Number(voteEnds * networkAvgBlockSpeed(chainId));
const tmpSeconds = (tmp ? tmpVoteSeconds : -tmpVoteSeconds);
const convertDeadline = (deadline, blockNumber, chainId) => {
const diff = blockNumber > deadline ? blockNumber - deadline : deadline - blockNumber;
const voteSeconds = Number(diff * networkAvgBlockSpeed(chainId));
const result = prettifySecondsInDays(tmpSeconds);
const result = prettifySeconds(voteSeconds, "mins");
if (result === "now") {
return new Date(Date.now()).toLocaleDateString('en-US', {
year: 'numeric',

View File

@ -11,7 +11,7 @@ import { ParsedCell } from "./index";
export const prepareAuditReservesCalldata = (chainId) => {
const value = 0n;
const label = "Audit Reserves";
const label = "auditReserves";
const target = DAO_TREASURY_ADDRESSES[chainId];
const calldata = encodeFunctionData({
abi: TreasuryAbi,

View File

@ -8,7 +8,7 @@ import { PrimaryButton, TertiaryButton } from "../../../../components/Button";
import { shorten } from "../../../../helpers";
import { useTokenSymbol } from "../../../../hooks/tokens";
import { BooleanTrigger, ArgumentInput, ParsedCell } from "./index";
import { ArgumentsWrapper, BooleanTrigger, ArgumentInput, ParsedCell } from "./index";
import {
RESERVE_ADDRESSES,
@ -19,7 +19,7 @@ import { abi as DepositoryAbi } from "../../../../abi/GhostBondDepository.json";
export const prepareCreateBondCalldata = (chainId, markets, terms, quoteToken, intervals, booleans) => {
const value = 0n;
const label = "Create Bond";
const label = "create";
const target = BOND_DEPOSITORY_ADDRESSES[chainId];
const calldata = encodeFunctionData({
abi: DepositoryAbi,
@ -39,7 +39,7 @@ export const CreateBondParsed = (props) => {
<Modal
headerContent={
<Box display="flex" justifyContent="center" alignItems="center" gap="15px">
<Typography variant="h4">View Create Bond</Typography>
<Typography variant="h4">View create</Typography>
</Box>
}
open={isOpened}
@ -80,6 +80,8 @@ export const CreateBondSteps = ({ nativeCurrency, ftsoSymbol, chainId, toInitial
const [vestingLength, setVestingLength] = useState(args?.at(1)?.at(0));
const [conclusionTimestamp, setConclusionTimestamp] = useState(args?.at(1)?.at(1));
const { symbol: reserveSymbol } = useTokenSymbol(chainId, RESERVE_ADDRESSES[chainId]);
const handleProceed = () => {
const markets = [capacity, initialPrice, debtBuffer];
const terms = [vestingLength, conclusionTimestamp];
@ -116,8 +118,8 @@ export const CreateBondSteps = ({ nativeCurrency, ftsoSymbol, chainId, toInitial
}, [step, tokenAddress, capacity, initialPrice, debtBuffer, depositInterval, tuneInterval]);
const possibleTokens = [
{ value: FTSO_DAI_LP_ADDRESSES[chainId], label: `${ftsoSymbol}-${nativeCurrency} LP: ${shorten(FTSO_DAI_LP_ADDRESSES[chainId])}` },
{ value: RESERVE_ADDRESSES[chainId], label: `${ftsoSymbol}: ${shorten(RESERVE_ADDRESSES[chainId])}` },
{ value: FTSO_DAI_LP_ADDRESSES[chainId], symbol: `${ftsoSymbol}-${nativeCurrency} LP`, label: `${ftsoSymbol}-${nativeCurrency} LP: ${shorten(FTSO_DAI_LP_ADDRESSES[chainId])}` },
{ value: RESERVE_ADDRESSES[chainId], symbol: reserveSymbol, label: `${reserveSymbol}: ${shorten(RESERVE_ADDRESSES[chainId])}` },
];
return (
@ -136,7 +138,7 @@ export const CreateBondSteps = ({ nativeCurrency, ftsoSymbol, chainId, toInitial
/>}
{step === 2 && <MarketArguments
capacityInQuote={capacityInQuote}
nativeCurrency={nativeCurrency}
currencySymbol={possibleTokens.find(token => token.value === tokenAddress).symbol}
ftsoSymbol={ftsoSymbol}
capacity={capacity}
setCapacity={createMode ? setCapacity : empty}
@ -171,7 +173,7 @@ export const CreateBondSteps = ({ nativeCurrency, ftsoSymbol, chainId, toInitial
const MarketArguments = ({
capacityInQuote,
nativeCurrency,
currencySymbol,
ftsoSymbol,
capacity,
setCapacity,
@ -183,25 +185,25 @@ const MarketArguments = ({
return (
<Box>
<ArgumentInput
endString={capacityInQuote ? nativeCurrency : ftsoSymbol}
label="Bond Capacity"
endString={capacityInQuote ? currencySymbol : ftsoSymbol}
label="market[0]"
value={capacity ?? ""}
setValue={setCapacity}
tooltip={`Bond market capacity, denominated in ${capacityInQuote ? nativeCurrency : ftsoSymbol}. This determines how the total bond supply is capped`}
tooltip={`Total capacity limit of the bond market denominated in ${capacityInQuote ? currencySymbol : ftsoSymbol}, representing the maximum amount of bonds that can be sold in this market.`}
/>
<ArgumentInput
endString={nativeCurrency}
label="Bond Initial Price"
endString={currencySymbol}
label="market[1]"
value={initialPrice ?? ""}
setValue={setInitialPrice}
tooltip={`The initial price used to bootstrap the Bond Control Variable (BCV). This price will dynamically adjust based on market demand once trading begins`}
tooltip={`Initial bond price of 1 ${ftsoSymbol} denominated in ${currencySymbol}. The bond price adjusts dynamically via Dutch auction based on market demand.`}
/>
<ArgumentInput
endString="%"
label="Debt buffer"
label="market[2]"
value={debtBuffer ?? ""}
setValue={setDebtBuffer}
tooltip="Safety threshold (in basis points) to cap rapid bond sales. For example, 30,000 equals a 30% buffer above the target debt level"
tooltip="Debt buffer in basis points (e.g. 30,000 = 30%). Determines how far bond price can fall from initial price before tuning is triggered."
/>
</Box>
)
@ -217,17 +219,17 @@ const IntervalsArguments = ({
<Box>
<ArgumentInput
endString="seconds"
label="Deposit Interval"
label="_intervals[0]"
value={depositInterval ?? ""}
setValue={setDepositInterval}
tooltip="Target timeframe for selling out the market. It regulates price sensitivity: a shorter interval causes the price to drop faster during periods of inactivity"
tooltip="Time in seconds that determines per-transaction bond purchase limit based on time until expiration. Higher values = larger per-transaction purchases allowed."
/>
<ArgumentInput
endString="seconds"
label="Tune interval"
label="_intervals[0]"
value={tuneInterval ?? ""}
setValue={setTuneInterval}
tooltip="The frequency at which the system ensures the bond's internal price stays aligned with the target market price by auto-tuning at specific intervals"
tooltip="Time in seconds between bond price tuning events that evaluate actual vs. expected bond sales, adjusting how aggressively price decays in the next interval."
/>
</Box>
)
@ -244,19 +246,20 @@ const TermsAgruments = ({
<Box>
<ArgumentInput
endString="seconds"
label={fixedTerm ? "Vesting Duration" : "Vested Timestamp"}
label={"_terms[0]"}
value={vestingLength ?? ""}
setValue={setVestingLength}
tooltip={`Determines the vesting schedule: ${fixedTerm
? "a relative time offset from the point of purchase"
: "a static maturity timestamp"}`}
tooltip={fixedTerm
? "Time in seconds representing bond vesting schedule. Bond becomes fully claimable after this time elapses from purchase date."
: "Absolute Unix timestamp when all bonds mature and become fully claimable, regardless of the purchase date."
}
/>
<ArgumentInput
endString="seconds"
label="Bond Conclusion"
label="_terms[1]"
value={conclusionTimestamp ?? ""}
setValue={setConclusionTimestamp}
tooltip="The timestamp that marks the end of the market's lifespan. It defines the point at which the market stops accepting new deposits and the decay mechanism ceases"
tooltip="Unix timestamp when bond market stops accepting new purchases. Bond market closes after this time."
/>
</Box>
)
@ -288,39 +291,44 @@ const TokenAndBooleansArguments = ({
<Box>
<BooleanTrigger
value={capacityInQuote}
label="Capacity Type"
leftText={nativeCurrency}
rightText={ftsoSymbol}
label="_booleans[0]"
leftText={"True"}
rightText={"False"}
setLeftValue={() => setCapacityInQuote(true)}
setRightValue={() => setCapacityInQuote(false)}
tooltip="Capacity is set in terms of the asset being paid, otherwise it is set in the asset being earned"
tooltip={`Determines how the bond market capacity is measured. True = measured in ${nativeCurrency}, False = measured in ${ftsoSymbol}.`}
/>
<BooleanTrigger
value={fixedTerm}
label="Fixed Term"
leftText="Yes"
rightText="No"
label="_booleans[1]"
leftText="True"
rightText="False"
setLeftValue={() => setFixedTerm(true)}
setRightValue={() => setFixedTerm(false)}
tooltip="Determines whether the bond has a fixed expiration date or a fixed duration starting from the time of purchase"
tooltip={`Defines the bond maturity model. True = each purchase matures after vesting duration from its purchase date. False = all purchases mature at the conclusion timestamp.`}
/>
<Select
value={selectedOption ?? ""}
onChange={handleChange}
options={possibleTokens}
inputWidth="100%"
renderValue={(selected) => {
if (!selected || selected.length === 0) {
return (
<span style={{ color: theme.colors.gray[500], opacity: 0.7 }}>
Select payment token
</span>
);
}
<ArgumentsWrapper
label="_quoteToken"
tooltip={`ERC20 token that users pay with to purchase bonds (e.g. ${nativeCurrency} or ${nativeCurrency}-${ftsoSymbol} LP token).`}
>
<Select
value={selectedOption ?? ""}
onChange={handleChange}
options={possibleTokens}
inputWidth="100%"
renderValue={(selected) => {
if (!selected || selected.length === 0) {
return (
<span style={{ color: theme.colors.gray[500], opacity: 0.7 }}>
Select payment token
</span>
);
}
return possibleTokens.find(opt => opt.value === selected)?.label || selected;
}}
/>
return possibleTokens.find(opt => opt.value === selected)?.label || selected;
}}
/>
</ArgumentsWrapper>
</Box>
)
}

View File

@ -14,7 +14,7 @@ import { BooleanTrigger, ArgumentInput, ParsedCell } from "./index";
export const prepareSetAdjustmentCalldata = (chainId, rateChange, targetRate, increase) => {
const value = 0n;
const label = "Set Adjustment";
const label = "setAdjustment";
const target = DISTRIBUTOR_ADDRESSES[chainId];
const calldata = encodeFunctionData({
abi: DistributorAbi,
@ -33,7 +33,7 @@ export const SetAdjustmentParsed = (props) => {
<Modal
headerContent={
<Box display="flex" justifyContent="center" alignItems="center" gap="15px">
<Typography variant="h4">View Set Adjustment</Typography>
<Typography variant="h4">View setAdjustment</Typography>
</Box>
}
open={isOpened}
@ -70,28 +70,28 @@ export const SetAdjustmentSteps = ({ chainId, toInitialStep, addCalldata, args }
<Box>
<BooleanTrigger
value={increase}
leftText="Add"
rightText="Sub"
leftText="True"
rightText="False"
setLeftValue={() => createMode ? setIncrease(true) : {}}
setRightValue={() => createMode ? setIncrease(false) : {}}
label="Direction"
tooltip="Determines the direction of the adjustment. When Add the parameter value will gradually increase; when Sub, it will decrease toward the target rate"
label="add"
tooltip="Adjusts the current rate toward the target eCSPR staking rate. True = increase rate, False = decrease rate."
/>
<ArgumentInput
disabled={!createMode}
endString="%"
label="Change Rate"
label="rate"
value={rate ?? ""}
setValue={createMode ? setRate : () => {}}
tooltip="Each epoch, the current staking reward rate changes by this amount until the target rate is reached"
tooltip="Each epoch, the current staking rate increases by this amount until the target rate is reached [e.g. 154 => APY = (1 + (Current + 154)/1,000,000)^(365*3)]."
/>
<ArgumentInput
disabled={!createMode}
endString="%"
label="Target Rate"
label="target"
value={target ?? ""}
setValue={createMode ? setTarget : () => {}}
tooltip="The final desired value for the reward rate, the adjustment process will continue automatically until this specific level is reached"
tooltip="The target staking rate to be achieved [e.g. 633 => APY = (1 + 633/1,000,000)^(365*3) 1 = 100%]."
/>
</Box>
{createMode && <Box width="100%" sx={{ marginTop: "20px" }} display="flex" flexDirection="column" gap="5px">

View File

@ -21,9 +21,9 @@ import { prepareCreateBondDescription, prepareCreateBondCalldata, CreateBondStep
const DEFAULT_DESCRIPTION = "Please select the function to include in your proposal. Multi-functional proposals are allowed, but each included function should be clearly specified."
export const allPossibleFunctions = [
{ value: "auditReserves", label: "Audit Reserves" },
{ value: "setAdjustment", label: "Change APY" },
{ value: "createBond", label: "Create Bond" },
{ value: "auditReserves", label: "auditReserves" },
{ value: "setAdjustment", label: "setAdjustment" },
{ value: "create", label: "create" },
];
const allAbis = [TreasuryAbi, DistributorAbi, DepositoryAbi];
@ -47,10 +47,9 @@ const identifyAction = (calldata) => {
export const parseFunctionCalldata = (metadata, index, chainId, nativeCoin, removeCalldata) => {
const { label, calldata, target, value } = metadata;
const { functionName, args } = identifyAction(calldata);
const labelOrName = label ?? (functionName ?? "Unknown");
const remove = () => removeCalldata(index);
console.log(`function arguments for ${label}: ${args}`);
const remove = removeCalldata && (() => removeCalldata(index));
switch (functionName) {
case "auditReserves":
@ -58,7 +57,7 @@ export const parseFunctionCalldata = (metadata, index, chainId, nativeCoin, remo
isTable
key={index}
calldata={calldata}
label={label}
label={labelOrName}
chainId={chainId}
remove={remove}
nativeCoin={nativeCoin}
@ -72,7 +71,7 @@ export const parseFunctionCalldata = (metadata, index, chainId, nativeCoin, remo
args={args}
key={index}
calldata={calldata}
label={label}
label={labelOrName}
chainId={chainId}
remove={remove}
nativeCoin={nativeCoin}
@ -86,7 +85,7 @@ export const parseFunctionCalldata = (metadata, index, chainId, nativeCoin, remo
args={args}
key={index}
calldata={calldata}
label={label}
label={labelOrName}
chainId={chainId}
remove={remove}
nativeCoin={nativeCoin}
@ -99,7 +98,7 @@ export const parseFunctionCalldata = (metadata, index, chainId, nativeCoin, remo
isTable
key={index}
calldata={calldata}
label={label}
label="Unknown"
remove={remove}
nativeCoin={nativeCoin}
value={value}
@ -115,7 +114,7 @@ export const getFunctionArguments = (functionName) => {
return null;
case "setAdjustment":
return SetAdjustmentSteps;
case "createBond":
case "create":
return CreateBondSteps;
default:
return null;
@ -128,7 +127,7 @@ export const getFunctionCalldata = (functionName, chainId) => {
return prepareAuditReservesCalldata(chainId);
case "setAdjustment":
return prepareSetAdjustmentCalldata(chainId);
case "createBond":
case "create":
return prepareCreateBondCalldata(chainId);
default:
return null;
@ -141,7 +140,7 @@ export const getFunctionDescription = (functionName) => {
return prepareAuditReservesDescription;
case "setAdjustment":
return prepareSetAdjustmentDescription;
case "createBond":
case "create":
return prepareCreateBondDescription;
default:
return DEFAULT_DESCRIPTION;
@ -261,7 +260,7 @@ export const ParsedCell = (props) => {
url = url + `/address/${props.target}`;
}
return url;
}, [props]);
}, [props, config]);
const handleCalldataCopy = async () => {
try {
@ -295,7 +294,7 @@ export const ParsedCell = (props) => {
</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>
<Typography>{formatCurrency(props.value, 2, props.nativeCoin)}</Typography>
<Typography>{formatCurrency(props.value, 4, props.nativeCoin)}</Typography>
</TableCell>
<TableCell>

View File

@ -1,14 +1,38 @@
export const MY_PROPOSALS_PREFIX = "MY_PROPOSALS";
export const VOTED_PROPOSALS_PREFIX = "VOTED_PROPOSALS";
export const convertStatusToTemplate = (status) => {
switch (status.toUpperCase()) {
case "EXECUTED":
switch (status) {
case 7:
return 'darkGray';
case "CANCELED":
case 2:
return 'warning';
case "SUCCEEDED":
case 4:
return 'success';
case "DEFEATED":
case 3:
return 'error';
default:
return 'darkGray';
}
}
export const convertStatusToLabel = (status) => {
switch (status) {
case 1:
return "Active";
case 2:
return "Canceled";
case 3:
return "Defeated";
case 4:
return "Succeeded";
case 5:
return "Queued";
case 6:
return "Expired";
case 7:
return "Executed";
default:
return "Pending";
}
}

View File

@ -1,18 +1,74 @@
import { useReadContract, useReadContracts } from "wagmi";
import { simulateContract, writeContract, waitForTransactionReceipt } from "@wagmi/core";
import toast from "react-hot-toast";
import { keccak256, stringToBytes } from 'viem'
import { config } from "../../config";
import {
GHOST_GOVERNANCE_ADDRESSES,
} from "../../constants/addresses";
import { abi as GovernorAbi } from "../../abi/Governor.json";
import { abi as GovernorAbi } from "../../abi/GhostGovernor.json";
import { abi as GovernorCountingAbi } from "../../abi/GovernorGhostCounting.json";
import { abi as GovernorStorageAbi } from "../../abi/GovernorStorage.json";
import { abi as GovernorVotesQuorumFractionAbi } from "../../abi/GovernorVotesQuorumFraction.json";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { getTokenDecimals } from "../helpers";
import { getTokenDecimals, getTokenAbi, getTokenAddress } from "../helpers";
export const useProposalVoteOf = (chainId, proposalId, who) => {
const { data, error } = useReadContract({
abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "voteOf",
args: [proposalId, who],
scopeKey: `voteOf-${chainId}-${proposalId?.toString()}-${who}`,
chainId: chainId,
});
const voteOf = data ? BigInt(data) : 0n;
return { voteOf };
}
export const useProposalHash = (chainId, functions) => {
const { proposalCount } = useProposalCount(chainId);
const proposalDescription = `Proposal #${proposalCount}`;
const descriptionHash = keccak256(stringToBytes(proposalDescription));
const { data: proposalHash, refetch } = useReadContract({
abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "hashProposal",
args: [
functions.map(f => f.target),
functions.map(f => f.value),
functions.map(f => f.calldata),
descriptionHash
],
scopeKey: `hashProposal-${chainId}-${functions.map(f => f.calldata)}`,
chainId: chainId,
});
return { proposalHash, proposalDescription };
}
export const useActiveProposedLock = (chainId) => {
const decimals = getTokenDecimals("GHST");
const { data: activeProposedLock, refetch } = useReadContract({
abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "activeProposedLock",
scopeKey: `activeProposedLock-${chainId}`,
chainId: chainId,
});
const result = new DecimalBigNumber(
activeProposedLock ? activeProposedLock : 0n,
decimals
);
return result;
}
export const useMinQuorum = (chainId) => {
const { data: quorumNumerator, refetch: quorumNumeratorRefetch } = useReadContract({
@ -40,7 +96,8 @@ export const useMinQuorum = (chainId) => {
export const useProposalThreshold = (chainId, name) => {
const decimals = getTokenDecimals(name);
const { data, refetch } = useReadContract({
const { data } = useReadContract({
abi: GovernorStorageAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "proposalThreshold",
@ -48,7 +105,23 @@ export const useProposalThreshold = (chainId, name) => {
chainId: chainId,
});
const threshold = new DecimalBigNumber(data ?? 0n, decimals);
const { data: activeProposedLock } = useReadContract({
abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "activeProposedLock",
scopeKey: `activeProposedLock-${chainId}`,
chainId: chainId,
});
let threshold = new DecimalBigNumber(data ?? 0n, decimals);
const { proposalCount } = useProposalCount(chainId);
const { proposalId } = useProposalDetailsAt(chainId, proposalCount === 0n ? 0n : proposalCount - 1n);
const { state } = useProposalState(chainId, proposalId);
if (state < 2) {
threshold = new DecimalBigNumber(activeProposedLock ?? 0n, decimals);
}
return { threshold };
}
@ -61,87 +134,456 @@ export const useProposalCount = (chainId) => {
scopeKey: `proposalCount-${chainId}`,
chainId: chainId,
});
const proposalCount = data ?? 0n;
return { proposalCount };
}
export const useProposalStatus = (chainId, proposalId) => {
const status = "Succeeded";
return { status };
export const useProposalState = (chainId, proposalId) => {
const { data } = useReadContract({
abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "state",
args: [proposalId],
scopeKey: `state-${chainId}`,
chainId: chainId,
});
const state = data ?? 0;
return { state };
}
export const useProposalProposer = (chainId, proposalId) => {
const proposer = "0x71C7656EC7ab88b098defB751B7401B5f6d8976F";
const { data } = useReadContract({
abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "proposalProposer",
args: [proposalId],
scopeKey: `proposalProposer-${chainId}`,
chainId: chainId,
});
const proposer = data ? data : "0x0000000000000000000000000000000000000000";
return { proposer };
}
export const useProposalLocked = (chainId, proposalId) => {
const decimals = getTokenDecimals(name);
const locked = new DecimalBigNumber(420_000_000_000_000_000_000n, decimals);
const { data } = useReadContract({
abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "lockedAmounts",
args: [proposalId],
scopeKey: `lockedAmounts-${chainId}`,
chainId: chainId,
});
const locked = new DecimalBigNumber(data ?? 0n, decimals);
return { locked }
}
export const useProposalQuorum = (chainId, proposalId) => {
const decimals = getTokenDecimals(name);
const quorum = new DecimalBigNumber(1337_000_000_000_000_000_000n, decimals);
const { snapshot } = useProposalSnapshot(chainId, proposalId);
const { data } = useReadContract({
abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "quorum",
args: [snapshot],
scopeKey: `quorum-${chainId}`,
chainId: chainId,
});
const quorum = new DecimalBigNumber(data ?? 0n, decimals);
return { quorum }
}
export const useProposalVotes = (chainId, proposalId) => {
const decimals = getTokenDecimals(name);
const forVotes = new DecimalBigNumber(420_000_000_000_000_000_000n, decimals);
const againstVotes = new DecimalBigNumber(69_000_000_000_000_000_000n, decimals);
const totalVotes = new DecimalBigNumber(489_000_000_000_000_000_000n, decimals);
const { data } = useReadContract({
abi: GovernorCountingAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "proposalVotes",
args: [proposalId],
scopeKey: `proposalVotes-${chainId}`,
chainId: chainId,
});
const againstVotes = new DecimalBigNumber(data?.at(0) ?? 0n, decimals);
const forVotes = new DecimalBigNumber(data?.at(1) ?? 0n, decimals);
const totalVotes = new DecimalBigNumber(
(data?.at(0) ?? 0n) + (data?.at(1) ?? 0n),
decimals
);
return { forVotes, againstVotes, totalVotes }
}
export const useProposalSnapshot = (chainId, proposalId) => {
const snapshot = Math.floor((Date.now() - (3 * 24 * 60 * 60 * 1000)) / 1000);
const { data, error } = useReadContract({
abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "proposalSnapshot",
args: [proposalId],
scopeKey: `proposalSnapshot-${chainId}`,
chainId: chainId,
});
const snapshot = data ?? 0n;
return { snapshot };
}
export const useProposalDetailsAt = (chainId, index) => {
const { data, error } = useReadContract({
abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "proposalDetailsAt",
args: [index],
scopeKey: `proposalDetailsAt-${chainId}-${index}`,
chainId: chainId,
});
const proposalId = data?.at(0) ?? 0n;
const proposalDetailsAt = data?.at(1)?.map((target, index) => ({
target,
value: data?.at(2)?.at(index),
calldata: data?.at(3)?.at(index),
}));
return { proposalDetailsAt, proposalId };
}
export const useProposalDetails = (chainId, proposalId) => {
const { data } = useReadContract({
abi: GovernorStorageAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "proposalDetails",
args: [proposalId],
scopeKey: `proposalDetails-${chainId}-${proposalId}`,
chainId: chainId,
});
const proposalDetails = data?.at(0)?.map((target, index) => ({
target,
value: data?.at(1)?.at(index),
calldata: data?.at(2)?.at(index),
}));
return { proposalDetails };
}
export const useProposalDeadline = (chainId, proposalId) => {
const deadline = Math.floor(Date.now() / 1000);
const { data, refetch } = useReadContract({
abi: GovernorStorageAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
args: [proposalId],
functionName: "proposalDeadline",
scopeKey: `proposalDeadline-${chainId}`,
chainId: chainId,
});
const deadline = data ?? 0n;
return { deadline };
}
export const useProposalVotingDelay = (chainId, proposalId) => {
const delay = 1;
export const useProposalVotingDelay = (chainId) => {
const { data } = useReadContract({
abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "votingDelay",
scopeKey: `votingDelay-${chainId}`,
chainId: chainId,
});
const delay = data ?? 0n;
return { delay };
}
export const useProposals = (chainId, depth) => {
const decimals = getTokenDecimals(name);
const { proposalCount } = useProposalCount(chainId);
export const useProposals = (chainId, depth, searchedIndexes) => {
const decimals = getTokenDecimals("GHST");
const ghstAbi = getTokenAbi("GHST");
const ghstAddress = getTokenAddress(chainId, "GHST");
const statuses = [
"Active",
"Executed",
"Canceled",
"Succeeded",
"Defeated"
];
let proposals = [];
const { proposalCount } = useProposalCount(chainId);
const start = Number(proposalCount);
const end = Math.max(0, start - depth);
const indexes = searchedIndexes
? searchedIndexes.map((_, i) => i)
: [...Array(start - end)].map((_, i) => start - 1 - i);
for (let i = start - 1; i >= end; --i) {
const voteEnds = 50n;
const yesVotes = new DecimalBigNumber(1337_000_000_000_000_000_000, decimals);
const noVotes = new DecimalBigNumber(420_000_000_000_000_000_000, decimals);
const { data: proposalsDetailsAt } = useReadContracts({
contracts: indexes?.map(index => {
return {
abi: GovernorStorageAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "proposalDetailsAt",
args: [index],
scopeKey: `proposalDetailsAt-${chainId}-${index}`,
chainId: chainId,
}
})
});
proposals.push({
id: i,
discussion: "https://google.com",
status: statuses[i % statuses.length],
voteEnds,
yesVotes,
noVotes
});
}
const { data: proposalDetails } = useReadContracts({
contracts: searchedIndexes?.map(index => {
return {
abi: GovernorStorageAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "proposalDetails",
args: [index],
scopeKey: `proposalDetails-${chainId}-${index}`,
chainId: chainId,
}
})
});
const { data: proposalDeadlines } = useReadContracts({
contracts: indexes?.map(index => {
const proposalId = searchedIndexes
? searchedIndexes?.at(0)
: proposalsDetailsAt?.at(index)?.result?.at(0);
return {
abi: GovernorStorageAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "proposalDeadline",
args: [proposalId],
scopeKey: `proposalDeadline-${chainId}-${index}`,
chainId: chainId,
}
})
});
const { data: proposalVotes } = useReadContracts({
contracts: indexes?.map(index => {
const proposalId = searchedIndexes
? searchedIndexes?.at(0)
: proposalsDetailsAt?.at(index)?.result?.at(0);
return {
abi: GovernorCountingAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "proposalVotes",
args: [proposalId],
scopeKey: `proposalVotes-${chainId}-${index}`,
chainId: chainId,
}
})
});
const { data: proposalStates } = useReadContracts({
contracts: indexes?.map(index => {
const proposalId = searchedIndexes
? searchedIndexes?.at(0)
: proposalsDetailsAt?.at(index)?.result?.at(0);
return {
abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "state",
args: [proposalId],
scopeKey: `state-${chainId}-${index}`,
chainId: chainId,
}
})
});
const { data: proposalSnapshots } = useReadContracts({
contracts: indexes?.map(index => {
const proposalId = searchedIndexes
? searchedIndexes?.at(0)
: proposalsDetailsAt?.at(index)?.result?.at(0);
return {
abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "proposalSnapshot",
args: [proposalId],
scopeKey: `proposalSnapshot-${chainId}-${index}`,
chainId: chainId,
}
})
});
const { data: proposalQuorums } = useReadContracts({
contracts: indexes?.map(index => {
const timepoint = proposalSnapshots?.at(index)?.result;
return {
abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "quorum",
args: [timepoint],
scopeKey: `quorum-${chainId}-${index}`,
chainId: chainId,
}
})
});
const { data: pastTotalSupplies } = useReadContracts({
contracts: indexes?.map(index => {
const timepoint = proposalSnapshots?.at(index)?.result;
return {
abi: ghstAbi,
address: ghstAddress,
functionName: "getPastTotalSupply",
args: [timepoint],
scopeKey: `getPastTotalSupply-${chainId}-${index}`,
chainId: chainId,
}
})
});
const { data: proposalProposer } = useReadContracts({
contracts: indexes?.map(index => {
const proposalId = searchedIndexes
? searchedIndexes?.at(0)
: proposalsDetailsAt?.at(index)?.result?.at(0);
return {
abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "proposalProposer",
args: [proposalId],
scopeKey: `proposalProposer-${chainId}-${index}`,
chainId: chainId,
}
})
});
const hashes = indexes?.map(index => {
let result = { short: index + 1, full: undefined };
const proposalId = searchedIndexes
? searchedIndexes?.at(0)
: proposalsDetailsAt?.at(index)?.result?.at(0);
if (proposalId) {
const hash = "0x" + proposalId.toString(16);
result.short = hash.slice(-5);
result.full = hash;
}
return result;
});
const proposals = indexes?.map(index => ({
hashes: hashes?.at(index),
proposer: proposalProposer?.at(index)?.result,
details: proposalsDetailsAt?.at(index)?.result,
deadline: proposalDeadlines?.at(index)?.result ?? 0n,
state: proposalStates?.at(index)?.result ?? 0,
pastTotalSupply: new DecimalBigNumber(pastTotalSupplies?.at(index)?.result ?? 0n, decimals),
quorum: new DecimalBigNumber(proposalQuorums?.at(index)?.result ?? 0n, decimals),
snapshot: new DecimalBigNumber(proposalSnapshots?.at(index)?.result ?? 0n, decimals),
votes: proposalVotes?.at(index)?.result?.map(
vote => new DecimalBigNumber(vote ?? 0n, decimals),
),
}));
return { proposals };
}
export const releaseLocked = async (chainId, account, proposalId) => {
try {
const { request } = await simulateContract(config, {
abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: 'releaseLocked',
args: [proposalId],
account: account,
chainId: chainId
});
const txHash = await writeContract(config, request);
await waitForTransactionReceipt(config, {
hash: txHash,
onReplaced: () => toast("Release locked transaction was replaced. Wait for inclusion please."),
chainId
});
toast.success("Successfully release locked funds from the governor.");
return true;
} catch (err) {
console.error(err);
toast.error("Release locked funds failed. Check logs for error detalization.");
return false;
}
}
export const executeProposal = async (chainId, account, proposalId) => {
try {
const { request } = await simulateContract(config, {
abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: 'execute',
args: [proposalId],
account: account,
chainId: chainId
});
const txHash = await writeContract(config, request);
await waitForTransactionReceipt(config, {
hash: txHash,
onReplaced: () => toast("Proposal execution transaction was replaced. Wait for inclusion please."),
chainId
});
toast.success("Proposal execution was successful, wait for updates.");
return true;
} catch (err) {
console.error(err);
toast.error("Proposal execution failed. Check logs for error detalization.");
return false;
}
}
export const castVote = async (chainId, account, proposalId, support) => {
try {
const { request } = await simulateContract(config, {
abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: 'castVote',
args: [proposalId, support],
account: account,
chainId: chainId
});
const txHash = await writeContract(config, request);
await waitForTransactionReceipt(config, {
hash: txHash,
onReplaced: () => toast("Cast vote transaction was replaced. Wait for inclusion please."),
chainId
});
toast.success("Successfully casted a vote, should be applied the proposal.");
return true;
} catch (err) {
console.error(err);
toast.error("Vote cast failed. Check logs for error detalization.");
return false;
}
}
export const propose = async (chainId, account, functions, description) => {
const targets = functions.map(f => f.target);
const values = functions.map(f => f.value);
const calldatas = functions.map(f => f.calldata);
try {
const { request } = await simulateContract(config, {
abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: 'propose',
args: [targets, values, calldatas, description],
account: account,
chainId: chainId
});
const txHash = await writeContract(config, request);
await waitForTransactionReceipt(config, {
hash: txHash,
onReplaced: () => toast("Proposal transaction was replaced. Wait for inclusion please."),
chainId
});
toast.success("Successfully proposed a set of functions to be executed.");
return true;
} catch (err) {
console.error(err);
toast.error("Proposal creation failed. Check logs for error detalization.");
return false;
}
}

View File

@ -48,6 +48,9 @@ export const getTokenAbi = (name) => {
case "WETH":
abi = WethAbi;
break;
case "WMWETH":
abi = WethAbi;
break;
}
return abi;
}
@ -86,6 +89,9 @@ export const getTokenDecimals = (name) => {
case "WETH":
decimals = 18;
break;
case "WMWETH":
decimals = 18;
break;
}
return decimals;
}
@ -133,6 +139,9 @@ export const getTokenAddress = (chainId, name) => {
case "WETC":
address = WETH_ADDRESSES[chainId];
break;
case "WMETC":
address = WETH_ADDRESSES[chainId];
break;
}
return address;
}

View File

@ -9,6 +9,40 @@ import { shorten } from "../../helpers";
import { tokenNameConverter } from "../../helpers/tokenConverter";
import { config } from "../../config";
export const usePastVotes = (chainId, name, timepoint, address) => {
const decimals = getTokenDecimals(name);
const contractAddress = getTokenAddress(chainId, name);
const { data, refetch, error } = useReadContract({
abi: getTokenAbi(name),
address: contractAddress,
functionName: "getPastVotes",
args: [address, timepoint],
scopeKey: `getPastVotes-${timepoint}-${chainId}`,
chainId: chainId,
});
const pastVotes = new DecimalBigNumber(data ? data : 0n, decimals);
return { pastVotes }
}
export const usePastTotalSupply = (chainId, name, timepoint) => {
const decimals = getTokenDecimals(name);
const contractAddress = getTokenAddress(chainId, name);
const { data, refetch, error } = useReadContract({
abi: getTokenAbi(name),
address: contractAddress,
functionName: "getPastTotalSupply",
args: [timepoint],
scopeKey: `getPastTotalSupply-${timepoint}-${chainId}`,
chainId: chainId,
});
const pastTotalSupply = new DecimalBigNumber(data ? data : 0n, decimals);
return { pastTotalSupply, refetch };
}
export const useTotalSupply = (chainId, name) => {
const contractAddress = getTokenAddress(chainId, name);
const { data, refetch } = useToken({
@ -26,7 +60,7 @@ export const useTotalSupply = (chainId, name) => {
export const useBalance = (chainId, name, address) => {
const contractAddress = getTokenAddress(chainId, name);
const { data, refetch } = useInnerBalance({
const { data, refetch, error } = useInnerBalance({
address,
chainId,
scopeKey: `balance-${contractAddress}-${address}-${chainId}`,