Professional-grade DAO voting system with Clarity 4 support
A secure, transparent, and feature-rich decentralized autonomous organization (DAO) voting system built on the Stacks blockchain using Clarity 4, the latest version of the Clarity smart contract language.
- Weighted Voting System: Assign different voting weights to different voters based on their roles or stake
- Commit-Reveal Scheme: Anonymous voting using cryptographic commitments to prevent vote manipulation
- Time-Based Voting: Proposals with configurable deadlines and automatic expiration
- Vote Delegation: Allow voters to delegate their voting power to trusted representatives
- Proposal Categories: Organize proposals by type (Governance, Treasury, Technical)
- Quorum Requirements: Configurable minimum vote thresholds for proposal passage
This project leverages the following Clarity 4 features:
stacks-block-time- Captures timestamps for proposal creation and vote revelation, enabling precise time-based logic- Enhanced Type System - Improved error handling and data structures
- Native Epoch 4.0 Support - Built specifically for Clarity version 4
- β Proposal lifecycle management (create, delete, extend, execute)
- β Batch voter operations for efficient management
- β Comprehensive statistics and status reporting
- β Vote commitment and revelation tracking
- β Delegation system with revocation
- β Owner-controlled voting periods
- β Minimum quorum enforcement
- Clarinet v2.0+
- Node.js v18+
- npm or yarn
-
Clone the repository
git clone <repository-url> cd SecureBallotsDAO
-
Install dependencies
npm install
-
Verify Clarinet installation
clarinet --version
The project includes a comprehensive test suite covering all contract functionality.
Run all tests:
npm testRun tests with coverage and cost analysis:
npm run test:reportRun tests in watch mode:
npm run test:watchSecureBallotsDAO/
βββ contracts/
β βββ SecureBallotsDAO.clar # Main smart contract
βββ tests/
β βββ SecureBallotsDAO.test.ts # Comprehensive test suite
βββ settings/
β βββ Devnet.toml # Devnet configuration
βββ Clarinet.toml # Clarinet project configuration
βββ package.json # Node.js dependencies
βββ vitest.config.js # Testing configuration
βββ tsconfig.json # TypeScript configuration
Returns proposal details for a given ID.
Returns: (optional {...})
Returns the voting weight of a specific voter.
Returns: uint
Returns the current status of a proposal ("active", "passed", "failed", or "executed").
Returns: (response string-ascii uint)
Returns comprehensive voting statistics including vote count, quorum, and percentage.
Returns: (response {...} uint)
Checks if a voter has already voted on a proposal.
Returns: bool
Checks if an address is a registered voter.
Returns: bool
Returns the delegate for a given voter, if any.
Returns: (optional principal)
create-proposal
(create-proposal
(title (string-ascii 256))
(description (string-ascii 1024))
(category uint)
(blocks uint)
(quorum uint))Creates a new proposal. Only callable by contract owner.
Parameters:
title: Proposal title (max 256 characters)description: Detailed description (max 1024 characters)category: 1=Governance, 2=Treasury, 3=Technicalblocks: Number of blocks until expirationquorum: Minimum votes required for passage
delete-proposal (proposal-id uint)
Deletes an unexecuted proposal. Only callable by owner.
extend-proposal-deadline (proposal-id uint, additional-blocks uint)
Extends a proposal's voting period. Only callable by owner.
execute-proposal (proposal-id uint)
Marks a passed proposal as executed. Only callable by owner.
add-voter (voter principal)
Adds a single voter to the registry. Only callable by owner.
batch-add-voters (voters (list 50 principal))
Adds multiple voters at once. Only callable by owner.
remove-voter (voter principal)
Removes a voter from the registry. Only callable by owner.
set-voter-weight (voter principal, weight uint)
Sets the voting weight for a voter. Only callable by owner.
commit-vote (proposal-id uint, vote-hash (buff 20))
Commits a vote using a cryptographic hash. Two-phase voting for privacy.
reveal-vote (proposal-id uint, nonce (buff 32))
Reveals a committed vote with the original nonce. Verifies commitment and records vote.
delegate-vote (delegate principal)
Delegates voting power to another registered voter.
revoke-delegation ()
Revokes an existing vote delegation.
close-voting ()
Temporarily closes all voting. Only callable by owner.
open-voting ()
Reopens voting after closing. Only callable by owner.
set-minimum-quorum (quorum uint)
Sets the global minimum quorum. Only callable by owner.
- Owner-Only Functions: Critical functions restricted to contract owner
- Voter Registry: Only pre-approved addresses can vote
- Commit-Reveal Scheme: Prevents vote manipulation and front-running
- Duplicate Vote Prevention: Each voter can only vote once per proposal
- Expiration Checks: Automatic proposal expiration based on block height
- Input Validation: Comprehensive validation of all inputs
// Add multiple voters at once
const voters = [addr1, addr2, addr3];
await contract.callPublic("batch-add-voters", [Cl.list(voters)]);
// Set voting weight for important stakeholders
await contract.callPublic("set-voter-weight", [addr1, Cl.uint(5)]);await contract.callPublic("create-proposal", [
Cl.stringAscii("Increase Treasury Budget"),
Cl.stringAscii("Proposal to increase treasury allocation by 20%"),
Cl.uint(2), // CATEGORY_TREASURY
Cl.uint(1440), // ~10 days
Cl.uint(100), // Minimum 100 votes needed
]);// Step 1: Commit vote
const nonce = generateRandomBytes(32);
const proposalId = 1;
const commitment = hash160(concat(nonce, proposalId));
await contract.callPublic("commit-vote", [
Cl.uint(proposalId),
Cl.buffer(commitment),
]);
// Step 2: Reveal vote (after commit phase)
await contract.callPublic("reveal-vote", [
Cl.uint(proposalId),
Cl.buffer(nonce),
]);const status = await contract.callReadOnly("get-proposal-status", [
Cl.uint(1),
]);
const stats = await contract.callReadOnly("get-vote-statistics", [
Cl.uint(1),
]);The codebase follows Clarity best practices:
- Descriptive function and variable names
- Comprehensive error handling
- Clear comments and documentation
- Secure-by-default design patterns
- Update the contract in
contracts/SecureBallotsDAO.clar - Add corresponding tests in
tests/SecureBallotsDAO.test.ts - Run the test suite to verify
- Update documentation
| Code | Constant | Description |
|---|---|---|
| u100 | ERR_NOT_AUTHORIZED | Caller not authorized for this action |
| u101 | ERR_ALREADY_VOTED | Voter has already voted on this proposal |
| u102 | ERR_INVALID_PROPOSAL | Proposal ID does not exist |
| u103 | ERR_VOTING_CLOSED | Voting is currently closed |
| u104 | ERR_INVALID_WEIGHT | Invalid voter weight (must be > 0) |
| u105 | ERR_INVALID_COMMITMENT | Vote commitment not found or invalid |
| u106 | ERR_INVALID_INPUT | Invalid input parameters |
| u107 | ERR_INVALID_VOTER | Voter not registered |
| u108 | ERR_PROPOSAL_EXPIRED | Proposal voting period has ended |
| u109 | ERR_PROPOSAL_NOT_FOUND | Proposal does not exist |
| u110 | ERR_QUORUM_NOT_MET | Proposal did not meet quorum |
| u111 | ERR_INVALID_DELEGATION | Invalid delegation parameters |
| u112 | ERR_PROPOSAL_NOT_EXPIRED | Proposal still active |
This project has been upgraded from Clarity 3 to Clarity 4. Key changes:
- Migrated from
@hirosystems/clarinet-sdkto@stacks/clarinet-sdkv3.10.0 - Using
@stacks/transactionsv6.12.0 - Updated
vitest.config.jsto import fromvitest/config
- Set
clarity_version = 4andepoch = 4.0inClarinet.toml - Implemented
stacks-block-timefor timestamp tracking - Enhanced error handling with new error constants
ISC
Contributions are welcome! Please follow these steps:
- Fork the repository
- Create a feature branch
- Make your changes with tests
- Run the test suite
- Submit a pull request
For issues and questions:
- Open an issue on GitHub
- Review the Clarity documentation
- Check Stacks Discord
- Built with Clarinet
- Powered by Stacks Blockchain
- Clarity 4 features from SIP-033 and SIP-034