calldata preparation and parsing added

Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
This commit is contained in:
Uncle Fatso 2026-02-11 21:44:59 +03:00
parent 982e191474
commit 126a890999
Signed by: f4ts0
GPG Key ID: 565F4F2860226EBB
20 changed files with 458 additions and 260 deletions

View File

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

View File

@ -14,7 +14,7 @@ import Sidebar from "./components/Sidebar/Sidebar";
import TopBar from "./components/TopBar/TopBar";
import { shouldTriggerSafetyCheck } from "./helpers";
import { isNetworkAvailable, isNetworkLegacy } from "./constants";
import { isNetworkAvailable, isNetworkLegacy, isGovernanceAvailable } from "./constants";
import useTheme from "./hooks/useTheme";
import { useUnstableProvider } from "./hooks/ghost";
import { dark as darkTheme } from "./themes/dark.js";
@ -216,9 +216,9 @@ function App() {
}
<Route path="/bridge" element={<Bridge config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
<Route path="/dex/:name" element={<Dex connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
<Route path="/governance" element={<Governance config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
<Route path="/governance/:id" element={<ProposalDetails config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
<Route path="/governance/create" element={<NewProposal config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
{isGovernanceAvailable(chainId, addressChainId) && <Route path="/governance" element={<Governance config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />}
{isGovernanceAvailable(chainId, addressChainId) && <Route path="/governance/:id" element={<ProposalDetails config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />}
{isGovernanceAvailable(chainId, addressChainId) && <Route path="/governance/create" element={<NewProposal config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />}
</>
}
<Route path="/empty" element={<NotFound

File diff suppressed because one or more lines are too long

1
src/abi/Governor.json Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -32,6 +32,7 @@ const Select = ({
onChange,
options,
inputWidth,
renderValue = null,
width = "100%",
}) => {
const theme = useTheme();
@ -56,6 +57,7 @@ const Select = ({
onChange={onChange}
inputWidth={inputWidth}
IconComponent={KeyboardArrowDownIcon}
renderValue={renderValue}
displayEmpty
>
{options.map((opt) => (

View File

@ -39,7 +39,7 @@ import BondIcon from "../Icon/BondIcon";
import StakeIcon from "../Icon/StakeIcon";
import WrapIcon from "../Icon/WrapIcon";
import { isNetworkAvailable, isNetworkLegacy } from "../../constants";
import { isNetworkAvailable, isNetworkLegacy, isGovernanceAvailable } from "../../constants";
import { AVAILABLE_DEXES } from "../../constants/dexes";
import { ECOSYSTEM } from "../../constants/ecosystem";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
@ -180,7 +180,7 @@ const NavContent = ({ chainId, addressChainId }) => {
/>
<NavItem icon={StakeIcon} label={`Stake`} to="/stake" />
<NavItem icon={ForkRightIcon} label={`Bridge`} to="/bridge" />
<NavItem icon={GavelIcon} label={`Governance`} to="/governance" />
{isGovernanceAvailable(chainId, addressChainId) && <NavItem icon={GavelIcon} label={`Governance`} to="/governance" />}
<Box className="menu-divider">
<Divider />
</Box>

View File

@ -4,6 +4,19 @@ export enum NetworkId {
TESTNET_MORDOR = 63,
}
export const isGovernanceAvailable = (chainId, addressChainId) => {
chainId = addressChainId ? addressChainId : chainId;
let exists = false;
switch (chainId) {
case 11155111:
exists = true
break;
default:
break;
}
return exists;
}
export const isNetworkAvailable = (chainId, addressChainId) => {
chainId = addressChainId ? addressChainId : chainId;
let exists = false;

View File

@ -1,25 +1,25 @@
import { NetworkId } from "../constants";
export const STAKING_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xC2C579631Bf6daA93252154080fecfd68c6aa506",//"0xd90E63E88282596E1ea33765b41Ba3d650f4aD52",
[NetworkId.TESTNET_SEPOLIA]: "0xC2C579631Bf6daA93252154080fecfd68c6aa506",
[NetworkId.TESTNET_HOODI]: "0x25F62eDc6C89FF84E957C22336A35d2dfc861a86",
[NetworkId.TESTNET_MORDOR]: "0xC25C9C56a89ebd6ef291b415d00ACfa7913c55e7",
};
export const BOND_DEPOSITORY_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x46BF6F7c3e96351eab7542f2B14c9f2ac6d08dF0",//"0xdcE486113280e49ca2fB200258E5Ee1B2D21D495",
[NetworkId.TESTNET_SEPOLIA]: "0x46BF6F7c3e96351eab7542f2B14c9f2ac6d08dF0",
[NetworkId.TESTNET_HOODI]: "0x6Ad50B1E293E68B2fC230c576220a93A9D311571",
[NetworkId.TESTNET_MORDOR]: "0x7C85cDEddBAd0f50453d373F7332BEa11ECa7BAf",
};
export const DAO_TREASURY_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x05D797f9F34844594C956da58f1785997397f02E",//"0x93dd30f819403710de7933B79A74C4A42438458D",
[NetworkId.TESTNET_SEPOLIA]: "0x05D797f9F34844594C956da58f1785997397f02E",
[NetworkId.TESTNET_HOODI]: "0x1a1b29b18f714fac9dDabEf530dFc4f85b56A6e8",
[NetworkId.TESTNET_MORDOR]: "0x5883C8e2259556B534036c7fDF4555E09dE9f243",
};
export const FTSO_DAI_LP_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x1394dC3f7bABaa2F0CA80353648087DAB1BF3fd6", // TBD
[NetworkId.TESTNET_SEPOLIA]: "0xCd1505E5d169525e0241c177aF5929A92E02276D",
[NetworkId.TESTNET_HOODI]: "0xf7B2d44209E70782d93A70F7D8eC50010dF7ae50",
[NetworkId.TESTNET_MORDOR]: "0xE6546D12665dB5B22Cb92FB9e0221aE51A57aeaa",
};
@ -31,7 +31,7 @@ export const FTSO_STNK_LP_ADDRESSES = {
}
export const RESERVE_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14",//"0x5f63a27a9214a0352F2EF8dAF1eD4974d713192B",
[NetworkId.TESTNET_SEPOLIA]: "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14",
[NetworkId.TESTNET_HOODI]: "0x80c6676c334BCcE60b3CC852085B72143379CE58",
[NetworkId.TESTNET_MORDOR]: "0x6af91B3763b5d020E0985f85555EB50e5852d7AC",
};
@ -43,37 +43,35 @@ export const WETH_ADDRESSES = {
};
export const GHST_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x1eCee8BfceC44e535B3Ee92Aca70507668781392",//"0xdf2e5306A3dCcfA4e21bbF4226C17Ff5B008dDC4",
[NetworkId.TESTNET_SEPOLIA]: "0x1eCee8BfceC44e535B3Ee92Aca70507668781392",
[NetworkId.TESTNET_HOODI]: "0xE98f7426457E6533B206e91B7EcA97aa8A258B46",
[NetworkId.TESTNET_MORDOR]: "0x14b5787F8a1E62786F50A7998A9b14aa24298423",
};
export const STNK_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xa31cf59baC26Dd8A8b422b999eB1Ba541C941EA7",//"0x02C296A27eA779d5a16F934337c12062C5E3c0D9",
[NetworkId.TESTNET_SEPOLIA]: "0xa31cf59baC26Dd8A8b422b999eB1Ba541C941EA7",
[NetworkId.TESTNET_HOODI]: "0xF07e9303A9f16Afd82f4f57Fd6fca68Aa0AB6D7F",
[NetworkId.TESTNET_MORDOR]: "0x137bA9403885D8ECEa95AaFBb8734F5a16121bAC",
};
export const FTSO_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x7ebd1224D36d64eA09312073e60f352d1383801A",//"0xcFedFFEB3FdeCd2196820Ba3b71f3F84A1255f93",
[NetworkId.TESTNET_SEPOLIA]: "0x7ebd1224D36d64eA09312073e60f352d1383801A",
[NetworkId.TESTNET_HOODI]: "0xb184e423811b644A1924334E63985c259F5D0033",
[NetworkId.TESTNET_MORDOR]: "0xeA170CC0faceC531a6a9e93a28C4330Ac50343a1",
};
export const DISTRIBUTOR_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xfa524772eec78FAeD0db2cF8A831FDDa9F5B0544",//"0x8fbF8eB4Fcd451EF62Aee33508D46FE120963194",
[NetworkId.TESTNET_SEPOLIA]: "0xfa524772eec78FAeD0db2cF8A831FDDa9F5B0544",
[NetworkId.TESTNET_HOODI]: "0xdF49dC81c457c6f92e26cf6d686C7a8715255842",
[NetworkId.TESTNET_MORDOR]: "0xaf5e76706520db7fb01096E322940206bf3fce57",
};
export const GHOST_GOVERNANCE_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x4823F1DC785D721eAdD2bD218E1eeD63aF67fBF4",
[NetworkId.TESTNET_HOODI]: "0x1B96B792840d4d19d5097ee007392Ed4d851e64F",
[NetworkId.TESTNET_MORDOR]: "0x3dD438416D9593A58193fC52850E588efAa3D57E",
};
export const BONDING_CALCULATOR_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xfA821181de76D3EAdb404dDe971A6d28289F22b3",//"0x4896bFc6256A57Df826d7144E48c9633d51d6319",
[NetworkId.TESTNET_SEPOLIA]: "0xfA821181de76D3EAdb404dDe971A6d28289F22b3",
[NetworkId.TESTNET_HOODI]: "0x2635d526Ad24b98082563937f7b996075052c6Fd",
[NetworkId.TESTNET_MORDOR]: "0x0c4C7C49a173E2a3f9Eed93125F3F146D8e17bCb",
}

View File

@ -1,4 +1,5 @@
import { useState, useMemo } from "react";
import toast from "react-hot-toast";
import {
Box,
@ -24,6 +25,7 @@ import ArrowUpIcon from "../../assets/icons/arrow-up.svg?react";
import Paper from "../../components/Paper/Paper";
import PageTitle from "../../components/PageTitle/PageTitle";
import { PrimaryButton, TertiaryButton } from "../../components/Button";
import { useTokenSymbol } from "../../hooks/tokens";
import ProposalModal from "./components/ProposalModal";
import { parseFunctionCalldata } from "./components/functions/index";
@ -35,6 +37,8 @@ const NewProposal = ({ config, address, connect, chainId }) => {
const theme = useTheme();
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
const [isModalOpened, setIsModalOpened] = useState(false);
const [proposalFunctions, setProposalFunctions] = useState([]);
@ -47,13 +51,20 @@ const NewProposal = ({ config, address, connect, chainId }) => {
}, [config]);
const submitProposal = () => {
alert("Proposal created");
toast.success("Coming soon! It's already connected to the chain data!", { duration: 5000 });
setProposalFunctions([]);
}
return (
<>
<ProposalModal addCalldata={addCalldata} isOpened={isModalOpened} closeModal={() => setIsModalOpened(false)} />
<ProposalModal
nativeCurrency={nativeCurrency}
ftsoSymbol={ftsoSymbol}
chainId={chainId}
addCalldata={addCalldata}
isOpened={isModalOpened}
closeModal={() => setIsModalOpened(false)}
/>
<Box>
<PageTitle name="Create Proposal" subtitle="Cool text describing what's goinf on there" />
<Container
@ -92,28 +103,19 @@ const NewProposal = ({ config, address, connect, chainId }) => {
<Typography variant="subtitle1">
Create new proposal by adding functions below
</Typography>
<Link
color={theme.colors.primary[300]}
<PrimaryButton
variant="text"
href="https://forum.ghostchain.io"
target="_blank"
rel="noopener noreferrer"
display="flex"
alignItems="center"
>
Learn more&nbsp;
<GhostStyledIcon
style={{ marginTop: "7px" }}
viewBox="0 0 30 30"
className="external-site-link-icon"
component={ArrowUpIcon}
/>
</Link>
Learn more
</PrimaryButton>
</Box>}
</Box>
<Box>
<TertiaryButton fullWidth onClick={() => setIsModalOpened(true)}>Add New</TertiaryButton>
<PrimaryButton fullWidth onClick={() => submitProposal()}>Submit Proposal</PrimaryButton>
<Box display="flex" flexDirection="column" alignItems="center">
<TertiaryButton sx={{ maxWidth: isSemiSmallScreen ? "100%" : "350px" }} fullWidth onClick={() => setIsModalOpened(true)}>Add New</TertiaryButton>
<PrimaryButton sx={{ maxWidth: isSemiSmallScreen ? "100%" : "350px" }} fullWidth onClick={() => submitProposal()}>Submit Proposal</PrimaryButton>
</Box>
</Box>
</Paper>
@ -138,8 +140,8 @@ const NewProposal = ({ config, address, connect, chainId }) => {
<TableCell align="center" style={{ padding: "8px 0" }}>Value</TableCell>
</TableRow>
</TableHead>
<TableBody>{proposalFunctions.map((calldata, index) => {
return parseFunctionCalldata(calldata, index, nativeCurrency, removeCalldata);
<TableBody>{proposalFunctions.map((metadata, index) => {
return parseFunctionCalldata(metadata, index, chainId, nativeCurrency, removeCalldata);
})}</TableBody>
</Table>
</TableContainer>

View File

@ -25,8 +25,10 @@ export const MinQuorumPercentage = props => {
label: `Min Quorum`,
tooltip: `Minimum $${props.ghstSymbol} turnout required for the proposal to become valid`,
};
const tokenValue = formatCurrency(value?.toString(), 2, props.ghstSymbol);
const percentageValue = formatNumber(percentage * 100, 2);
if (percentage) _props.metric = `${formatCurrency(value?.toString(), 2, props.ghstSymbol)} (${formatNumber(percentage * 100, 2)}%)`;
if (percentage) _props.metric = `${tokenValue} (${percentageValue}%)`;
else _props.isLoading = true;
return <Metric {..._props} />;
@ -41,22 +43,22 @@ export const ProposalThreshold = props => {
tooltip: `Minimum $${props.ghstSymbol} required to be locked to create a proposal`,
};
if (threshold) _props.metric = `${formatCurrency(threshold.toString(), 0, props.ghstSymbol)}`;
if (threshold) _props.metric = `${formatCurrency(threshold.toString(), 2, props.ghstSymbol)}`;
else _props.isLoading = true;
return <Metric {..._props} />;
}
export const ProposalsCount = props => {
const { proposalsCount } = useProposalCount(props.chainId);
const { proposalCount } = useProposalCount(props.chainId);
const _props = {
...props,
label: `Proposals Count`,
label: `Proposal Count`,
tooltip: `Total proposals created`,
};
if (proposalsCount) _props.metric = `${formatNumber(proposalsCount.toString(), 0)}`;
if (proposalCount || proposalCount === 0n) _props.metric = `${formatNumber(proposalCount.toString(), 0)}`;
else _props.isLoading = true;
return <Metric {..._props} />;

View File

@ -1,5 +1,5 @@
import { useState, useEffect, useMemo, useCallback } from "react";
import { Box, Typography } from "@mui/material";
import { Box, Typography, useTheme } from "@mui/material";
import Modal from "../../../components/Modal/Modal";
import Select from "../../../components/Select/Select";
@ -12,7 +12,7 @@ import {
allPossibleFunctions
} from "./functions";
const ProposalModal = ({ isOpened, closeModal, addCalldata }) => {
const ProposalModal = ({ isOpened, closeModal, nativeCurrency, ftsoSymbol, addCalldata, chainId }) => {
const [selectedOption, setSelectedOption] = useState();
const [renderArguments, setRenderArguments] = useState(false);
@ -29,10 +29,10 @@ const ProposalModal = ({ isOpened, closeModal, addCalldata }) => {
}, [selectedOption, renderArguments]);
const handleCalldata = useCallback(() => {
addCalldata(getFunctionCalldata(selectedOption));
addCalldata(getFunctionCalldata(selectedOption, chainId));
setSelectedOption(null);
closeModal();
}, [selectedOption]);
}, [selectedOption, chainId]);
const handleClose = () => {
setSelectedOption(null);
@ -45,7 +45,7 @@ const ProposalModal = ({ isOpened, closeModal, addCalldata }) => {
handleClose();
}
const ArgumentsSteps = useMemo(() => getFunctionArguments(selectedOption), [selectedOption]);
const ArgumentsSteps = useMemo(() => getFunctionArguments(selectedOption, chainId), [selectedOption]);
return (
<Modal
@ -62,6 +62,9 @@ const ProposalModal = ({ isOpened, closeModal, addCalldata }) => {
<Box minHeight="220px" display="flex" alignItems="start" justifyContent="space-between" flexDirection="column">
{renderArguments
? <ArgumentsSteps
nativeCurrency={nativeCurrency}
ftsoSymbol={ftsoSymbol}
chainId={chainId}
addCalldata={handleAddCalldata}
toInitialStep={() => setRenderArguments(false)}
/>
@ -79,6 +82,7 @@ const ProposalModal = ({ isOpened, closeModal, addCalldata }) => {
}
const InitialStep = ({ selectedOption, handleChange, handleCalldata, handleProceed, ready }) => {
const theme = useTheme();
const functionDescription = useMemo(() => getFunctionDescription(selectedOption), [selectedOption]);
return (
<Box minHeight="220px" display="flex" alignItems="start" justifyContent="space-between" flexDirection="column">
@ -87,6 +91,17 @@ const InitialStep = ({ selectedOption, handleChange, handleCalldata, handleProce
onChange={handleChange}
options={allPossibleFunctions}
inputWidth="100%"
renderValue={(selected) => {
if (!selected || selected.length === 0) {
return (
<span style={{ color: theme.colors.gray[500], opacity: 0.7 }}>
Select function
</span>
);
}
return allPossibleFunctions.find(opt => opt.value === selected)?.label || selected;
}}
/>
<Typography align="center" variant="body2">{functionDescription}</Typography>
{ready

View File

@ -120,22 +120,12 @@ const ProposalsList = ({ chainId, config }) => {
Proposals
</Typography>
<Link
color={theme.colors.primary[300]}
<PrimaryButton
variant="text"
href="https://forum.ghostchain.io"
target="_blank"
rel="noopener noreferrer"
display="flex"
alignItems="center"
>
View Forum&nbsp;
<GhostStyledIcon
style={{ marginTop: "7px" }}
viewBox="0 0 30 30"
className="external-site-link-icon"
component={ArrowUpIcon}
/>
</Link>
View Forum
</PrimaryButton>
</Box>
}
>
@ -165,8 +155,8 @@ const ProposalTable = ({ children }) => (
<Table aria-label="Available bonds" style={{ tableLayout: "fixed" }}>
<TableHead>
<TableRow>
<TableCell align="center" style={{ width: "100px", padding: "8px 0" }}>Proposal ID</TableCell>
<TableCell align="center" style={{padding: "8px 0" }}>Status</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>Proposal ID</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>Status</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>Vote Ends</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>Voting Stats</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}></TableCell>
@ -222,14 +212,14 @@ const ProposalRow = ({ proposal, blockNumber, openProposal, chainId }) => {
{(proposal.status === "Active" || proposal.status === "Succeeded") && <PrimaryButton
fullWidth
onClick={() => openProposal()}
sx={{ maxWidth: "150px" }}
sx={{ maxWidth: "130px" }}
>
{proposal.status === "Succeeded" ? "Execute" : "Vote"}
</PrimaryButton>}
{(proposal.status !== "Active" && proposal.status !== "Succeeded") && <TertiaryButton
fullWidth
onClick={() => openProposal()}
sx={{ maxWidth: "150px" }}
sx={{ maxWidth: "130px" }}
>
View
</TertiaryButton>}

View File

@ -1,11 +1,26 @@
import { Box, Typography, TableCell, TableRow, useTheme } from "@mui/material";
import { TertiaryButton } from "../../../../components/Button";
import { useMemo } from "react";
import { encodeFunctionData } from 'viem';
import {
DAO_TREASURY_ADDRESSES,
} from "../../../../constants/addresses";
import { abi as TreasuryAbi } from "../../../../abi/GhostTreasury.json";
import { ParsedCell } from "./index";
export const prepareAuditReservesCalldata = (chainId) => {
return "0xauditReserves";
const value = 0n;
const label = "Audit Reserves";
const target = DAO_TREASURY_ADDRESSES[chainId];
const calldata = encodeFunctionData({
abi: TreasuryAbi,
functionName: 'auditReserves',
});
return { label, target, calldata, value };
}
export const prepareAuditReservesDescription = "Explanation about what is audit reserves, pros and cons should be here, we need to fill as much text here as possible to make initial screen usable and not empty.";
export const prepareAuditReservesDescription = "Audit Reserves function audits and updates the protocol's total reserve value. It sums the value of all approved reserve and liquidity tokens, then stores and logs the new total.";
export const AuditReservesSteps = () => {
return null;
@ -20,26 +35,5 @@ export const AuditReservesParsed = (props) => {
}
const AuditReservesParsedCell = (props) => {
return (
<TableRow id={props.id + `--proposalFunction`} data-testid={props.id + `--proposalFunction`}>
<TableCell align="center" style={{ padding: "8px 0" }}>
<Typography>Audit Reserves</Typography>
</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>
<Typography>{props.target}</Typography>
</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>
<Typography>{props.value} {props.nativeCoin}</Typography>
</TableCell>
{props.remove && <TableCell>
<div style={{ display: 'flex', gap: '8px' }}>
<TertiaryButton fullWidth onClick={() => alert("Do we need it????")}>Edit</TertiaryButton>
<TertiaryButton fullWidth onClick={() => props.remove()}>Delete</TertiaryButton>
</div>
</TableCell>}
</TableRow>
)
return <ParsedCell {...props} />
}

View File

@ -1,14 +1,32 @@
import { useRef, useState } from "react";
import { useRef, useMemo, useState, useEffect } from "react";
import { encodeFunctionData } from 'viem';
import { Box, Typography, TableRow, TableCell, useTheme } from "@mui/material";
import Select from "../../../../components/Select/Select";
import { PrimaryButton, TertiaryButton } from "../../../../components/Button";
import { BooleanTrigger, ArgumentInput } from "./index";
import { shorten } from "../../../../helpers";
import { BooleanTrigger, ArgumentInput, ParsedCell } from "./index";
export const prepareCreateBondCalldata = (chainId) => {
return "0xcreateBond";
import {
RESERVE_ADDRESSES,
FTSO_DAI_LP_ADDRESSES,
BOND_DEPOSITORY_ADDRESSES,
} from "../../../../constants/addresses";
import { abi as DepositoryAbi } from "../../../../abi/GhostBondDepository.json";
export const prepareCreateBondCalldata = (chainId, markets, terms, quoteToken, intervals, booleans) => {
const value = 0n;
const label = "Create Bond";
const target = BOND_DEPOSITORY_ADDRESSES[chainId];
const calldata = encodeFunctionData({
abi: DepositoryAbi,
functionName: 'create',
args: [markets, terms, quoteToken, intervals, booleans]
});
return { label, target, calldata, value };
}
export const prepareCreateBondDescription = "Explanation about what is bond creation, pros and cons should be here, we need to fill as much text here as possible to make initial screen usable and not empty.";
export const prepareCreateBondDescription = "Create Bond function creates a new bond market by processing pricing, capacity, and term inputs. It initializes and stores the new bond market's complete configuration in the protocol.";
export const CreateBondParsed = (props) => {
return (
@ -19,41 +37,21 @@ export const CreateBondParsed = (props) => {
}
const CreateBondParsedCell = (props) => {
return (
<TableRow id={props.id + `--proposalFunction`} data-testid={props.id + `--proposalFunction`}>
<TableCell align="center" style={{ padding: "8px 0" }}>
<Typography>Create Bond</Typography>
</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>
<Typography>{props.target}</Typography>
</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>
<Typography>{props.value} {props.nativeCoin}</Typography>
</TableCell>
{props.remove && <TableCell>
<div style={{ display: 'flex', gap: '8px' }}>
<TertiaryButton fullWidth onClick={() => alert("Do we need it????")}>Edit</TertiaryButton>
<TertiaryButton fullWidth onClick={() => props.remove()}>Delete</TertiaryButton>
</div>
</TableCell>}
</TableRow>
)
return <ParsedCell {...props} />
}
export const CreateBondSteps = ({ toInitialStep, addCalldata }) => {
export const CreateBondSteps = ({ nativeCurrency, ftsoSymbol, chainId, toInitialStep, addCalldata }) => {
const [step, setStep] = useState(1);
const [nextDisabled, setNextDisabled] = useState(false);
const [capacity, setCapacity] = useState();
const [initialPrice, setInitialPrice] = useState();
const [debtBuffer, setDebtBuffer] = useState();
const [bondVesting, setBondVesting] = useState();
const [bondDuration, setBondDuration] = useState();
const [depositInterval, setDepositInterval] = useState();
const [tuneInterval, setTuneInterval] = useState();
const [tokenAddress, setTokenAddress] = useState("");
const [tokenAddress, setTokenAddress] = useState();
const [capacityInQuote, setCapacityInQuote] = useState(true);
const [fixedTerm, setFixedTerm] = useState(false);
@ -61,7 +59,12 @@ export const CreateBondSteps = ({ toInitialStep, addCalldata }) => {
const [conclusionTimestamp, setConclusionTimestamp] = useState();
const handleProceed = () => {
addCalldata(prepareCreateBondCalldata())
const markets = [capacity, initialPrice, debtBuffer];
const terms = [vestingLength, conclusionTimestamp];
const intervals = [depositInterval, tuneInterval];
const booleans = [capacityInQuote, fixedTerm];
addCalldata(prepareCreateBondCalldata(chainId, markets, terms, tokenAddress, intervals, booleans))
}
const incrementStep = () => {
@ -73,17 +76,42 @@ export const CreateBondSteps = ({ toInitialStep, addCalldata }) => {
else toInitialStep();
}
const isNextDisabled = useMemo(() => {
switch (step) {
case 1:
return tokenAddress === undefined;
case 2:
return !capacity || !initialPrice || !debtBuffer;
case 3:
return !depositInterval || !tuneInterval;
case 4:
return true;
default:
return false;
}
}, [step, tokenAddress, capacity, initialPrice, debtBuffer, depositInterval, tuneInterval]);
const possibleTokens = [
{ value: FTSO_DAI_LP_ADDRESSES[chainId], label: `${ftsoSymbol}-${nativeCurrency} LP: ${shorten(FTSO_DAI_LP_ADDRESSES[chainId])}` },
{ value: RESERVE_ADDRESSES[chainId], label: `${ftsoSymbol}: ${shorten(RESERVE_ADDRESSES[chainId])}` },
];
return (
<Box height="100%" width="100%" display="flex" flexDirection="column" justifyContent="space-between">
{step === 1 && <AddressAndBooleansArguments
tokenAddress={tokenAddress}
{step === 1 && <TokenAndBooleansArguments
possibleTokens={possibleTokens}
setTokenAddress={setTokenAddress}
nativeCurrency={nativeCurrency}
ftsoSymbol={ftsoSymbol}
capacityInQuote={capacityInQuote}
setCapacityInQuote={setCapacityInQuote}
fixedTerm={fixedTerm}
setFixedTerm={setFixedTerm}
/>}
{step === 2 && <MarketArguments
capacityInQuote={capacityInQuote}
nativeCurrency={nativeCurrency}
ftsoSymbol={ftsoSymbol}
capacity={capacity}
setCapacity={setCapacity}
initialPrice={initialPrice}
@ -91,13 +119,13 @@ export const CreateBondSteps = ({ toInitialStep, addCalldata }) => {
debtBuffer={debtBuffer}
setDebtBuffer={setDebtBuffer}
/>}
{step === 3 && <TermsArguments
bondVesting={bondVesting}
setBondVesting={setBondVesting}
bondDuration={bondDuration}
setBondDuration={setBondDuration}
{step === 3 && <IntervalsArguments
depositInterval={depositInterval}
setDepositInterval={setDepositInterval}
tuneInterval={tuneInterval}
setTuneInterval={setTuneInterval}
/>}
{step === 4 && <IntervalsArguments
{step === 4 && <TermsAgruments
fixedTerm={fixedTerm}
vestingLength={vestingLength}
setVestingLength={setVestingLength}
@ -107,7 +135,7 @@ export const CreateBondSteps = ({ toInitialStep, addCalldata }) => {
<Box width="100%" sx={{ marginTop: "20px" }} display="flex" flexDirection="column" gap="5px">
<Box display="flex" gap="10px">
<TertiaryButton onClick={() => decrementStep()} fullWidth>Back</TertiaryButton>
<TertiaryButton disabled={step === 4} onClick={() => incrementStep()} fullWidth>Next</TertiaryButton>
<TertiaryButton disabled={isNextDisabled} onClick={() => incrementStep()} fullWidth>Next</TertiaryButton>
</Box>
{step === 4 && <PrimaryButton onClick={() => handleProceed()} fullWidth>Create Function</PrimaryButton>}
</Box>
@ -116,6 +144,9 @@ export const CreateBondSteps = ({ toInitialStep, addCalldata }) => {
}
const MarketArguments = ({
capacityInQuote,
nativeCurrency,
ftsoSymbol,
capacity,
setCapacity,
initialPrice,
@ -126,57 +157,57 @@ const MarketArguments = ({
return (
<Box>
<ArgumentInput
endString="CSPR or LP"
endString={capacityInQuote ? nativeCurrency : ftsoSymbol}
label="Bond Capacity"
value={capacity}
value={capacity ?? ""}
setValue={setCapacity}
tooltip="Bond capacity"
tooltip={`Bond market capacity, denominated in ${capacityInQuote ? nativeCurrency : ftsoSymbol}. This determines how the total bond supply is capped`}
/>
<ArgumentInput
endString="???"
endString={nativeCurrency}
label="Bond Initial Price"
value={initialPrice}
value={initialPrice ?? ""}
setValue={setInitialPrice}
tooltip="Bond initial price"
tooltip={`The initial price used to bootstrap the Bond Control Variable (BCV). This price will dynamically adjust based on market demand once trading begins`}
/>
<ArgumentInput
endString="%"
label="Debt buffer"
value={debtBuffer}
value={debtBuffer ?? ""}
setValue={setDebtBuffer}
tooltip="Debt buffer description"
/>
</Box>
)
}
const TermsArguments = ({
bondVesting,
setBondVesting,
bondDuration,
setBondDuration,
}) => {
return (
<Box>
<ArgumentInput
endString="seconds"
label="Bond Vesting"
value={bondVesting}
setValue={setBondVesting}
tooltip="Bond vesting"
/>
<ArgumentInput
endString="seconds"
label="Bond Duration"
value={bondDuration}
setValue={setBondDuration}
tooltip="Bond duration"
tooltip="Safety threshold (in basis points) to cap rapid bond sales. For example, 30,000 equals a 30% buffer above the target debt level"
/>
</Box>
)
}
const IntervalsArguments = ({
depositInterval,
setDepositInterval,
tuneInterval,
setTuneInterval,
}) => {
return (
<Box>
<ArgumentInput
endString="seconds"
label="Deposit Interval"
value={depositInterval ?? ""}
setValue={setDepositInterval}
tooltip="Target timeframe for selling out the market. It regulates price sensitivity: a shorter interval causes the price to drop faster during periods of inactivity"
/>
<ArgumentInput
endString="seconds"
label="Tune interval"
value={tuneInterval ?? ""}
setValue={setTuneInterval}
tooltip="The frequency at which the system ensures the bond's internal price stays aligned with the target market price by auto-tuning at specific intervals"
/>
</Box>
)
}
const TermsAgruments = ({
fixedTerm,
vestingLength,
setVestingLength,
@ -187,40 +218,52 @@ const IntervalsArguments = ({
<Box>
<ArgumentInput
endString="seconds"
label={fixedTerm ? "Vesting Length" : "Vested Timestamp"}
value={vestingLength}
label={fixedTerm ? "Vesting Duration" : "Vested Timestamp"}
value={vestingLength ?? ""}
setValue={setVestingLength}
tooltip={fixedTerm ? "Vesting length what is" : "Vested timestamp what is"}
tooltip={`Determines the vesting schedule: ${fixedTerm
? "a relative time offset from the point of purchase"
: "a static maturity timestamp"}`}
/>
<ArgumentInput
endString="seconds"
label="Bond Conclusion"
value={conclusionTimestamp}
value={conclusionTimestamp ?? ""}
setValue={setConclusionTimestamp}
tooltip="Bond conclusion timestamp"
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"
/>
</Box>
)
}
const AddressAndBooleansArguments = ({
tokenAddress,
const TokenAndBooleansArguments = ({
possibleTokens,
nativeCurrency,
ftsoSymbol,
setTokenAddress,
capacityInQuote,
setCapacityInQuote,
fixedTerm,
setFixedTerm,
}) => {
const theme = useTheme();
const [selectedOption, setSelectedOption] = useState();
const handleChange = (event) => {
setSelectedOption(event.target.value);
setTokenAddress(event.target.value);
};
return (
<Box>
<BooleanTrigger
value={capacityInQuote}
label="Capacity in Quote"
leftText="CSPR?"
rightText="LP?"
label="Capacity Type"
leftText={nativeCurrency}
rightText={ftsoSymbol}
setLeftValue={() => setCapacityInQuote(true)}
setRightValue={() => setCapacityInQuote(false)}
tooltip="WTF is this"
tooltip="Capacity is set in terms of the asset being paid, otherwise it is set in the asset being earned"
/>
<BooleanTrigger
value={fixedTerm}
@ -229,15 +272,24 @@ const AddressAndBooleansArguments = ({
rightText="No"
setLeftValue={() => setFixedTerm(true)}
setRightValue={() => setFixedTerm(false)}
tooltip="Fixed term... No idea what it is"
tooltip="Determines whether the bond has a fixed expiration date or a fixed duration starting from the time of purchase"
/>
<ArgumentInput
placeholder="0x"
inputType="text"
label="Token Address"
value={tokenAddress}
setValue={setTokenAddress}
tooltip="Token used as deposit for the bond"
<Select
value={selectedOption ?? ""}
onChange={handleChange}
options={possibleTokens}
inputWidth="100%"
renderValue={(selected) => {
if (!selected || selected.length === 0) {
return (
<span style={{ color: theme.colors.gray[500], opacity: 0.7 }}>
Select payment token
</span>
);
}
return possibleTokens.find(opt => opt.value === selected)?.label || selected;
}}
/>
</Box>
)

View File

@ -1,15 +1,29 @@
import { useState } from "react";
import { encodeFunctionData } from 'viem';
import { Box, Typography, TableRow, TableCell, useTheme } from "@mui/material";
import { PrimaryButton, TertiaryButton } from "../../../../components/Button";
import { BooleanTrigger, ArgumentInput } from "./index";
import {
DISTRIBUTOR_ADDRESSES,
} from "../../../../constants/addresses";
import { abi as DistributorAbi } from "../../../../abi/GhostDistributor.json";
export const prepareSetAdjustmentCalldata = (chainId) => {
return "0xsetAdjustment";
import { BooleanTrigger, ArgumentInput, ParsedCell } from "./index";
export const prepareSetAdjustmentCalldata = (chainId, rateChange, targetRate, increase) => {
const value = 0n;
const label = "Set Adjustment";
const target = DISTRIBUTOR_ADDRESSES[chainId];
const calldata = encodeFunctionData({
abi: DistributorAbi,
functionName: 'setAdjustment',
args: [rateChange, targetRate, increase]
});
return { label, target, calldata, value };
}
export const prepareSetAdjustmentDescription = "Explanation about what is set adjustment (APY change), pros and cons should be here, we need to fill as much text here as possible to make initial screen usable and not empty.";
export const prepareSetAdjustmentDescription = "Set Adjustment function schedules a gradual change to the staking APY. It increases or decreases the staking APY by a specified rate per epoch until a target rate reached.";
export const SetAdjustmentParsed = (props) => {
return (
@ -20,37 +34,16 @@ export const SetAdjustmentParsed = (props) => {
}
const SetAdjustmentParsedCell = (props) => {
return (
<TableRow id={props.id + `--proposalFunction`} data-testid={props.id + `--proposalFunction`}>
<TableCell align="center" style={{ padding: "8px 0" }}>
<Typography>Set Adjustment</Typography>
</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>
<Typography>{props.target}</Typography>
</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>
<Typography>{props.value} {props.nativeCoin}</Typography>
</TableCell>
{props.remove && <TableCell>
<div style={{ display: 'flex', gap: '8px' }}>
<TertiaryButton fullWidth onClick={() => alert("Do we need it????")}>Edit</TertiaryButton>
<TertiaryButton fullWidth onClick={() => props.remove()}>Delete</TertiaryButton>
</div>
</TableCell>}
</TableRow>
)
return <ParsedCell {...props} />
}
export const SetAdjustmentSteps = ({ toInitialStep, addCalldata }) => {
export const SetAdjustmentSteps = ({ chainId, toInitialStep, addCalldata }) => {
const [rate, setRate] = useState();
const [target, setTarget] = useState();
const [increase, setIncrease] = useState(false);
const handleProceed = () => {
addCalldata(prepareSetAdjustmentCalldata())
addCalldata(prepareSetAdjustmentCalldata(chainId, rate, target, increase));
}
return (
@ -63,21 +56,21 @@ export const SetAdjustmentSteps = ({ toInitialStep, addCalldata }) => {
setLeftValue={() => setIncrease(true)}
setRightValue={() => setIncrease(false)}
label="Direction"
tooltip="To add or not to 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"
/>
<ArgumentInput
endString="%"
label="Change Rate"
value={rate}
value={rate ?? ""}
setValue={setRate}
tooltip="APY will change each time by this value, by basis points"
tooltip="Each epoch, the current staking reward rate changes by this amount until the target rate is reached"
/>
<ArgumentInput
endString="%"
label="Target Rate"
value={target}
value={target ?? ""}
setValue={setTarget}
tooltip="APY will change until it reaches this value, it's in basis points"
tooltip="The final desired value for the reward rate, the adjustment process will continue automatically until this specific level is reached"
/>
</Box>
<Box width="100%" sx={{ marginTop: "20px" }} display="flex" flexDirection="column" gap="5px">
@ -85,7 +78,13 @@ export const SetAdjustmentSteps = ({ toInitialStep, addCalldata }) => {
<TertiaryButton onClick={toInitialStep} fullWidth>Back</TertiaryButton>
<TertiaryButton disabled fullWidth>Next</TertiaryButton>
</Box>
<PrimaryButton onClick={() => handleProceed()} fullWidth>Create Function</PrimaryButton>
<PrimaryButton
disabled={!target || !rate}
onClick={() => handleProceed()}
fullWidth
>
Create Function
</PrimaryButton>
</Box>
</Box>
);

View File

@ -1,49 +1,85 @@
import { useRef } from "react";
import { Box, Typography, useTheme } from "@mui/material";
import { useRef, useMemo } from "react";
import { Box, Typography, Link, TableCell, TableRow, useTheme } from "@mui/material";
import { decodeFunctionData } from 'viem';
import { StyledInputBase } from "../../../../components/Swap/SwapCard";
import { TertiaryButton } from "../../../../components/Button";
import InfoTooltip from "../../../../components/Tooltip/InfoTooltip";
import { abi as TreasuryAbi } from "../../../../abi/GhostTreasury.json";
import { abi as DistributorAbi } from "../../../../abi/GhostDistributor.json";
import { abi as DepositoryAbi } from "../../../../abi/GhostBondDepository.json";
import { config } from "../../../../config";
import { shorten, formatCurrency } from "../../../../helpers";
import { prepareAuditReservesDescription, prepareAuditReservesCalldata, AuditReservesSteps, AuditReservesParsed } from "./AuditReserves";
import { prepareSetAdjustmentDescription, prepareSetAdjustmentCalldata, SetAdjustmentSteps, SetAdjustmentParsed } from "./SetAdjustment";
import { prepareCreateBondDescription, prepareCreateBondCalldata, CreateBondSteps, CreateBondParsed } from "./CreateBond";
const DEFAULT_DESCRIPTION = "Select function for the proposal to start. Make sure you are ready to fill the proposal, here should be pretty long text just to fill space. Here we are trying to make everything looks cool."
const DEFAULT_DESCRIPTION = "Please select the function to include in your proposal. Multi-functional proposals are allowed, but each included function should be clearly specified."
export const allPossibleFunctions = [
{ value: "auditReserves", label: "Audit Reserves" },
{ value: "setAdjustment", label: "Change APY" },
{ value: "createBond", label: "Create Bond" },
{ value: "setAdjustment", label: "Change APY" },
{ value: "createBond", label: "Create Bond" },
];
export const parseFunctionCalldata = (calldata, index, nativeCoin, removeCalldata) => {
const target = "0x5324..123123";
const value = 0;
const allAbis = [TreasuryAbi, DistributorAbi, DepositoryAbi];
const identifyAction = (calldata) => {
let decoded = { functionName: "Unknown", args: [] };
for (const abi of allAbis) {
try {
decoded = decodeFunctionData({
abi: abi,
data: calldata,
});
return decoded;
} catch (err) {
continue;
}
}
return decoded;
}
export const parseFunctionCalldata = (metadata, index, chainId, nativeCoin, removeCalldata) => {
const { label, calldata, target, value } = metadata;
const { functionName, args } = identifyAction(calldata);
const remove = () => removeCalldata(index);
switch (true) {
case calldata.includes("auditReserves"):
console.log(`function arguments for ${label}: ${args}`);
switch (functionName) {
case "auditReserves":
return <AuditReservesParsed
isTable
key={index}
label={label}
chainId={chainId}
remove={remove}
nativeCoin={nativeCoin}
value={value}
target={target}
id={index}
/>;
case calldata.includes("setAdjustment"):
case "setAdjustment":
return <SetAdjustmentParsed
isTable
key={index}
label={label}
remove={remove}
nativeCoin={nativeCoin}
value={value}
target={target}
id={index}
/>;
case calldata.includes("createBond"):
case "create":
return <CreateBondParsed
isTable
key={index}
label={label}
remove={remove}
nativeCoin={nativeCoin}
value={value}
@ -51,7 +87,16 @@ export const parseFunctionCalldata = (calldata, index, nativeCoin, removeCalldat
id={index}
/>;
default:
return null;
return <ParsedCell
isTable
key={index}
label={label}
remove={remove}
nativeCoin={nativeCoin}
value={value}
target={target}
id={index}
/>;
}
}
@ -68,16 +113,16 @@ export const getFunctionArguments = (functionName) => {
}
}
export const getFunctionCalldata = (functionName) => {
export const getFunctionCalldata = (functionName, chainId) => {
switch (functionName) {
case "auditReserves":
return prepareAuditReservesCalldata();
return prepareAuditReservesCalldata(chainId);
case "setAdjustment":
return prepareSetAdjustmentCalldata();
return prepareSetAdjustmentCalldata(chainId);
case "createBond":
return prepareCreateBondCalldata();
return prepareCreateBondCalldata(chainId);
default:
return "";
return null;
}
}
@ -106,10 +151,18 @@ export const BooleanValue = ({ left, text, isSelected, setSelected }) => {
cursor: "pointer",
borderRadius: left ? "12px 0 0 12px" : "0 12px 12px 0",
flex: 1,
border: `1px solid #fff`
border: "2px solid #fff",
borderLeft: left ? "2px solid #fff" : "none",
borderRight: left ? "none" : "2px solid #fff",
background: `${isSelected ? "#fff" : theme.colors.gray[600] }`
}}
>
<Typography color={`${isSelected ? theme.colors.primary[300] : "#fff"}`} align="center">{text}</Typography>
<Typography
color={isSelected ? theme.colors.gray[600] : "#fff"}
align="center"
>
{text}
</Typography>
</Box>
)
}
@ -186,3 +239,39 @@ export const ArgumentInput = ({
</Box>
)
}
export const ParsedCell = (props) => {
const etherscanLink = useMemo(() => {
const client = config.getClient();
let url = client?.chain?.blockExplorers?.default?.url;
if (url) {
url = url + `/address/${props.target}`;
}
return url;
}, [props]);
return (
<TableRow id={props.id + `--proposalFunction`} data-testid={props.id + `--proposalFunction`}>
<TableCell align="center" style={{ padding: "8px 0" }}>
<Typography>{props.label}</Typography>
</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>
<Link href={etherscanLink} target="_blank" rel="noopener noreferrer">
<Typography>{shorten(props.target)}</Typography>
</Link>
</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>
<Typography>{formatCurrency(props.value, 2, props.nativeCoin)}</Typography>
</TableCell>
{props.remove && <TableCell>
<div style={{ display: 'flex', gap: '8px' }}>
<TertiaryButton fullWidth onClick={() => alert("Do we need it????")}>Edit</TertiaryButton>
<TertiaryButton fullWidth onClick={() => props.remove()}>Delete</TertiaryButton>
</div>
</TableCell>}
</TableRow>
)
}

View File

@ -1,29 +1,70 @@
import { useReadContract, useReadContracts } from "wagmi";
import { simulateContract, writeContract, waitForTransactionReceipt } from "@wagmi/core";
import toast from "react-hot-toast";
import { config } from "../../config";
import {
GHOST_GOVERNANCE_ADDRESSES,
} from "../../constants/addresses";
import { abi as GovernorAbi } from "../../abi/Governor.json";
import { abi as GovernorStorageAbi } from "../../abi/GovernorStorage.json";
import { abi as GovernorVotesQuorumFractionAbi } from "../../abi/GovernorVotesQuorumFraction.json";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { getTokenDecimals } from "../helpers";
export const useMinQuorum = (chainId) => {
const numerator = 69n;
const denominator = 100n;
const { data: quorumNumerator, refetch: quorumNumeratorRefetch } = useReadContract({
abi: GovernorVotesQuorumFractionAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "quorumNumerator",
scopeKey: `quorumNumerator-${chainId}`,
chainId: chainId,
});
let percentage = 0;
const { data: quorumDenominator, refetch: quorumDenominatorRefetch } = useReadContract({
abi: GovernorVotesQuorumFractionAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "quorumDenominator",
scopeKey: `quorumDenominator-${chainId}`,
chainId: chainId,
});
if (numerator && denominator && denominator > 0n) {
percentage = Number(100n * numerator / denominator) / 100;
}
const numerator = quorumNumerator ?? 0n;
const denominator = quorumDenominator ?? 1n;
const percentage = Number(100n * numerator / denominator) / 100;
return { numerator, denominator, percentage }
}
export const useProposalThreshold = (chainId, name) => {
const decimals = getTokenDecimals(name);
const threshold = new DecimalBigNumber(420_000_000_000_000_000_000n, decimals);
const { data, refetch } = useReadContract({
abi: GovernorStorageAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "proposalThreshold",
scopeKey: `proposalThreshold-${chainId}`,
chainId: chainId,
});
const threshold = new DecimalBigNumber(data ?? 0n, decimals);
return { threshold };
}
export const useProposalCount = (chainId) => {
const proposalsCount = 1337n;
return { proposalsCount };
const { data, refetch } = useReadContract({
abi: GovernorStorageAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "proposalCount",
scopeKey: `proposalCount-${chainId}`,
chainId: chainId,
});
const proposalCount = data ?? 0n;
return { proposalCount };
}
export const useProposalStatus = (chainId, proposalId) => {
@ -73,11 +114,7 @@ export const useProposalVotingDelay = (chainId, proposalId) => {
export const useProposals = (chainId, depth) => {
const decimals = getTokenDecimals(name);
const { proposalsCount } = useProposalCount(chainId);
let iterator = proposalsCount ? proposalsCount : 0n;
const bigIntDepth = BigInt(depth);
const edgeProposalId = iterator > bigIntDepth ? iterator - bigIntDepth : 0n;
const { proposalCount } = useProposalCount(chainId);
const statuses = [
"Active",
@ -88,17 +125,18 @@ export const useProposals = (chainId, depth) => {
];
let proposals = [];
while (iterator > proposalsCount - bigIntDepth) {
iterator -= 1n;
const start = Number(proposalCount);
const end = Math.max(0, start - depth);
for (let i = start - 1; i >= end; --i) {
const voteEnds = 50n;
const yesVotes = new DecimalBigNumber(1337_000_000_000_000_000_000, decimals);
const noVotes = new DecimalBigNumber(420_000_000_000_000_000_000, decimals);
proposals.push({
id: iterator,
id: i,
discussion: "https://google.com",
status: statuses[Number(iterator) % statuses.length],
status: statuses[i % statuses.length],
voteEnds,
yesVotes,
noVotes