Overview
After a council is elected, council members can create proposals. All token holders can vote on proposals by locking their tokens. After the voting period ends, results are finalized and winning options are determined.
Governance Rounds
Governance operates in rounds with two phases:
Reading Governance State
const council = new ethers.Contract(councilAddress, CouncilV1ABI, provider);
const state = await council.getCouncilState(tokenAddress);
console.log("Phase:", state.governancePhase); // 0=Idle, 1=Proposal, 2=Voting
console.log("Proposal phase ends:", new Date(Number(state.proposalPhaseEnd) * 1000));
console.log("Voting phase ends:", new Date(Number(state.votingPhaseEnd) * 1000));
console.log("Current proposal ID:", state.currentProposalId.toString());
console.log("Treasury:", ethers.formatEther(state.treasuryBalance));
Voting on a Proposal
Before voting, you must approve the Council contract to transfer your tokens.
const tokenContract = new ethers.Contract(tokenAddress, [
"function approve(address spender, uint256 amount) returns (bool)",
], signer);
const council = new ethers.Contract(councilAddress, CouncilV1ABI, signer);
// 1. Approve tokens
const amount = ethers.parseEther("10000");
await (await tokenContract.approve(councilAddress, amount)).wait();
// 2. Vote on proposal
// target: address(0) for general proposals
// optionIndex: index of the option you're voting for
// amount: tokens to allocate to this option
const votes = [
{
target: ethers.ZeroAddress,
optionIndex: 0, // first option
amount: ethers.parseEther("10000"),
},
];
const proposalId = 1; // on-chain proposal ID
const tx = await council.voteProposal(tokenAddress, proposalId, votes);
await tx.wait();
You can split your tokens across multiple options in a single vote transaction.
Reading Proposal Details
// Get proposal info
const proposal = await council.getProposal(tokenAddress, proposalId);
console.log("Start:", new Date(Number(proposal.startTime) * 1000));
console.log("End:", new Date(Number(proposal.endTime) * 1000));
console.log("Finalized:", proposal.finalized);
console.log("Type:", proposal.proposalType); // 0=General, 1=Tax
console.log("Options:", proposal.optionCodes);
console.log("Winning option:", proposal.winningOption.toString());
// Get vote counts per option
const optionVotes = await council.getProposalOptionVotes(tokenAddress, proposalId);
optionVotes.forEach((votes, i) => {
console.log(`Option ${i}: ${ethers.formatEther(votes)} votes`);
});
// Get your own votes
const myVotes = await council.getVoterOptionVotes(tokenAddress, proposalId, myAddress);
// Check locked amount (claimable after proposal ends)
const locked = await council.getVoterLockedAmount(tokenAddress, proposalId, myAddress);
Claiming Tokens After Voting
After the voting period ends:
// First caller triggers finalization + claims
// Subsequent callers just claim their tokens
const tx = await council.claimProposalTokens(tokenAddress, proposalId);
await tx.wait();
Tax Configuration
Tax proposals set the token’s transaction tax. After a tax proposal is finalized and the winning option is applied:
const taxConfig = await council.getTaxConfig(tokenAddress);
console.log("Rate:", taxConfig.rate.toString()); // in basis points
console.log("Purpose:", taxConfig.purpose); // 0=None, 1=Burn, 2=Transfer, 3=Treasury
console.log("Target:", taxConfig.target);
// Check if an address is tax exempt
const exempt = await council.isTaxExempt(tokenAddress, someAddress);
| Purpose | Value | Description |
|---|
| None | 0 | No tax |
| Burn | 1 | Tax tokens are burned |
| Transfer | 2 | Tax tokens sent to target address |
| CouncilTreasury | 3 | Tax tokens sent to council treasury |
Events
| Event | Description |
|---|
ProposalCreated(token, proposalId, nonce, proposer, proposalType, optionCodes, endTime) | Proposal created |
ProposalFinalized(token, proposalId, winningOption, winningVotes) | Proposal finalized |