ghost-dao-interface/src/hooks/governance/index.js
Uncle Fatso 5dc0bb3a1b
unite onchain tx as one function with gas sanitization
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2026-04-27 21:54:48 +03:00

611 lines
21 KiB
JavaScript

import { useMemo } from "react";
import { useReadContract, useReadContracts } from "wagmi";
import { keccak256, stringToBytes } from 'viem'
import toast from "react-hot-toast";
import {
GHOST_GOVERNANCE_ADDRESSES,
} from "../../constants/addresses";
import { abi as GovernorAbi } from "../../abi/GhostGovernor.json";
import { abi as GovernorCountingAbi } from "../../abi/GovernorGhostCounting.json";
import { abi as GovernorStorageAbi } from "../../abi/GovernorStorage.json";
import { abi as GovernorVotesQuorumFractionAbi } from "../../abi/GovernorVotesQuorumFraction.json";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { getTokenDecimals, getTokenAbi, getTokenAddress, executeOnChainTransaction } from "../helpers";
export const getVoteValue = (forVotes, totalVotes) => {
if (totalVotes == 0n) return 0;
const value = forVotes * 100n / totalVotes;
return Math.floor(Number(value.toString()));
}
export const getVoteTarget = (totalVotes, totalSupply) => {
if (totalSupply == 0n) return 80;
const precision = 10n ** 3n;
const valueRaw = (totalVotes * precision) / totalSupply;
const value = Number(valueRaw) / Number(precision);
const result = (5 - Math.sqrt(1 + 80/9 * (value - 0.1) )) / 4;
return Math.floor(result * 100);
}
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 ? proposalId.toString() : "undefined"}-${who}`,
chainId: chainId,
});
const voteOf = data ? BigInt(data) : 0n;
return { voteOf };
}
export const useProposalHash = (chainId, functions) => {
const { proposalCount } = useProposalCount(chainId);
const proposalDescription = `Proposal #${proposalCount}`;
const descriptionHash = keccak256(stringToBytes(proposalDescription));
const { data: proposalHash, refetch } = useReadContract({
abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "hashProposal",
args: [
functions.map(f => f.target),
functions.map(f => f.value),
functions.map(f => f.calldata),
descriptionHash
],
scopeKey: `hashProposal-${chainId}-${functions.map(f => f.calldata)}`,
chainId: chainId,
});
return { proposalHash, proposalDescription };
}
export const useActiveProposedLock = (chainId) => {
const decimals = getTokenDecimals("GHST");
const { data: activeProposedLock, refetch } = useReadContract({
abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "activeProposedLock",
scopeKey: `activeProposedLock-${chainId}`,
chainId: chainId,
});
const result = new DecimalBigNumber(
activeProposedLock ? activeProposedLock : 0n,
decimals
);
return result;
}
export const useMinQuorum = (chainId) => {
const { data: quorumNumerator, refetch: quorumNumeratorRefetch } = useReadContract({
abi: GovernorVotesQuorumFractionAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "quorumNumerator",
scopeKey: `quorumNumerator-${chainId}`,
chainId: chainId,
});
const { data: quorumDenominator, refetch: quorumDenominatorRefetch } = useReadContract({
abi: GovernorVotesQuorumFractionAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "quorumDenominator",
scopeKey: `quorumDenominator-${chainId}`,
chainId: chainId,
});
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 { proposalCount } = useProposalCount(chainId);
const { data } = useReadContract({
abi: GovernorStorageAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "proposalThreshold",
scopeKey: `proposalThreshold-${chainId}`,
chainId: chainId,
});
const { data: activeProposedLock } = useReadContract({
abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "activeProposedLock",
scopeKey: `activeProposedLock-${chainId}`,
chainId: chainId,
});
const lastIndex = proposalCount === 0n ? 0n : proposalCount - 1n;
const { proposalId } = useProposalDetailsAt(chainId, lastIndex, {
enabled: proposalCount !== 0n
});
const { state } = useProposalState(chainId, proposalId, {
enabled: proposalCount !== 0n && !!proposalId
});
let threshold = new DecimalBigNumber(data ?? 0n, decimals);
if (proposalCount !== 0n && state !== undefined && state < 2) {
threshold = new DecimalBigNumber(activeProposedLock ?? 0n, decimals);
}
return { threshold };
}
export const useProposalCount = (chainId) => {
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 useProposalState = (chainId, proposalId) => {
const { data } = useReadContract({
abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "state",
args: [proposalId],
scopeKey: `state-${chainId}`,
chainId: chainId,
});
const state = data ?? 0;
return { state };
}
export const useProposalProposer = (chainId, proposalId) => {
const { data } = useReadContract({
abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "proposalProposer",
args: [proposalId],
scopeKey: `proposalProposer-${chainId}`,
chainId: chainId,
});
const proposer = data ? data : "0x0000000000000000000000000000000000000000";
return { proposer };
}
export const useProposalLocked = (chainId, proposalId) => {
const decimals = getTokenDecimals(name);
const { data } = useReadContract({
abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "lockedAmounts",
args: [proposalId],
scopeKey: `lockedAmounts-${chainId}`,
chainId: chainId,
});
const locked = new DecimalBigNumber(data ?? 0n, decimals);
return { locked }
}
export const useProposalQuorum = (chainId, proposalId) => {
const decimals = getTokenDecimals(name);
const { snapshot } = useProposalSnapshot(chainId, proposalId);
const { data } = useReadContract({
abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "quorum",
args: [snapshot],
scopeKey: `quorum-${chainId}`,
chainId: chainId,
});
const quorum = new DecimalBigNumber(data ?? 0n, decimals);
return { quorum }
}
export const useProposalVotes = (chainId, proposalId) => {
const decimals = getTokenDecimals(name);
const { data } = useReadContract({
abi: GovernorCountingAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "proposalVotes",
args: [proposalId],
scopeKey: `proposalVotes-${chainId}`,
chainId: chainId,
});
const againstVotes = new DecimalBigNumber(data?.at(0) ?? 0n, decimals);
const forVotes = new DecimalBigNumber(data?.at(1) ?? 0n, decimals);
const totalVotes = new DecimalBigNumber(
(data?.at(0) ?? 0n) + (data?.at(1) ?? 0n),
decimals
);
return { forVotes, againstVotes, totalVotes }
}
export const useProposalSnapshot = (chainId, proposalId) => {
const { data, error } = useReadContract({
abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "proposalSnapshot",
args: [proposalId],
scopeKey: `proposalSnapshot-${chainId}`,
chainId: chainId,
});
const snapshot = data ?? 0n;
return { snapshot };
}
export const useProposalDetailsAt = (chainId, index) => {
const { data, error } = useReadContract({
abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "proposalDetailsAt",
args: [index],
scopeKey: `proposalDetailsAt-${chainId}-${index}`,
chainId: chainId,
});
const proposalId = data?.at(0) ?? 0n;
const proposalDetailsAt = data?.at(1)?.map((target, index) => ({
target,
value: data?.at(2)?.at(index),
calldata: data?.at(3)?.at(index),
}));
return { proposalDetailsAt, proposalId };
}
export const useProposalDetails = (chainId, proposalId) => {
const { data } = useReadContract({
abi: GovernorStorageAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "proposalDetails",
args: [proposalId],
scopeKey: `proposalDetails-${chainId}-${proposalId}`,
chainId: chainId,
});
const proposalDetails = data?.at(0)?.map((target, index) => ({
target,
value: data?.at(1)?.at(index),
calldata: data?.at(2)?.at(index),
}));
return { proposalDetails };
}
export const useProposalDeadline = (chainId, proposalId) => {
const { data, refetch } = useReadContract({
abi: GovernorStorageAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
args: [proposalId],
functionName: "proposalDeadline",
scopeKey: `proposalDeadline-${chainId}`,
chainId: chainId,
});
const deadline = data ?? 0n;
return { deadline };
}
export const useProposalVotingDelay = (chainId) => {
const { data } = useReadContract({
abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "votingDelay",
scopeKey: `votingDelay-${chainId}`,
chainId: chainId,
});
const delay = data ?? 0n;
return { delay };
}
export const useProposals = (chainId, depth, searchedIndexes) => {
const decimals = getTokenDecimals("GHST");
const ghstAbi = getTokenAbi("GHST");
const ghstAddress = getTokenAddress(chainId, "GHST");
const { proposalCount } = useProposalCount(chainId);
const start = Number(proposalCount);
const end = Math.max(0, start - depth);
const indexes = searchedIndexes
? searchedIndexes.map((_, i) => i)
: [...Array(start - end)].map((_, i) => start - 1 - i);
const { data: proposalsDetailsAt } = useReadContracts({
contracts: indexes?.map(index => {
return {
abi: GovernorStorageAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "proposalDetailsAt",
args: [index],
scopeKey: `proposalDetailsAt-${chainId}-${index}`,
chainId: chainId,
}
})
});
const { data: proposalDetails } = useReadContracts({
contracts: searchedIndexes?.map(proposalId => {
return {
abi: GovernorStorageAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "proposalDetails",
args: [proposalId],
scopeKey: `proposalDetails-${chainId}-${proposalId}`,
chainId: chainId,
}
})
});
const { data: proposalDeadlines } = useReadContracts({
contracts: indexes?.map((_, index) => {
const proposalId = searchedIndexes
? searchedIndexes?.at(index)
: 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(index)
: 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(index)
: 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(index)
: 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: proposalSnapshots?.map((proposal, index) => {
const timepoint = proposal?.result;
return {
abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "quorum",
args: [timepoint],
scopeKey: `quorum-${chainId}-${index}`,
chainId: chainId,
}
})
});
const { data: pastTotalSupplies } = useReadContracts({
contracts: proposalSnapshots?.map((proposal, index) => {
const timepoint = proposal?.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(index)
: 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 = useMemo(() => {
return indexes?.map((_, index) => {
let result = { short: index + 1, full: undefined };
const proposalId = searchedIndexes
? searchedIndexes?.at(index)
: proposalsDetailsAt?.at(index)?.result?.at(0);
if (proposalId) {
const hash = "0x" + proposalId.toString(16);
result.short = hash.slice(-5);
result.full = hash;
}
return result;
});
}, [indexes, searchedIndexes, proposalsDetailsAt]);
const voteValues = useMemo(() => {
return indexes?.map((_, idx) => {
const index = indexes.length - idx - 1;
const againstVotes = proposalVotes?.at(index)?.result?.at(0) ?? 0n
const forVotes = proposalVotes?.at(index)?.result?.at(1) ?? 0n;
const totalVotes = againstVotes + forVotes;
return getVoteValue(forVotes, totalVotes);
}) ?? [];
}, [indexes, proposalVotes]);
const voteTargets = useMemo(() => {
return indexes?.map((_, idx) => {
const index = indexes.length - idx - 1;
const againstVotes = proposalVotes?.at(index)?.result?.at(0) ?? 0n
const forVotes = proposalVotes?.at(index)?.result?.at(1) ?? 0n;
const totalSupply = pastTotalSupplies?.at(index)?.result ?? 0n;
const totalVotes = againstVotes + forVotes;
return getVoteTarget(totalVotes, totalSupply);
}) ?? [];
}, [indexes, proposalVotes, pastTotalSupplies]);
const proposalDetailsPrepared = useMemo(() => {
if (!searchedIndexes) return proposalsDetailsAt;
return proposalDetails?.map((obj, index) => {
if (!obj?.result) return obj;
const proposalId = searchedIndexes.at(index);
return {
...obj,
result: [proposalId, ...obj.result],
};
}) ?? [];
}, [searchedIndexes, proposalsDetailsAt, proposalDetails]);
const proposals = indexes?.map((_, index) => ({
hashes: hashes?.at(index) ?? { short: "", full: "" },
proposer: proposalProposer?.at(index)?.result,
details: proposalDetailsPrepared?.at(index)?.result,
deadline: proposalDeadlines?.at(index)?.result ?? 0n,
snapshot: proposalSnapshots?.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),
votes: proposalVotes?.at(index)?.result?.map(vote => new DecimalBigNumber(vote ?? 0n, decimals)),
voteValue: voteValues?.at(index),
voteTarget: voteTargets?.at(index),
}));
return { proposals };
}
export const releaseLocked = async (chainId, account, proposalId) => {
const messages = {
replacedMsg: "Release locked transaction was replaced. Wait for inclusion please.",
successMsg: "Successfully release locked funds from the governor.",
errorMsg: "Release locked funds failed. Check logs for error detalization.",
};
await executeOnChainTransaction({
chainId,
args: [proposalId],
abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "releaseLocked",
account,
messages,
});
}
export const executeProposal = async (chainId, account, proposalId) => {
const messages = {
replacedMsg: "Proposal execution transaction was replaced. Wait for inclusion please.",
successMsg: "Proposal execution was successful, wait for updates.",
errorMsg: "Proposal execution failed. Check logs for error detalization.",
};
await executeOnChainTransaction({
chainId,
args: [proposalId],
abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "execute",
account,
messages,
});
}
export const castVote = async (chainId, account, proposalId, support) => {
const messages = {
replacedMsg: "Cast vote transaction was replaced. Wait for inclusion please.",
successMsg: "Successfully casted a vote, should be applied the proposal.",
errorMsg: "Vote cast failed. Check logs for error detalization.",
};
await executeOnChainTransaction({
chainId,
args: [proposalId, support],
abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "castVote",
account,
messages,
});
}
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);
const messages = {
replacedMsg: "Proposal transaction was replaced. Wait for inclusion please.",
successMsg: "Successfully proposed a set of functions to be executed.",
errorMsg: "Proposal creation failed. Check logs for error detalization.",
};
await executeOnChainTransaction({
chainId,
args: [targets, values, calldatas, description],
abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "propose",
account,
messages,
});
}