ghost-dao-interface/src/hooks/governance/index.js
Uncle Fatso 843093915f
fix and unify voteTarget and voteValue for linear progress bar
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2026-03-04 13:35:28 +03:00

631 lines
21 KiB
JavaScript

import { useReadContract, useReadContracts } from "wagmi";
import { simulateContract, writeContract, waitForTransactionReceipt } from "@wagmi/core";
import toast from "react-hot-toast";
import { keccak256, stringToBytes } from 'viem'
import { config } from "../../config";
import {
GHOST_GOVERNANCE_ADDRESSES,
} from "../../constants/addresses";
import { abi as GovernorAbi } from "../../abi/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 } 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?.toString()}-${who}`,
chainId: chainId,
});
const voteOf = data ? BigInt(data) : 0n;
return { voteOf };
}
export const useProposalHash = (chainId, functions) => {
const { proposalCount } = useProposalCount(chainId);
const proposalDescription = `Proposal #${proposalCount}`;
const descriptionHash = keccak256(stringToBytes(proposalDescription));
const { data: proposalHash, refetch } = useReadContract({
abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "hashProposal",
args: [
functions.map(f => f.target),
functions.map(f => f.value),
functions.map(f => f.calldata),
descriptionHash
],
scopeKey: `hashProposal-${chainId}-${functions.map(f => f.calldata)}`,
chainId: chainId,
});
return { proposalHash, proposalDescription };
}
export const useActiveProposedLock = (chainId) => {
const decimals = getTokenDecimals("GHST");
const { data: activeProposedLock, refetch } = useReadContract({
abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "activeProposedLock",
scopeKey: `activeProposedLock-${chainId}`,
chainId: chainId,
});
const result = new DecimalBigNumber(
activeProposedLock ? activeProposedLock : 0n,
decimals
);
return result;
}
export const useMinQuorum = (chainId) => {
const { data: quorumNumerator, refetch: quorumNumeratorRefetch } = useReadContract({
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(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(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 = 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;
});
const voteValues = 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);
});
const voteTargets = 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);
});
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,
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) => {
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;
}
}