Overview
Council elections allow token holders to vote for candidates. The top candidates (by vote amount) become council members. Voting requires locking tokens in the Council contract.
Election Flow
Election Starts
An election is initiated (requires EIP-712 signature from the platform). The election has a fixed duration.
Voting Period
Token holders approve their tokens to the Council contract, then call voteElection() to vote for candidates. Tokens are locked until the election ends.
Election Ends
After endTime, anyone can call claimElectionTokens(). The first caller triggers finalization — top candidates become council members.
Claim Tokens
All voters call claimElectionTokens() to retrieve their locked tokens.
Reading Election State
const council = new ethers.Contract(councilAddress, CouncilV1ABI, provider);
const state = await council.getElectionState(tokenAddress);
console.log("Active:", state.active);
console.log("End time:", new Date(Number(state.endTime) * 1000));
console.log("Finalized:", state.finalized);
console.log("Council size:", state.councilSize.toString());
console.log("Epoch:", state.epoch.toString());
Election State Fields
| Field | Type | Description |
|---|
active | bool | Whether an election is currently running |
endTime | uint256 | Election end timestamp (unix seconds) |
lastElectionEnd | uint256 | When the last election ended |
finalized | bool | Whether the current/last election results are finalized |
councilSize | uint256 | Number of council seats |
epoch | uint256 | Election epoch counter |
Voting in an Election
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 for candidates
// You can split your tokens across multiple candidates
const votes = [
{ candidate: "0xCandidate1...", amount: ethers.parseEther("6000") },
{ candidate: "0xCandidate2...", amount: ethers.parseEther("4000") },
];
const tx = await council.voteElection(tokenAddress, votes);
await tx.wait();
Checking Candidates
// Get top 20 candidates sorted by votes (descending)
const [candidates, voteAmounts] = await council.getTop20Candidates(tokenAddress);
for (let i = 0; i < candidates.length; i++) {
if (candidates[i] === ethers.ZeroAddress) break;
console.log(`${candidates[i]}: ${ethers.formatEther(voteAmounts[i])} votes`);
}
// Check a specific candidate's votes
const votes = await council.getCandidateVotes(tokenAddress, candidateAddress);
Claiming Tokens After Election
After the election ends (block.timestamp > endTime):
// First caller triggers finalization + claims
// Subsequent callers just claim their tokens
const tx = await council.claimElectionTokens(tokenAddress);
await tx.wait();
Reading Council Members
// After election is finalized
const members = await council.getCouncilMembers(tokenAddress);
console.log("Council members:", members);
// Check if an address is a council member
const isMember = await council.isCouncilMember(tokenAddress, someAddress);
Events
| Event | Description |
|---|
ElectionStarted(token, endTime, councilSize) | Election begins |
ElectionVoted(token, voter, totalLocked, candidates, amounts) | A vote is cast |
ElectionFinalized(token, councilMembers) | Election results finalized |
ElectionTokensClaimed(token, voter, amount) | Voter claims locked tokens |