governance rev.5
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
This commit is contained in:
parent
1239b81889
commit
ad55c04525
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "ghost-dao-interface",
|
"name": "ghost-dao-interface",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.5.14",
|
"version": "0.5.15",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
1
src/abi/GhostGovernor.json
Normal file
1
src/abi/GhostGovernor.json
Normal file
File diff suppressed because one or more lines are too long
1
src/abi/GovernorGhostCounting.json
Normal file
1
src/abi/GovernorGhostCounting.json
Normal file
File diff suppressed because one or more lines are too long
@ -319,6 +319,7 @@ const Bridge = ({ chainId, address, config, connect }) => {
|
|||||||
handleButtonProceed={handleButtonProceed}
|
handleButtonProceed={handleButtonProceed}
|
||||||
/>
|
/>
|
||||||
<BridgeModal
|
<BridgeModal
|
||||||
|
providerDetail={providerDetail}
|
||||||
currentRecord={currentRecord}
|
currentRecord={currentRecord}
|
||||||
activeTxIndex={activeTxIndex}
|
activeTxIndex={activeTxIndex}
|
||||||
setActiveTxIndex={setActiveTxIndex}
|
setActiveTxIndex={setActiveTxIndex}
|
||||||
|
|||||||
@ -19,12 +19,13 @@ import ContentPasteIcon from '@mui/icons-material/ContentPaste';
|
|||||||
import InfoTooltip from "../../components/Tooltip/InfoTooltip";
|
import InfoTooltip from "../../components/Tooltip/InfoTooltip";
|
||||||
import Modal from "../../components/Modal/Modal";
|
import Modal from "../../components/Modal/Modal";
|
||||||
import GhostStyledIcon from "../../components/Icon/GhostIcon";
|
import GhostStyledIcon from "../../components/Icon/GhostIcon";
|
||||||
import { PrimaryButton } from "../../components/Button";
|
import { PrimaryButton, TertiaryButton } from "../../components/Button";
|
||||||
|
|
||||||
import { formatCurrency } from "../../helpers";
|
import { formatCurrency } from "../../helpers";
|
||||||
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||||
|
|
||||||
export const BridgeModal = ({
|
export const BridgeModal = ({
|
||||||
|
providerDetail,
|
||||||
currentRecord,
|
currentRecord,
|
||||||
activeTxIndex,
|
activeTxIndex,
|
||||||
setActiveTxIndex,
|
setActiveTxIndex,
|
||||||
@ -88,7 +89,15 @@ export const BridgeModal = ({
|
|||||||
minHeight={"100px"}
|
minHeight={"100px"}
|
||||||
>
|
>
|
||||||
<Box display="flex" gap="1.5rem" flexDirection="column" marginTop=".8rem">
|
<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 && (
|
{currentRecord?.finalization > 0 && (
|
||||||
<>
|
<>
|
||||||
<Box
|
<Box
|
||||||
@ -276,7 +285,7 @@ export const BridgeModal = ({
|
|||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>}
|
||||||
|
|
||||||
<Box display="flex" flexDirection="column" gap="5px" padding="0.6rem 0">
|
<Box display="flex" flexDirection="column" gap="5px" padding="0.6rem 0">
|
||||||
<Box display="flex" flexDirection="row" justifyContent="space-between">
|
<Box display="flex" flexDirection="row" justifyContent="space-between">
|
||||||
|
|||||||
@ -105,11 +105,11 @@ export const ValidatorTable = ({
|
|||||||
<Box width="100%" height="340px" display="flex" flexDirection="column" justifyContent="space-between" gap="10px">
|
<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">
|
{!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">
|
<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="h6">GHOST Connect 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">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 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="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')}>
|
<PrimaryButton onClick={() => window.open('https://git.ghostchain.io/ghostchain/ghost-extension-wallet/releases', '_blank', 'noopener,noreferrer')}>
|
||||||
Get GHOST Extension
|
Get GHOST Connect
|
||||||
</PrimaryButton>
|
</PrimaryButton>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>}
|
</Box>}
|
||||||
|
|||||||
@ -46,6 +46,7 @@ const SwapContainer = ({
|
|||||||
refetch: balanceRefetchTop,
|
refetch: balanceRefetchTop,
|
||||||
contractAddress: addressTop,
|
contractAddress: addressTop,
|
||||||
} = useBalance(chainId, tokenNameTop, address);
|
} = useBalance(chainId, tokenNameTop, address);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
balance: balanceBottom,
|
balance: balanceBottom,
|
||||||
refetch: balanceRefetchBottom,
|
refetch: balanceRefetchBottom,
|
||||||
|
|||||||
@ -67,11 +67,7 @@ const Governance = ({ connect, config, address, chainId }) => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Divider sx={{ marginTop: "30px" }} />
|
<Box mt="45px" display="flex" flexDirection="column" alignItems="center" justifyContent="center">
|
||||||
<Box display="flex" justifyContent="center">Redeem Collateral</Box>
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<Box mt="15px" display="flex" flexDirection="column" alignItems="center" justifyContent="center">
|
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
fullWidth
|
fullWidth
|
||||||
onClick={() => navigate(`/governance/create`)}
|
onClick={() => navigate(`/governance/create`)}
|
||||||
@ -84,7 +80,7 @@ const Governance = ({ connect, config, address, chainId }) => {
|
|||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
<ProposalsList config={config} chainId={chainId} />
|
<ProposalsList address={address} config={config} chainId={chainId} />
|
||||||
</Box>
|
</Box>
|
||||||
</Container>
|
</Container>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { useState, useMemo } from "react";
|
import { useState, useMemo, useCallback, useEffect } from "react";
|
||||||
import toast from "react-hot-toast";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
@ -21,14 +20,20 @@ import {
|
|||||||
|
|
||||||
import GhostStyledIcon from "../../components/Icon/GhostIcon";
|
import GhostStyledIcon from "../../components/Icon/GhostIcon";
|
||||||
import ArrowUpIcon from "../../assets/icons/arrow-up.svg?react";
|
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 Paper from "../../components/Paper/Paper";
|
||||||
import PageTitle from "../../components/PageTitle/PageTitle";
|
import PageTitle from "../../components/PageTitle/PageTitle";
|
||||||
import { PrimaryButton, TertiaryButton } from "../../components/Button";
|
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 ProposalModal from "./components/ProposalModal";
|
||||||
import { parseFunctionCalldata } from "./components/functions/index";
|
import { parseFunctionCalldata } from "./components/functions/index";
|
||||||
|
import { MY_PROPOSALS_PREFIX, VOTED_PROPOSALS_PREFIX } from "./helpers";
|
||||||
|
|
||||||
const NewProposal = ({ config, address, connect, chainId }) => {
|
const NewProposal = ({ config, address, connect, chainId }) => {
|
||||||
const isSemiSmallScreen = useMediaQuery("(max-width: 745px)");
|
const isSemiSmallScreen = useMediaQuery("(max-width: 745px)");
|
||||||
@ -37,24 +42,52 @@ const NewProposal = ({ config, address, connect, chainId }) => {
|
|||||||
|
|
||||||
const theme = useTheme();
|
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 [isModalOpened, setIsModalOpened] = useState(false);
|
||||||
const [proposalFunctions, setProposalFunctions] = useState([]);
|
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 addCalldata = (calldata) => setProposalFunctions(prev => [...prev, calldata]);
|
||||||
const removeCalldata = (index) => setProposalFunctions(prev => prev.filter((_, i) => i !== index));
|
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 nativeCurrency = useMemo(() => {
|
||||||
const client = config?.getClient();
|
const client = config?.getClient();
|
||||||
return client?.chain?.nativeCurrency?.symbol;
|
return client?.chain?.nativeCurrency?.symbol;
|
||||||
}, [config]);
|
}, [config]);
|
||||||
|
|
||||||
const submitProposal = () => {
|
const submitProposal = useCallback(async () => {
|
||||||
toast.success("Coming soon! It's already connected to the chain data!", { duration: 5000 });
|
setIsPending(true);
|
||||||
|
|
||||||
|
const result = await propose(chainId, address, proposalFunctions, proposalDescription);
|
||||||
|
if (result) {
|
||||||
|
storeProposal(proposalHash);
|
||||||
setProposalFunctions([]);
|
setProposalFunctions([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setIsPending(false);
|
||||||
|
}, [chainId, address, proposalHash, proposalFunctions, proposalDescription]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ProposalModal
|
<ProposalModal
|
||||||
@ -88,6 +121,11 @@ const NewProposal = ({ config, address, connect, chainId }) => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
|
topRight={
|
||||||
|
<PrimaryButton variant="text" href="http://ghostchain.io/governance">
|
||||||
|
Explore Governance
|
||||||
|
</PrimaryButton>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Box>
|
<Box>
|
||||||
<Box>
|
<Box>
|
||||||
@ -101,21 +139,45 @@ const NewProposal = ({ config, address, connect, chainId }) => {
|
|||||||
marginTop="25px"
|
marginTop="25px"
|
||||||
>
|
>
|
||||||
<Typography variant="subtitle1">
|
<Typography variant="subtitle1">
|
||||||
Create new proposal by adding functions below
|
Create new proposal by adding one or more of the functions below.
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<PrimaryButton
|
|
||||||
variant="text"
|
|
||||||
href="https://forum.ghostchain.io"
|
|
||||||
>
|
|
||||||
Learn more
|
|
||||||
</PrimaryButton>
|
|
||||||
</Box>}
|
</Box>}
|
||||||
|
|
||||||
</Box>
|
</Box>
|
||||||
<Box display="flex" flexDirection="column" alignItems="center">
|
<Box display="flex" flexDirection="column" alignItems="center">
|
||||||
<TertiaryButton sx={{ maxWidth: isSemiSmallScreen ? "100%" : "350px" }} fullWidth onClick={() => setIsModalOpened(true)}>Add New</TertiaryButton>
|
<Box sx={{ width: isSemiSmallScreen ? "100%" : "350px" }}>
|
||||||
<PrimaryButton sx={{ maxWidth: isSemiSmallScreen ? "100%" : "350px" }} fullWidth onClick={() => submitProposal()}>Submit Proposal</PrimaryButton>
|
<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>
|
||||||
</Box>
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@ -1,35 +1,57 @@
|
|||||||
import { useEffect, useState, useMemo } from "react";
|
import { useEffect, useState, useMemo } from "react";
|
||||||
import ReactGA from "react-ga4";
|
import ReactGA from "react-ga4";
|
||||||
import { useParams } from 'react-router-dom';
|
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 Paper from "../../components/Paper/Paper";
|
||||||
import PageTitle from "../../components/PageTitle/PageTitle";
|
import PageTitle from "../../components/PageTitle/PageTitle";
|
||||||
import LinearProgressBar from "../../components/Progress/LinearProgressBar";
|
import LinearProgressBar from "../../components/Progress/LinearProgressBar";
|
||||||
import InfoTooltip from "../../components/Tooltip/InfoTooltip";
|
import InfoTooltip from "../../components/Tooltip/InfoTooltip";
|
||||||
import Chip from "../../components/Chip/Chip";
|
import Chip from "../../components/Chip/Chip";
|
||||||
import { SecondaryButton } from "../../components/Button";
|
import { PrimaryButton, SecondaryButton } from "../../components/Button";
|
||||||
|
|
||||||
import GhostStyledIcon from "../../components/Icon/GhostIcon";
|
import GhostStyledIcon from "../../components/Icon/GhostIcon";
|
||||||
import ArrowUpIcon from "../../assets/icons/arrow-up.svg?react";
|
import ArrowUpIcon from "../../assets/icons/arrow-up.svg?react";
|
||||||
|
|
||||||
import { formatNumber } from "../../helpers";
|
import { formatNumber, shorten } from "../../helpers";
|
||||||
import { prettifySecondsInDays } from "../../helpers/timeUtil";
|
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 {
|
import {
|
||||||
useProposalStatus,
|
useProposalState,
|
||||||
useProposalProposer,
|
useProposalProposer,
|
||||||
useProposalLocked,
|
useProposalLocked,
|
||||||
useProposalQuorum,
|
useProposalQuorum,
|
||||||
|
useProposalVoteOf,
|
||||||
useProposalVotes,
|
useProposalVotes,
|
||||||
|
useProposalDetails,
|
||||||
useProposalSnapshot,
|
useProposalSnapshot,
|
||||||
useProposalDeadline,
|
useProposalDeadline,
|
||||||
useProposalVotingDelay
|
useProposalVotingDelay,
|
||||||
|
castVote,
|
||||||
|
executeProposal,
|
||||||
|
releaseLocked
|
||||||
} from "../../hooks/governance";
|
} from "../../hooks/governance";
|
||||||
|
|
||||||
///////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////
|
||||||
@ -48,8 +70,11 @@ import RepeatIcon from '@mui/icons-material/Repeat';
|
|||||||
|
|
||||||
const HUNDRED = new DecimalBigNumber(100n, 0);
|
const HUNDRED = new DecimalBigNumber(100n, 0);
|
||||||
|
|
||||||
const ProposalDetails = ({ chainId, address }) => {
|
const ProposalDetails = ({ chainId, address, connect, config }) => {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
const proposalId = BigInt(id);
|
||||||
|
|
||||||
|
const [isPending, setIsPending] = useState(false);
|
||||||
const [selectedDiscussionUrl, setSelectedDiscussionUrl] = useState(undefined);
|
const [selectedDiscussionUrl, setSelectedDiscussionUrl] = useState(undefined);
|
||||||
|
|
||||||
const isSemiSmallScreen = useMediaQuery("(max-width: 745px)");
|
const isSemiSmallScreen = useMediaQuery("(max-width: 745px)");
|
||||||
@ -60,22 +85,24 @@ const ProposalDetails = ({ chainId, address }) => {
|
|||||||
|
|
||||||
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
||||||
const { balance } = useBalance(chainId, "GHST", address);
|
const { balance } = useBalance(chainId, "GHST", address);
|
||||||
const { totalSupply } = useTotalSupply(chainId, "GHST"); // TODO: revisit
|
|
||||||
|
|
||||||
const { status: proposalStatus } = useProposalStatus(chainId, id);
|
const { state: proposalState } = useProposalState(chainId, proposalId);
|
||||||
const { proposer: proposalProposer } = useProposalProposer(chainId, id);
|
const { proposer: proposalProposer } = useProposalProposer(chainId, proposalId);
|
||||||
const { locked: proposalLocked } = useProposalLocked(chainId, id);
|
const { locked: proposalLocked } = useProposalLocked(chainId, proposalId);
|
||||||
const { quorum: proposalQuorum } = useProposalQuorum(chainId, id);
|
const { quorum: proposalQuorum } = useProposalQuorum(chainId, proposalId);
|
||||||
const { forVotes, againstVotes, totalVotes } = useProposalVotes(chainId, id);
|
|
||||||
|
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(() => {
|
useEffect(() => {
|
||||||
ReactGA.send({ hitType: "pageview", page: `/governance/${id}` });
|
ReactGA.send({ hitType: "pageview", page: `/governance/${id}` });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const isDiscussionModalOpened = useMemo(() => {
|
|
||||||
return selectedDiscussionUrl !== undefined;
|
|
||||||
}, [selectedDiscussionUrl]);
|
|
||||||
|
|
||||||
const quorumPercentage = useMemo(() => {
|
const quorumPercentage = useMemo(() => {
|
||||||
if (totalSupply._value === 0n) return 0;
|
if (totalSupply._value === 0n) return 0;
|
||||||
return proposalQuorum / totalSupply * HUNDRED;
|
return proposalQuorum / totalSupply * HUNDRED;
|
||||||
@ -83,17 +110,78 @@ const ProposalDetails = ({ chainId, address }) => {
|
|||||||
|
|
||||||
const votePercentage = useMemo(() => {
|
const votePercentage = useMemo(() => {
|
||||||
if (totalSupply._value === 0n) return 0;
|
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]);
|
}, [totalVotes, totalSupply]);
|
||||||
|
|
||||||
const voteWeightPercentage = useMemo(() => {
|
const voteWeightPercentage = useMemo(() => {
|
||||||
if (totalSupply._value === 0n) return 0;
|
if (totalSupply._value === 0n) return 0;
|
||||||
return balance / totalSupply * HUNDRED;
|
const value = (pastVotes?._value ?? 0n) * 100n / (totalSupply?._value ?? 1n);
|
||||||
}, [balance, totalSupply]);
|
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 (
|
return (
|
||||||
<Box>
|
<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
|
<Container
|
||||||
style={{
|
style={{
|
||||||
paddingLeft: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
|
paddingLeft: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
|
||||||
@ -116,31 +204,18 @@ const ProposalDetails = ({ chainId, address }) => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
<Chip
|
<Chip
|
||||||
sx={{ marginTop: "4px", width: "88px" }}
|
sx={{ marginTop: "4px", width: "88px" }}
|
||||||
label={proposalStatus}
|
label={convertStatusToLabel(proposalState)}
|
||||||
template={convertStatusToTemplate(proposalStatus)}
|
template={convertStatusToTemplate(proposalState)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
topRight={
|
topRight={
|
||||||
<Link
|
<PrimaryButton sx={{ padding: "0 !important"}} variant="text" href={"https://forum.ghostchain.io"} >
|
||||||
color={theme.colors.primary[300]}
|
View Forum
|
||||||
href="https://forum.ghostchain.io"
|
</PrimaryButton>
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
display="flex"
|
|
||||||
alignItems="center"
|
|
||||||
>
|
|
||||||
View Forum
|
|
||||||
<GhostStyledIcon
|
|
||||||
style={{ marginTop: "7px" }}
|
|
||||||
viewBox="0 0 30 30"
|
|
||||||
className="external-site-link-icon"
|
|
||||||
component={ArrowUpIcon}
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<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" flexDirection="column">
|
||||||
<Box display="flex" justifyContent="space-between">
|
<Box display="flex" justifyContent="space-between">
|
||||||
<Typography sx={{ textShadow: "0 0 black", fontWeight: 600 }} variant="body1" color={theme.colors.feedback.success}>
|
<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}
|
barColor={theme.colors.feedback.success}
|
||||||
barBackground={theme.colors.feedback.error}
|
barBackground={theme.colors.feedback.error}
|
||||||
variant="determinate"
|
variant="determinate"
|
||||||
value={69}
|
value={voteValue}
|
||||||
target={Math.floor(Math.random() * 101)}
|
target={voteTarget}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box display="flex" flexDirection="column">
|
<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" justifyContent="space-between">
|
||||||
<Box display="flex" flexDirection="row">
|
<Box display="flex" flexDirection="row">
|
||||||
<Typography>Quorum</Typography>
|
<Typography>Quorum</Typography>
|
||||||
@ -181,13 +269,37 @@ const ProposalDetails = ({ chainId, address }) => {
|
|||||||
<Typography>Votes</Typography>
|
<Typography>Votes</Typography>
|
||||||
<InfoTooltip message={`Your voting power, as percentage of total $${ghstSymbol} at the time when proposal was created`} />
|
<InfoTooltip message={`Your voting power, as percentage of total $${ghstSymbol} at the time when proposal was created`} />
|
||||||
</Box>
|
</Box>
|
||||||
<Typography>{formatNumber(balance.toString(), 4)} ({formatNumber(voteWeightPercentage, 1)}%)</Typography>
|
<Typography>{formatNumber(pastVotes.toString(), 4)} ({formatNumber(voteWeightPercentage, 1)}%)</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box display="flex" gap="20px">
|
<Box display="flex" gap="20px">
|
||||||
<SecondaryButton fullWidth onClick={() => alert("For vote casted")}>For</SecondaryButton>
|
{address === undefined || address === ""
|
||||||
<SecondaryButton fullWidth onClick={() => alert("Against vote casted")}>Against</SecondaryButton>
|
? <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>
|
||||||
</Box>
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
@ -196,14 +308,24 @@ const ProposalDetails = ({ chainId, address }) => {
|
|||||||
fullWidth
|
fullWidth
|
||||||
enableBackground
|
enableBackground
|
||||||
headerContent={
|
headerContent={
|
||||||
<Box display="flex" alignItems="center" flexDirection="row" gap="5px">
|
<Box height="48px" display="flex" alignItems="center" flexDirection="row" gap="5px">
|
||||||
<Typography variant="h6">
|
<Typography variant="h6">
|
||||||
Timeline
|
Timeline
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</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>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
@ -219,14 +341,28 @@ const ProposalDetails = ({ chainId, address }) => {
|
|||||||
</Box>
|
</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>
|
</Paper>
|
||||||
</Container>
|
</Container>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const VotingTimeline = ({ proposalId, chainId }) => {
|
const VotingTimeline = ({ connect, handleExecute, handleRelease, proposalLocked, proposalId, chainId, state, address, isProposer }) => {
|
||||||
const { delay: propsalVotingDelay } = useProposalVotingDelay(chainId, proposalId);
|
const { delay: propsalVotingDelay } = useProposalVotingDelay(chainId, proposalId);
|
||||||
const { snapshot: proposalSnapshot } = useProposalSnapshot(chainId, proposalId);
|
const { snapshot: proposalSnapshot } = useProposalSnapshot(chainId, proposalId);
|
||||||
const { deadline: proposalDeadline } = useProposalDeadline(chainId, proposalId);
|
const { deadline: proposalDeadline } = useProposalDeadline(chainId, proposalId);
|
||||||
@ -235,28 +371,69 @@ const VotingTimeline = ({ proposalId, chainId }) => {
|
|||||||
if (proposalSnapshot && propsalVotingDelay) {
|
if (proposalSnapshot && propsalVotingDelay) {
|
||||||
return proposalSnapshot > propsalVotingDelay ? proposalSnapshot - propsalVotingDelay : 0;
|
return proposalSnapshot > propsalVotingDelay ? proposalSnapshot - propsalVotingDelay : 0;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0n;
|
||||||
}, [proposalSnapshot, propsalVotingDelay]);
|
}, [proposalSnapshot, propsalVotingDelay]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<Box height="280px" display="flex" flexDirection="column" justifyContent="space-between" gap="20px">
|
||||||
<Timeline sx={{ margin: 0, padding: 0 }}>
|
<Timeline sx={{ margin: 0, padding: 0 }}>
|
||||||
<VotingTimelineItem time={voteStarted} message="Proposed on:" isFirst />
|
<VotingTimelineItem chainId={chainId} occured={voteStarted} message="Proposed on" isFirst />
|
||||||
<VotingTimelineItem time={proposalSnapshot} message="Voting started:" />
|
<VotingTimelineItem chainId={chainId} occured={proposalSnapshot} message="Voting started" />
|
||||||
<VotingTimelineItem time={proposalDeadline} message="Voting ends:" isLast />
|
<VotingTimelineItem chainId={chainId} occured={proposalDeadline} message="Voting ends" />
|
||||||
</Timeline>
|
</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 (
|
return (
|
||||||
<TimelineItem>
|
<TimelineItem>
|
||||||
<TimelineOppositeContent
|
<TimelineOppositeContent
|
||||||
sx={{
|
sx={{
|
||||||
maxWidth: "120px",
|
|
||||||
textAlign: "left",
|
|
||||||
paddingLeft: "0",
|
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
|
justifyContent: "flex-end",
|
||||||
|
padding: "0 16px",
|
||||||
|
minWidth: "140px",
|
||||||
|
flex: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography>{message}</Typography>
|
<Typography>{message}</Typography>
|
||||||
@ -272,11 +449,11 @@ const VotingTimelineItem = ({ isFirst, isLast, time, message }) => {
|
|||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
|
justifyContent: "flex-start",
|
||||||
|
padding: "0 26px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography>
|
<Typography>{timestamp}</Typography>
|
||||||
{new Date(time * 1000).toLocaleString()}
|
|
||||||
</Typography>
|
|
||||||
</TimelineContent>
|
</TimelineContent>
|
||||||
</TimelineItem>
|
</TimelineItem>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -55,7 +55,7 @@ export const ProposalsCount = props => {
|
|||||||
const _props = {
|
const _props = {
|
||||||
...props,
|
...props,
|
||||||
label: `Proposal Count`,
|
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)}`;
|
if (proposalCount || proposalCount === 0n) _props.metric = `${formatNumber(proposalCount.toString(), 0)}`;
|
||||||
|
|||||||
@ -22,7 +22,7 @@ import GhostStyledIcon from "../../../components/Icon/GhostIcon";
|
|||||||
import ArrowUpIcon from "../../../assets/icons/arrow-up.svg?react";
|
import ArrowUpIcon from "../../../assets/icons/arrow-up.svg?react";
|
||||||
|
|
||||||
import { networkAvgBlockSpeed } from "../../../constants";
|
import { networkAvgBlockSpeed } from "../../../constants";
|
||||||
import { prettifySecondsInDays } from "../../../helpers/timeUtil";
|
import { prettifySecondsInDays, prettifySeconds } from "../../../helpers/timeUtil";
|
||||||
|
|
||||||
import Chip from "../../../components/Chip/Chip";
|
import Chip from "../../../components/Chip/Chip";
|
||||||
import Modal from "../../../components/Modal/Modal";
|
import Modal from "../../../components/Modal/Modal";
|
||||||
@ -31,7 +31,12 @@ import LinearProgressBar from "../../../components/Progress/LinearProgressBar";
|
|||||||
import { PrimaryButton, TertiaryButton } from "../../../components/Button";
|
import { PrimaryButton, TertiaryButton } from "../../../components/Button";
|
||||||
|
|
||||||
import ProposalInfoText from "./ProposalInfoText";
|
import ProposalInfoText from "./ProposalInfoText";
|
||||||
import { convertStatusToTemplate } from "../helpers";
|
import {
|
||||||
|
convertStatusToTemplate,
|
||||||
|
convertStatusToLabel,
|
||||||
|
MY_PROPOSALS_PREFIX,
|
||||||
|
VOTED_PROPOSALS_PREFIX
|
||||||
|
} from "../helpers";
|
||||||
|
|
||||||
import { useScreenSize } from "../../../hooks/useScreenSize";
|
import { useScreenSize } from "../../../hooks/useScreenSize";
|
||||||
|
|
||||||
@ -41,29 +46,40 @@ import {
|
|||||||
|
|
||||||
const MAX_PROPOSALS_TO_SHOW = 10;
|
const MAX_PROPOSALS_TO_SHOW = 10;
|
||||||
|
|
||||||
const ProposalsList = ({ chainId, config }) => {
|
const ProposalsList = ({ chainId, address, config }) => {
|
||||||
const isSmallScreen = useScreenSize("md");
|
const isSmallScreen = useScreenSize("md");
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const [blockNumber, setBlockNumber] = useState(0n);
|
|
||||||
const [proposalsFilter, setProposalFilter] = useState("active");
|
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));
|
getBlockNumber(config).then(block => setBlockNumber(block));
|
||||||
|
|
||||||
const filteredProposals = useMemo(() => {
|
if (proposals?.length === 0 && proposalsFilter === "active") {
|
||||||
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) {
|
|
||||||
return (
|
return (
|
||||||
<Box display="flex" justifyContent="center">
|
<Box display="flex" justifyContent="center">
|
||||||
<Typography variant="h4">No proposals yet</Typography>
|
<Typography variant="h4">No proposals yet</Typography>
|
||||||
@ -85,13 +101,13 @@ const ProposalsList = ({ chainId, config }) => {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Box display="flex" flexDirection="column" gap="40px">
|
<Box display="flex" flexDirection="column" gap="40px">
|
||||||
{filteredProposals?.map(proposal => (
|
{proposals?.map(proposal => (
|
||||||
<ProposalCard
|
<ProposalCard
|
||||||
key={proposal.id}
|
key={proposal.hashes.short}
|
||||||
proposal={proposal}
|
proposal={proposal}
|
||||||
blockNumber={blockNumber}
|
blockNumber={blockNumber}
|
||||||
chainId={chainId}
|
chainId={chainId}
|
||||||
openProposal={() => navigate(`/governance/${proposal.id}`)}
|
openProposal={() => navigate(`/governance/${proposal.hashes.full}`)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
@ -132,13 +148,13 @@ const ProposalsList = ({ chainId, config }) => {
|
|||||||
<ProposalFilterTrigger trigger={proposalsFilter} setTrigger={setProposalFilter} />
|
<ProposalFilterTrigger trigger={proposalsFilter} setTrigger={setProposalFilter} />
|
||||||
|
|
||||||
<ProposalTable>
|
<ProposalTable>
|
||||||
{filteredProposals?.map(proposal => (
|
{proposals?.map(proposal => (
|
||||||
<ProposalRow
|
<ProposalRow
|
||||||
key={proposal.id}
|
key={proposal.hashes.short}
|
||||||
proposal={proposal}
|
proposal={proposal}
|
||||||
blockNumber={blockNumber}
|
blockNumber={blockNumber}
|
||||||
chainId={chainId}
|
chainId={chainId}
|
||||||
openProposal={() => navigate(`/governance/${proposal.id}`)}
|
openProposal={() => navigate(`/governance/${proposal.hashes.full}`)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</ProposalTable>
|
</ProposalTable>
|
||||||
@ -155,8 +171,8 @@ const ProposalTable = ({ children }) => (
|
|||||||
<Table aria-label="Available bonds" style={{ tableLayout: "fixed" }}>
|
<Table aria-label="Available bonds" style={{ tableLayout: "fixed" }}>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell align="center" style={{ padding: "8px 0" }}>Proposal ID</TableCell>
|
<TableCell align="center" style={{ width: "130px", padding: "8px 0" }}>Proposal ID</TableCell>
|
||||||
<TableCell align="center" style={{ padding: "8px 0" }}>Status</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" }}>Vote Ends</TableCell>
|
||||||
<TableCell align="center" style={{ padding: "8px 0" }}>Voting Stats</TableCell>
|
<TableCell align="center" style={{ padding: "8px 0" }}>Voting Stats</TableCell>
|
||||||
<TableCell align="center" style={{ padding: "8px 0" }}></TableCell>
|
<TableCell align="center" style={{ padding: "8px 0" }}></TableCell>
|
||||||
@ -171,25 +187,48 @@ const ProposalTable = ({ children }) => (
|
|||||||
const ProposalRow = ({ proposal, blockNumber, openProposal, chainId }) => {
|
const ProposalRow = ({ proposal, blockNumber, openProposal, chainId }) => {
|
||||||
const theme = useTheme();
|
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 (
|
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" }}>
|
<TableCell align="center" style={{ padding: "8px 0" }}>
|
||||||
<Typography>GDP-{proposal.id}</Typography>
|
<Typography>GDP-{proposal.hashes.short}</Typography>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell align="center" style={{ padding: "8px 0" }}>
|
<TableCell align="center" style={{ padding: "8px 0" }}>
|
||||||
<Chip
|
<Chip
|
||||||
sx={{ width: "100px" }}
|
sx={{ width: "100px" }}
|
||||||
label={proposal.status}
|
label={convertStatusToLabel(proposal.state)}
|
||||||
template={convertStatusToTemplate(proposal.status)}
|
template={convertStatusToTemplate(proposal.state)}
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell align="center" style={{ padding: "8px 0" }}>
|
<TableCell align="center" style={{ padding: "8px 0" }}>
|
||||||
<Typography>
|
<Typography>
|
||||||
{convertVoteEnds(
|
{convertDeadline(
|
||||||
proposal.id % 2n === 0n,
|
proposal.deadline,
|
||||||
proposal.voteEnds,
|
|
||||||
blockNumber,
|
blockNumber,
|
||||||
chainId
|
chainId
|
||||||
)}
|
)}
|
||||||
@ -202,24 +241,24 @@ const ProposalRow = ({ proposal, blockNumber, openProposal, chainId }) => {
|
|||||||
barColor={theme.colors.feedback.success}
|
barColor={theme.colors.feedback.success}
|
||||||
barBackground={theme.colors.feedback.error}
|
barBackground={theme.colors.feedback.error}
|
||||||
variant="determinate"
|
variant="determinate"
|
||||||
value={69}
|
value={voteValue}
|
||||||
target={Math.floor(Math.random() * 101)}
|
target={voteTarget}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell align="center" style={{ padding: "8px 0" }}>
|
<TableCell align="center" style={{ padding: "8px 0" }}>
|
||||||
{(proposal.status === "Active" || proposal.status === "Succeeded") && <PrimaryButton
|
{(proposal.state === "Active" || proposal.state === "Succeeded") && <PrimaryButton
|
||||||
fullWidth
|
fullWidth
|
||||||
onClick={() => openProposal()}
|
onClick={() => openProposal()}
|
||||||
sx={{ maxWidth: "130px" }}
|
sx={{ maxWidth: "130px" }}
|
||||||
>
|
>
|
||||||
{proposal.status === "Succeeded" ? "Execute" : "Vote"}
|
{proposal.state === "Succeeded" ? "Execute" : "Vote"}
|
||||||
</PrimaryButton>}
|
</PrimaryButton>}
|
||||||
{(proposal.status !== "Active" && proposal.status !== "Succeeded") && <TertiaryButton
|
{(proposal.state !== "Active" && proposal.state !== "Succeeded") && <TertiaryButton
|
||||||
fullWidth
|
fullWidth
|
||||||
onClick={() => openProposal()}
|
onClick={() => openProposal()}
|
||||||
sx={{ maxWidth: "130px" }}
|
sx={{ alignSelf: "right", maxWidth: "130px" }}
|
||||||
>
|
>
|
||||||
View
|
View
|
||||||
</TertiaryButton>}
|
</TertiaryButton>}
|
||||||
@ -233,21 +272,20 @@ const ProposalCard = ({ proposal, blockNumber, openProposal, chainId }) => {
|
|||||||
const isSmallScreen = useMediaQuery('(max-width: 450px)');
|
const isSmallScreen = useMediaQuery('(max-width: 450px)');
|
||||||
|
|
||||||
return (
|
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={isSmallScreen ? "column" : "row"} justifyContent="space-between">
|
||||||
<Box display="flex" flexDirection="column" width="100%">
|
<Box display="flex" flexDirection="column" width="100%">
|
||||||
<Box display="flex" flexDirection="row" alignItems="center" width="100%" gap="10px">
|
<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
|
<Chip
|
||||||
sx={{ width: "88px" }}
|
sx={{ width: "88px" }}
|
||||||
label={proposal.status}
|
label={convertStatusToLabel(proposal.state)}
|
||||||
template={convertStatusToTemplate(proposal.status)}
|
template={convertStatusToTemplate(proposal.state)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Typography>
|
<Typography>
|
||||||
{convertVoteEnds(
|
{convertDeadline(
|
||||||
proposal.id % 2n === 0n,
|
proposal.deadline,
|
||||||
proposal.voteEnds,
|
|
||||||
blockNumber,
|
blockNumber,
|
||||||
chainId
|
chainId
|
||||||
)}
|
)}
|
||||||
@ -265,13 +303,13 @@ const ProposalCard = ({ proposal, blockNumber, openProposal, chainId }) => {
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Box marginBottom="20px">
|
<Box marginBottom="20px">
|
||||||
{(proposal.status === "Active" || proposal.status === "Succeeded") && <PrimaryButton
|
{(proposal.state === "Active" || proposal.state === "Succeeded") && <PrimaryButton
|
||||||
fullWidth
|
fullWidth
|
||||||
onClick={() => openProposal()}
|
onClick={() => openProposal()}
|
||||||
>
|
>
|
||||||
{proposal.status === "Succeeded" ? "Execute" : "Vote"}
|
{proposal.state === "Succeeded" ? "Execute" : "Vote"}
|
||||||
</PrimaryButton>}
|
</PrimaryButton>}
|
||||||
{(proposal.status !== "Active" && proposal.status !== "Succeeded") && <TertiaryButton
|
{(proposal.state !== "Active" && proposal.state !== "Succeeded") && <TertiaryButton
|
||||||
fullWidth
|
fullWidth
|
||||||
onClick={() => openProposal()}
|
onClick={() => openProposal()}
|
||||||
>
|
>
|
||||||
@ -300,11 +338,11 @@ const ProposalFilterTrigger = ({ trigger, setTrigger }) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const convertVoteEnds = (tmp, voteEnds, blockNumber, chainId) => {
|
const convertDeadline = (deadline, blockNumber, chainId) => {
|
||||||
const tmpVoteSeconds = Number(voteEnds * networkAvgBlockSpeed(chainId));
|
const diff = blockNumber > deadline ? blockNumber - deadline : deadline - blockNumber;
|
||||||
const tmpSeconds = (tmp ? tmpVoteSeconds : -tmpVoteSeconds);
|
const voteSeconds = Number(diff * networkAvgBlockSpeed(chainId));
|
||||||
|
|
||||||
const result = prettifySecondsInDays(tmpSeconds);
|
const result = prettifySeconds(voteSeconds, "mins");
|
||||||
if (result === "now") {
|
if (result === "now") {
|
||||||
return new Date(Date.now()).toLocaleDateString('en-US', {
|
return new Date(Date.now()).toLocaleDateString('en-US', {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import { ParsedCell } from "./index";
|
|||||||
|
|
||||||
export const prepareAuditReservesCalldata = (chainId) => {
|
export const prepareAuditReservesCalldata = (chainId) => {
|
||||||
const value = 0n;
|
const value = 0n;
|
||||||
const label = "Audit Reserves";
|
const label = "auditReserves";
|
||||||
const target = DAO_TREASURY_ADDRESSES[chainId];
|
const target = DAO_TREASURY_ADDRESSES[chainId];
|
||||||
const calldata = encodeFunctionData({
|
const calldata = encodeFunctionData({
|
||||||
abi: TreasuryAbi,
|
abi: TreasuryAbi,
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { PrimaryButton, TertiaryButton } from "../../../../components/Button";
|
|||||||
import { shorten } from "../../../../helpers";
|
import { shorten } from "../../../../helpers";
|
||||||
import { useTokenSymbol } from "../../../../hooks/tokens";
|
import { useTokenSymbol } from "../../../../hooks/tokens";
|
||||||
|
|
||||||
import { BooleanTrigger, ArgumentInput, ParsedCell } from "./index";
|
import { ArgumentsWrapper, BooleanTrigger, ArgumentInput, ParsedCell } from "./index";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
RESERVE_ADDRESSES,
|
RESERVE_ADDRESSES,
|
||||||
@ -19,7 +19,7 @@ import { abi as DepositoryAbi } from "../../../../abi/GhostBondDepository.json";
|
|||||||
|
|
||||||
export const prepareCreateBondCalldata = (chainId, markets, terms, quoteToken, intervals, booleans) => {
|
export const prepareCreateBondCalldata = (chainId, markets, terms, quoteToken, intervals, booleans) => {
|
||||||
const value = 0n;
|
const value = 0n;
|
||||||
const label = "Create Bond";
|
const label = "create";
|
||||||
const target = BOND_DEPOSITORY_ADDRESSES[chainId];
|
const target = BOND_DEPOSITORY_ADDRESSES[chainId];
|
||||||
const calldata = encodeFunctionData({
|
const calldata = encodeFunctionData({
|
||||||
abi: DepositoryAbi,
|
abi: DepositoryAbi,
|
||||||
@ -39,7 +39,7 @@ export const CreateBondParsed = (props) => {
|
|||||||
<Modal
|
<Modal
|
||||||
headerContent={
|
headerContent={
|
||||||
<Box display="flex" justifyContent="center" alignItems="center" gap="15px">
|
<Box display="flex" justifyContent="center" alignItems="center" gap="15px">
|
||||||
<Typography variant="h4">View Create Bond</Typography>
|
<Typography variant="h4">View create</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
open={isOpened}
|
open={isOpened}
|
||||||
@ -80,6 +80,8 @@ export const CreateBondSteps = ({ nativeCurrency, ftsoSymbol, chainId, toInitial
|
|||||||
const [vestingLength, setVestingLength] = useState(args?.at(1)?.at(0));
|
const [vestingLength, setVestingLength] = useState(args?.at(1)?.at(0));
|
||||||
const [conclusionTimestamp, setConclusionTimestamp] = useState(args?.at(1)?.at(1));
|
const [conclusionTimestamp, setConclusionTimestamp] = useState(args?.at(1)?.at(1));
|
||||||
|
|
||||||
|
const { symbol: reserveSymbol } = useTokenSymbol(chainId, RESERVE_ADDRESSES[chainId]);
|
||||||
|
|
||||||
const handleProceed = () => {
|
const handleProceed = () => {
|
||||||
const markets = [capacity, initialPrice, debtBuffer];
|
const markets = [capacity, initialPrice, debtBuffer];
|
||||||
const terms = [vestingLength, conclusionTimestamp];
|
const terms = [vestingLength, conclusionTimestamp];
|
||||||
@ -116,8 +118,8 @@ export const CreateBondSteps = ({ nativeCurrency, ftsoSymbol, chainId, toInitial
|
|||||||
}, [step, tokenAddress, capacity, initialPrice, debtBuffer, depositInterval, tuneInterval]);
|
}, [step, tokenAddress, capacity, initialPrice, debtBuffer, depositInterval, tuneInterval]);
|
||||||
|
|
||||||
const possibleTokens = [
|
const possibleTokens = [
|
||||||
{ value: FTSO_DAI_LP_ADDRESSES[chainId], label: `${ftsoSymbol}-${nativeCurrency} LP: ${shorten(FTSO_DAI_LP_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], label: `${ftsoSymbol}: ${shorten(RESERVE_ADDRESSES[chainId])}` },
|
{ value: RESERVE_ADDRESSES[chainId], symbol: reserveSymbol, label: `${reserveSymbol}: ${shorten(RESERVE_ADDRESSES[chainId])}` },
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -136,7 +138,7 @@ export const CreateBondSteps = ({ nativeCurrency, ftsoSymbol, chainId, toInitial
|
|||||||
/>}
|
/>}
|
||||||
{step === 2 && <MarketArguments
|
{step === 2 && <MarketArguments
|
||||||
capacityInQuote={capacityInQuote}
|
capacityInQuote={capacityInQuote}
|
||||||
nativeCurrency={nativeCurrency}
|
currencySymbol={possibleTokens.find(token => token.value === tokenAddress).symbol}
|
||||||
ftsoSymbol={ftsoSymbol}
|
ftsoSymbol={ftsoSymbol}
|
||||||
capacity={capacity}
|
capacity={capacity}
|
||||||
setCapacity={createMode ? setCapacity : empty}
|
setCapacity={createMode ? setCapacity : empty}
|
||||||
@ -171,7 +173,7 @@ export const CreateBondSteps = ({ nativeCurrency, ftsoSymbol, chainId, toInitial
|
|||||||
|
|
||||||
const MarketArguments = ({
|
const MarketArguments = ({
|
||||||
capacityInQuote,
|
capacityInQuote,
|
||||||
nativeCurrency,
|
currencySymbol,
|
||||||
ftsoSymbol,
|
ftsoSymbol,
|
||||||
capacity,
|
capacity,
|
||||||
setCapacity,
|
setCapacity,
|
||||||
@ -183,25 +185,25 @@ const MarketArguments = ({
|
|||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<ArgumentInput
|
<ArgumentInput
|
||||||
endString={capacityInQuote ? nativeCurrency : ftsoSymbol}
|
endString={capacityInQuote ? currencySymbol : ftsoSymbol}
|
||||||
label="Bond Capacity"
|
label="market[0]"
|
||||||
value={capacity ?? ""}
|
value={capacity ?? ""}
|
||||||
setValue={setCapacity}
|
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
|
<ArgumentInput
|
||||||
endString={nativeCurrency}
|
endString={currencySymbol}
|
||||||
label="Bond Initial Price"
|
label="market[1]"
|
||||||
value={initialPrice ?? ""}
|
value={initialPrice ?? ""}
|
||||||
setValue={setInitialPrice}
|
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
|
<ArgumentInput
|
||||||
endString="%"
|
endString="%"
|
||||||
label="Debt buffer"
|
label="market[2]"
|
||||||
value={debtBuffer ?? ""}
|
value={debtBuffer ?? ""}
|
||||||
setValue={setDebtBuffer}
|
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>
|
</Box>
|
||||||
)
|
)
|
||||||
@ -217,17 +219,17 @@ const IntervalsArguments = ({
|
|||||||
<Box>
|
<Box>
|
||||||
<ArgumentInput
|
<ArgumentInput
|
||||||
endString="seconds"
|
endString="seconds"
|
||||||
label="Deposit Interval"
|
label="_intervals[0]"
|
||||||
value={depositInterval ?? ""}
|
value={depositInterval ?? ""}
|
||||||
setValue={setDepositInterval}
|
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
|
<ArgumentInput
|
||||||
endString="seconds"
|
endString="seconds"
|
||||||
label="Tune interval"
|
label="_intervals[0]"
|
||||||
value={tuneInterval ?? ""}
|
value={tuneInterval ?? ""}
|
||||||
setValue={setTuneInterval}
|
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>
|
</Box>
|
||||||
)
|
)
|
||||||
@ -244,19 +246,20 @@ const TermsAgruments = ({
|
|||||||
<Box>
|
<Box>
|
||||||
<ArgumentInput
|
<ArgumentInput
|
||||||
endString="seconds"
|
endString="seconds"
|
||||||
label={fixedTerm ? "Vesting Duration" : "Vested Timestamp"}
|
label={"_terms[0]"}
|
||||||
value={vestingLength ?? ""}
|
value={vestingLength ?? ""}
|
||||||
setValue={setVestingLength}
|
setValue={setVestingLength}
|
||||||
tooltip={`Determines the vesting schedule: ${fixedTerm
|
tooltip={fixedTerm
|
||||||
? "a relative time offset from the point of purchase"
|
? "Time in seconds representing bond vesting schedule. Bond becomes fully claimable after this time elapses from purchase date."
|
||||||
: "a static maturity timestamp"}`}
|
: "Absolute Unix timestamp when all bonds mature and become fully claimable, regardless of the purchase date."
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<ArgumentInput
|
<ArgumentInput
|
||||||
endString="seconds"
|
endString="seconds"
|
||||||
label="Bond Conclusion"
|
label="_terms[1]"
|
||||||
value={conclusionTimestamp ?? ""}
|
value={conclusionTimestamp ?? ""}
|
||||||
setValue={setConclusionTimestamp}
|
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>
|
</Box>
|
||||||
)
|
)
|
||||||
@ -288,22 +291,26 @@ const TokenAndBooleansArguments = ({
|
|||||||
<Box>
|
<Box>
|
||||||
<BooleanTrigger
|
<BooleanTrigger
|
||||||
value={capacityInQuote}
|
value={capacityInQuote}
|
||||||
label="Capacity Type"
|
label="_booleans[0]"
|
||||||
leftText={nativeCurrency}
|
leftText={"True"}
|
||||||
rightText={ftsoSymbol}
|
rightText={"False"}
|
||||||
setLeftValue={() => setCapacityInQuote(true)}
|
setLeftValue={() => setCapacityInQuote(true)}
|
||||||
setRightValue={() => setCapacityInQuote(false)}
|
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
|
<BooleanTrigger
|
||||||
value={fixedTerm}
|
value={fixedTerm}
|
||||||
label="Fixed Term"
|
label="_booleans[1]"
|
||||||
leftText="Yes"
|
leftText="True"
|
||||||
rightText="No"
|
rightText="False"
|
||||||
setLeftValue={() => setFixedTerm(true)}
|
setLeftValue={() => setFixedTerm(true)}
|
||||||
setRightValue={() => setFixedTerm(false)}
|
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.`}
|
||||||
/>
|
/>
|
||||||
|
<ArgumentsWrapper
|
||||||
|
label="_quoteToken"
|
||||||
|
tooltip={`ERC20 token that users pay with to purchase bonds (e.g. ${nativeCurrency} or ${nativeCurrency}-${ftsoSymbol} LP token).`}
|
||||||
|
>
|
||||||
<Select
|
<Select
|
||||||
value={selectedOption ?? ""}
|
value={selectedOption ?? ""}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
@ -321,6 +328,7 @@ const TokenAndBooleansArguments = ({
|
|||||||
return possibleTokens.find(opt => opt.value === selected)?.label || selected;
|
return possibleTokens.find(opt => opt.value === selected)?.label || selected;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</ArgumentsWrapper>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import { BooleanTrigger, ArgumentInput, ParsedCell } from "./index";
|
|||||||
|
|
||||||
export const prepareSetAdjustmentCalldata = (chainId, rateChange, targetRate, increase) => {
|
export const prepareSetAdjustmentCalldata = (chainId, rateChange, targetRate, increase) => {
|
||||||
const value = 0n;
|
const value = 0n;
|
||||||
const label = "Set Adjustment";
|
const label = "setAdjustment";
|
||||||
const target = DISTRIBUTOR_ADDRESSES[chainId];
|
const target = DISTRIBUTOR_ADDRESSES[chainId];
|
||||||
const calldata = encodeFunctionData({
|
const calldata = encodeFunctionData({
|
||||||
abi: DistributorAbi,
|
abi: DistributorAbi,
|
||||||
@ -33,7 +33,7 @@ export const SetAdjustmentParsed = (props) => {
|
|||||||
<Modal
|
<Modal
|
||||||
headerContent={
|
headerContent={
|
||||||
<Box display="flex" justifyContent="center" alignItems="center" gap="15px">
|
<Box display="flex" justifyContent="center" alignItems="center" gap="15px">
|
||||||
<Typography variant="h4">View Set Adjustment</Typography>
|
<Typography variant="h4">View setAdjustment</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
open={isOpened}
|
open={isOpened}
|
||||||
@ -70,28 +70,28 @@ export const SetAdjustmentSteps = ({ chainId, toInitialStep, addCalldata, args }
|
|||||||
<Box>
|
<Box>
|
||||||
<BooleanTrigger
|
<BooleanTrigger
|
||||||
value={increase}
|
value={increase}
|
||||||
leftText="Add"
|
leftText="True"
|
||||||
rightText="Sub"
|
rightText="False"
|
||||||
setLeftValue={() => createMode ? setIncrease(true) : {}}
|
setLeftValue={() => createMode ? setIncrease(true) : {}}
|
||||||
setRightValue={() => createMode ? setIncrease(false) : {}}
|
setRightValue={() => createMode ? setIncrease(false) : {}}
|
||||||
label="Direction"
|
label="add"
|
||||||
tooltip="Determines the direction of the adjustment. When Add the parameter value will gradually increase; when Sub, it will decrease toward the target rate"
|
tooltip="Adjusts the current rate toward the target eCSPR staking rate. True = increase rate, False = decrease rate."
|
||||||
/>
|
/>
|
||||||
<ArgumentInput
|
<ArgumentInput
|
||||||
disabled={!createMode}
|
disabled={!createMode}
|
||||||
endString="%"
|
endString="%"
|
||||||
label="Change Rate"
|
label="rate"
|
||||||
value={rate ?? ""}
|
value={rate ?? ""}
|
||||||
setValue={createMode ? setRate : () => {}}
|
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
|
<ArgumentInput
|
||||||
disabled={!createMode}
|
disabled={!createMode}
|
||||||
endString="%"
|
endString="%"
|
||||||
label="Target Rate"
|
label="target"
|
||||||
value={target ?? ""}
|
value={target ?? ""}
|
||||||
setValue={createMode ? setTarget : () => {}}
|
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>
|
</Box>
|
||||||
{createMode && <Box width="100%" sx={{ marginTop: "20px" }} display="flex" flexDirection="column" gap="5px">
|
{createMode && <Box width="100%" sx={{ marginTop: "20px" }} display="flex" flexDirection="column" gap="5px">
|
||||||
|
|||||||
@ -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."
|
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 = [
|
export const allPossibleFunctions = [
|
||||||
{ value: "auditReserves", label: "Audit Reserves" },
|
{ value: "auditReserves", label: "auditReserves" },
|
||||||
{ value: "setAdjustment", label: "Change APY" },
|
{ value: "setAdjustment", label: "setAdjustment" },
|
||||||
{ value: "createBond", label: "Create Bond" },
|
{ value: "create", label: "create" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const allAbis = [TreasuryAbi, DistributorAbi, DepositoryAbi];
|
const allAbis = [TreasuryAbi, DistributorAbi, DepositoryAbi];
|
||||||
@ -47,10 +47,9 @@ const identifyAction = (calldata) => {
|
|||||||
export const parseFunctionCalldata = (metadata, index, chainId, nativeCoin, removeCalldata) => {
|
export const parseFunctionCalldata = (metadata, index, chainId, nativeCoin, removeCalldata) => {
|
||||||
const { label, calldata, target, value } = metadata;
|
const { label, calldata, target, value } = metadata;
|
||||||
const { functionName, args } = identifyAction(calldata);
|
const { functionName, args } = identifyAction(calldata);
|
||||||
|
const labelOrName = label ?? (functionName ?? "Unknown");
|
||||||
|
|
||||||
const remove = () => removeCalldata(index);
|
const remove = removeCalldata && (() => removeCalldata(index));
|
||||||
|
|
||||||
console.log(`function arguments for ${label}: ${args}`);
|
|
||||||
|
|
||||||
switch (functionName) {
|
switch (functionName) {
|
||||||
case "auditReserves":
|
case "auditReserves":
|
||||||
@ -58,7 +57,7 @@ export const parseFunctionCalldata = (metadata, index, chainId, nativeCoin, remo
|
|||||||
isTable
|
isTable
|
||||||
key={index}
|
key={index}
|
||||||
calldata={calldata}
|
calldata={calldata}
|
||||||
label={label}
|
label={labelOrName}
|
||||||
chainId={chainId}
|
chainId={chainId}
|
||||||
remove={remove}
|
remove={remove}
|
||||||
nativeCoin={nativeCoin}
|
nativeCoin={nativeCoin}
|
||||||
@ -72,7 +71,7 @@ export const parseFunctionCalldata = (metadata, index, chainId, nativeCoin, remo
|
|||||||
args={args}
|
args={args}
|
||||||
key={index}
|
key={index}
|
||||||
calldata={calldata}
|
calldata={calldata}
|
||||||
label={label}
|
label={labelOrName}
|
||||||
chainId={chainId}
|
chainId={chainId}
|
||||||
remove={remove}
|
remove={remove}
|
||||||
nativeCoin={nativeCoin}
|
nativeCoin={nativeCoin}
|
||||||
@ -86,7 +85,7 @@ export const parseFunctionCalldata = (metadata, index, chainId, nativeCoin, remo
|
|||||||
args={args}
|
args={args}
|
||||||
key={index}
|
key={index}
|
||||||
calldata={calldata}
|
calldata={calldata}
|
||||||
label={label}
|
label={labelOrName}
|
||||||
chainId={chainId}
|
chainId={chainId}
|
||||||
remove={remove}
|
remove={remove}
|
||||||
nativeCoin={nativeCoin}
|
nativeCoin={nativeCoin}
|
||||||
@ -99,7 +98,7 @@ export const parseFunctionCalldata = (metadata, index, chainId, nativeCoin, remo
|
|||||||
isTable
|
isTable
|
||||||
key={index}
|
key={index}
|
||||||
calldata={calldata}
|
calldata={calldata}
|
||||||
label={label}
|
label="Unknown"
|
||||||
remove={remove}
|
remove={remove}
|
||||||
nativeCoin={nativeCoin}
|
nativeCoin={nativeCoin}
|
||||||
value={value}
|
value={value}
|
||||||
@ -115,7 +114,7 @@ export const getFunctionArguments = (functionName) => {
|
|||||||
return null;
|
return null;
|
||||||
case "setAdjustment":
|
case "setAdjustment":
|
||||||
return SetAdjustmentSteps;
|
return SetAdjustmentSteps;
|
||||||
case "createBond":
|
case "create":
|
||||||
return CreateBondSteps;
|
return CreateBondSteps;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
@ -128,7 +127,7 @@ export const getFunctionCalldata = (functionName, chainId) => {
|
|||||||
return prepareAuditReservesCalldata(chainId);
|
return prepareAuditReservesCalldata(chainId);
|
||||||
case "setAdjustment":
|
case "setAdjustment":
|
||||||
return prepareSetAdjustmentCalldata(chainId);
|
return prepareSetAdjustmentCalldata(chainId);
|
||||||
case "createBond":
|
case "create":
|
||||||
return prepareCreateBondCalldata(chainId);
|
return prepareCreateBondCalldata(chainId);
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
@ -141,7 +140,7 @@ export const getFunctionDescription = (functionName) => {
|
|||||||
return prepareAuditReservesDescription;
|
return prepareAuditReservesDescription;
|
||||||
case "setAdjustment":
|
case "setAdjustment":
|
||||||
return prepareSetAdjustmentDescription;
|
return prepareSetAdjustmentDescription;
|
||||||
case "createBond":
|
case "create":
|
||||||
return prepareCreateBondDescription;
|
return prepareCreateBondDescription;
|
||||||
default:
|
default:
|
||||||
return DEFAULT_DESCRIPTION;
|
return DEFAULT_DESCRIPTION;
|
||||||
@ -261,7 +260,7 @@ export const ParsedCell = (props) => {
|
|||||||
url = url + `/address/${props.target}`;
|
url = url + `/address/${props.target}`;
|
||||||
}
|
}
|
||||||
return url;
|
return url;
|
||||||
}, [props]);
|
}, [props, config]);
|
||||||
|
|
||||||
const handleCalldataCopy = async () => {
|
const handleCalldataCopy = async () => {
|
||||||
try {
|
try {
|
||||||
@ -295,7 +294,7 @@ export const ParsedCell = (props) => {
|
|||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell align="center" style={{ padding: "8px 0" }}>
|
<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>
|
||||||
|
|
||||||
<TableCell>
|
<TableCell>
|
||||||
|
|||||||
@ -1,14 +1,38 @@
|
|||||||
|
export const MY_PROPOSALS_PREFIX = "MY_PROPOSALS";
|
||||||
|
export const VOTED_PROPOSALS_PREFIX = "VOTED_PROPOSALS";
|
||||||
|
|
||||||
export const convertStatusToTemplate = (status) => {
|
export const convertStatusToTemplate = (status) => {
|
||||||
switch (status.toUpperCase()) {
|
switch (status) {
|
||||||
case "EXECUTED":
|
case 7:
|
||||||
return 'darkGray';
|
return 'darkGray';
|
||||||
case "CANCELED":
|
case 2:
|
||||||
return 'warning';
|
return 'warning';
|
||||||
case "SUCCEEDED":
|
case 4:
|
||||||
return 'success';
|
return 'success';
|
||||||
case "DEFEATED":
|
case 3:
|
||||||
return 'error';
|
return 'error';
|
||||||
default:
|
default:
|
||||||
return 'darkGray';
|
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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,18 +1,74 @@
|
|||||||
import { useReadContract, useReadContracts } from "wagmi";
|
import { useReadContract, useReadContracts } from "wagmi";
|
||||||
import { simulateContract, writeContract, waitForTransactionReceipt } from "@wagmi/core";
|
import { simulateContract, writeContract, waitForTransactionReceipt } from "@wagmi/core";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
import { keccak256, stringToBytes } from 'viem'
|
||||||
|
|
||||||
import { config } from "../../config";
|
import { config } from "../../config";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
GHOST_GOVERNANCE_ADDRESSES,
|
GHOST_GOVERNANCE_ADDRESSES,
|
||||||
} from "../../constants/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 GovernorStorageAbi } from "../../abi/GovernorStorage.json";
|
||||||
import { abi as GovernorVotesQuorumFractionAbi } from "../../abi/GovernorVotesQuorumFraction.json";
|
import { abi as GovernorVotesQuorumFractionAbi } from "../../abi/GovernorVotesQuorumFraction.json";
|
||||||
|
|
||||||
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
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) => {
|
export const useMinQuorum = (chainId) => {
|
||||||
const { data: quorumNumerator, refetch: quorumNumeratorRefetch } = useReadContract({
|
const { data: quorumNumerator, refetch: quorumNumeratorRefetch } = useReadContract({
|
||||||
@ -40,7 +96,8 @@ export const useMinQuorum = (chainId) => {
|
|||||||
|
|
||||||
export const useProposalThreshold = (chainId, name) => {
|
export const useProposalThreshold = (chainId, name) => {
|
||||||
const decimals = getTokenDecimals(name);
|
const decimals = getTokenDecimals(name);
|
||||||
const { data, refetch } = useReadContract({
|
|
||||||
|
const { data } = useReadContract({
|
||||||
abi: GovernorStorageAbi,
|
abi: GovernorStorageAbi,
|
||||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||||
functionName: "proposalThreshold",
|
functionName: "proposalThreshold",
|
||||||
@ -48,7 +105,23 @@ export const useProposalThreshold = (chainId, name) => {
|
|||||||
chainId: chainId,
|
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 };
|
return { threshold };
|
||||||
}
|
}
|
||||||
@ -61,87 +134,456 @@ export const useProposalCount = (chainId) => {
|
|||||||
scopeKey: `proposalCount-${chainId}`,
|
scopeKey: `proposalCount-${chainId}`,
|
||||||
chainId: chainId,
|
chainId: chainId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const proposalCount = data ?? 0n;
|
const proposalCount = data ?? 0n;
|
||||||
|
|
||||||
return { proposalCount };
|
return { proposalCount };
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useProposalStatus = (chainId, proposalId) => {
|
export const useProposalState = (chainId, proposalId) => {
|
||||||
const status = "Succeeded";
|
const { data } = useReadContract({
|
||||||
return { status };
|
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) => {
|
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 };
|
return { proposer };
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useProposalLocked = (chainId, proposalId) => {
|
export const useProposalLocked = (chainId, proposalId) => {
|
||||||
const decimals = getTokenDecimals(name);
|
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 }
|
return { locked }
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useProposalQuorum = (chainId, proposalId) => {
|
export const useProposalQuorum = (chainId, proposalId) => {
|
||||||
const decimals = getTokenDecimals(name);
|
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 }
|
return { quorum }
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useProposalVotes = (chainId, proposalId) => {
|
export const useProposalVotes = (chainId, proposalId) => {
|
||||||
const decimals = getTokenDecimals(name);
|
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 { data } = useReadContract({
|
||||||
const totalVotes = new DecimalBigNumber(489_000_000_000_000_000_000n, decimals);
|
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 }
|
return { forVotes, againstVotes, totalVotes }
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useProposalSnapshot = (chainId, proposalId) => {
|
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 };
|
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) => {
|
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 };
|
return { deadline };
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useProposalVotingDelay = (chainId, proposalId) => {
|
export const useProposalVotingDelay = (chainId) => {
|
||||||
const delay = 1;
|
const { data } = useReadContract({
|
||||||
|
abi: GovernorAbi,
|
||||||
|
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||||
|
functionName: "votingDelay",
|
||||||
|
scopeKey: `votingDelay-${chainId}`,
|
||||||
|
chainId: chainId,
|
||||||
|
});
|
||||||
|
const delay = data ?? 0n;
|
||||||
return { delay };
|
return { delay };
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useProposals = (chainId, depth) => {
|
export const useProposals = (chainId, depth, searchedIndexes) => {
|
||||||
const decimals = getTokenDecimals(name);
|
const decimals = getTokenDecimals("GHST");
|
||||||
const { proposalCount } = useProposalCount(chainId);
|
const ghstAbi = getTokenAbi("GHST");
|
||||||
|
const ghstAddress = getTokenAddress(chainId, "GHST");
|
||||||
|
|
||||||
const statuses = [
|
const { proposalCount } = useProposalCount(chainId);
|
||||||
"Active",
|
|
||||||
"Executed",
|
|
||||||
"Canceled",
|
|
||||||
"Succeeded",
|
|
||||||
"Defeated"
|
|
||||||
];
|
|
||||||
let proposals = [];
|
|
||||||
|
|
||||||
const start = Number(proposalCount);
|
const start = Number(proposalCount);
|
||||||
const end = Math.max(0, start - depth);
|
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 { data: proposalsDetailsAt } = useReadContracts({
|
||||||
const voteEnds = 50n;
|
contracts: indexes?.map(index => {
|
||||||
const yesVotes = new DecimalBigNumber(1337_000_000_000_000_000_000, decimals);
|
return {
|
||||||
const noVotes = new DecimalBigNumber(420_000_000_000_000_000_000, decimals);
|
abi: GovernorStorageAbi,
|
||||||
|
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||||
proposals.push({
|
functionName: "proposalDetailsAt",
|
||||||
id: i,
|
args: [index],
|
||||||
discussion: "https://google.com",
|
scopeKey: `proposalDetailsAt-${chainId}-${index}`,
|
||||||
status: statuses[i % statuses.length],
|
chainId: chainId,
|
||||||
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 };
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -48,6 +48,9 @@ export const getTokenAbi = (name) => {
|
|||||||
case "WETH":
|
case "WETH":
|
||||||
abi = WethAbi;
|
abi = WethAbi;
|
||||||
break;
|
break;
|
||||||
|
case "WMWETH":
|
||||||
|
abi = WethAbi;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return abi;
|
return abi;
|
||||||
}
|
}
|
||||||
@ -86,6 +89,9 @@ export const getTokenDecimals = (name) => {
|
|||||||
case "WETH":
|
case "WETH":
|
||||||
decimals = 18;
|
decimals = 18;
|
||||||
break;
|
break;
|
||||||
|
case "WMWETH":
|
||||||
|
decimals = 18;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return decimals;
|
return decimals;
|
||||||
}
|
}
|
||||||
@ -133,6 +139,9 @@ export const getTokenAddress = (chainId, name) => {
|
|||||||
case "WETC":
|
case "WETC":
|
||||||
address = WETH_ADDRESSES[chainId];
|
address = WETH_ADDRESSES[chainId];
|
||||||
break;
|
break;
|
||||||
|
case "WMETC":
|
||||||
|
address = WETH_ADDRESSES[chainId];
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return address;
|
return address;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,40 @@ import { shorten } from "../../helpers";
|
|||||||
import { tokenNameConverter } from "../../helpers/tokenConverter";
|
import { tokenNameConverter } from "../../helpers/tokenConverter";
|
||||||
import { config } from "../../config";
|
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) => {
|
export const useTotalSupply = (chainId, name) => {
|
||||||
const contractAddress = getTokenAddress(chainId, name);
|
const contractAddress = getTokenAddress(chainId, name);
|
||||||
const { data, refetch } = useToken({
|
const { data, refetch } = useToken({
|
||||||
@ -26,7 +60,7 @@ export const useTotalSupply = (chainId, name) => {
|
|||||||
|
|
||||||
export const useBalance = (chainId, name, address) => {
|
export const useBalance = (chainId, name, address) => {
|
||||||
const contractAddress = getTokenAddress(chainId, name);
|
const contractAddress = getTokenAddress(chainId, name);
|
||||||
const { data, refetch } = useInnerBalance({
|
const { data, refetch, error } = useInnerBalance({
|
||||||
address,
|
address,
|
||||||
chainId,
|
chainId,
|
||||||
scopeKey: `balance-${contractAddress}-${address}-${chainId}`,
|
scopeKey: `balance-${contractAddress}-${address}-${chainId}`,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user