VoiGovernance is an Algorand smart contract for a small, admin-controlled governance system. It supports a fixed set of proposal slots, a whitelist of eligible voters, and compact on-chain vote tracking using box storage. The contract is implemented in Algorand TypeScript (PuyaTS) and compiled to AVM bytecode.
The contract allows an admin to:
- manage the governance admin address
- define the voter whitelist
- initialize a proposal in one of 10 available slots
- resolve active proposals or expired proposals
- terminate a proposal with a custom reason
Eligible wallets can:
- cast a vote only if they are on the whitelist
- vote once per proposal slot
- vote only while the proposal is active and before expiry
The contract enforces a simple voting model:
- Up to 10 slots are available (
maxSlots = 10) - Each slot stores a single proposal payload in a box
- A whitelist is stored in a dedicated box and can be updated by the admin
- Each proposal is encoded as a compact byte payload containing:
- status byte
- proposalId
- yea count
- nay count
- abstain count
- expiration timestamp
- vote mask
| Key | Type | Purpose |
|---|---|---|
admin |
address |
Address that is allowed to manage whitelist, proposals, and resolutions |
maxSlots |
uint64 |
Maximum number of proposal slots, currently 10 |
| Box | Key | Value | Purpose |
|---|---|---|---|
slots |
slot_<uint64> |
bytes | Active or cleared proposal data for a given slot |
whitelistBox |
whitelist |
address[] |
List of wallet addresses eligible to vote |
Each proposal slot behaves like a state machine:
-
initializeProposal(slot, proposalId, duration)- verifies the caller is the admin
- ensures the slot index is valid
- checks the slot is not already active
- stores a new proposal payload with status byte
0x01(active) - sets expiration = latest timestamp + duration
-
castVote(slot, voteType)- only whitelisted wallet addresses can vote
- vote type must be one of:
1= Yea2= Nay3= Abstain
- double-voting is prevented using a bitmask keyed by whitelist index
- proposal must still be active and not expired
-
resolveProposal(slot, resolution)- admin-only resolution of an active proposal
- resolution must be
1(approve) or2(reject) - then the slot is finalized and cleared
-
resolveExpiredProposal(slot, resolution)- allows the admin to resolve a proposal after the deadline
- the proposal is finalized with expired status (
E)
-
terminateProposal(slot, reason)- admin-only emergency termination
- stores a reason string and marks the slot as terminated (
T)
All finalization paths emit a log message for off-chain indexing and then clear the slot box.
Changes the admin address. Only the current admin can call this.
Replaces the whitelist with a new list of authorized voter addresses. The list length must not exceed the configured maxSlots value.
Initializes a new proposal in the given slot. The slot must not already be active.
Allows a whitelisted wallet to cast a vote in the active proposal for that slot.
Admin resolves a proposal based on a final decision. Accepts 1 for approve and 2 for reject.
Allows expiration-based resolution after the voting window closes. The contract does not validate the same resolution values here, but the admin path is still restricted by the call pattern and the active-slot conditions.
Admin can terminate a proposal and record a human-readable cause.
The proposal data stored in each slot is a compact byte sequence. The payload is built in this order:
- status byte
proposalId(8 bytes, big-endian/integer representation)yea(8 bytes)nay(8 bytes)abstain(8 bytes)expirationtimestamp (8 bytes)voteMask(8 bytes)
The contract uses voteMask to enforce one vote per whitelist wallet. Each wallet occupies one bit based on its index in the whitelist array.
- Only the
admincan update governance configuration, initialize proposals, and resolve/terminate them. - Voter eligibility is controlled through the whitelist box, not by arbitrary addresses.
- Double-voting is rejected by checking the vote bit before updating the mask.
- Proposal expiry is checked against
Global.latestTimestampbefore vote acceptance. - Finalization clears the proposal slot to avoid stale active state.
This contract is designed to be compiled with Algorand TypeScript/Puya and deployed to an Algorand network using standard app deployment tooling. After deployment:
- call
updateWhitelistwith the eligible voting wallets - call
initializeProposalwith a slot, proposal identifier, and duration - allow the whitelist to
castVote - resolve the proposal via
resolveProposalorresolveExpiredProposal
// admin updates whitelist
app.updateWhitelist([accountA, accountB, accountC])
// admin starts a proposal in slot 0 for 10,000 rounds
app.initializeProposal(0, 42, 10000)
// whitelisted voter casts a Yea vote
app.castVote(0, 1)
// admin resolves it after voting ends
app.resolveProposal(0, 1)This contract is intentionally minimal and governance-focused rather than general-purpose DAO infrastructure. It is best suited for controlled governance workflows where a trusted admin manages proposal lifecycle and a fixed whitelist of voters participates.