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; } }