DAO Governance Architecture
This document describes the detailed architecture of the confidential governance system.
Overview
The DAO system implements fully confidential on-chain governance where votes, voting power, and token balances remain encrypted throughout the entire governance lifecycle. Built on Zama's fhEVM, the system enables secure, private decision-making while maintaining verifiable outcomes.
Architecture Diagram

The diagram illustrates the complete governance flow from token deposit through encrypted voting to proposal execution.
System Components
ConfidentialGovernanceToken
ERC7984 token with encrypted balances for governance participation.
Key Features:
- Encrypted balance storage (
euint64) - Confidential transfers
- Operator approval system
- Minting and burning with privacy
Core Functions:
function confidentialTransfer(address to, euint64 amount) external returns (euint64);
function confidentialTransferFrom(address from, address to, euint64 amount) external returns (euint64);
function approveOperator(address operator) external;
function mint(address to, externalEuint64 amount, bytes proof) external;
ConfidentialGovernor
Main governance contract managing proposals and encrypted voting.
State Management:
struct Proposal {
address proposer;
address[] targets;
uint256[] values;
bytes[] calldatas;
uint64 voteStart;
uint64 voteEnd;
bool finalized;
euint64 forVotes; // Encrypted
euint64 againstVotes; // Encrypted
euint64 abstainVotes; // Encrypted
}
mapping(address => euint64) private _balances; // Total deposited
mapping(address => euint64) private _available; // Unlocked power
Key Features:
- Encrypted voting power tracking
- Confidential vote tallying
- Oracle-based outcome decryption
- Timelock integration
ConfidentialTimelock
Time-delayed execution controller for security.
Functionality:
- Mandatory delay before execution
- Batch operation scheduling
- Emergency cancellation
- Role-based access control
Roles:
PROPOSER_ROLE: Can schedule operations (Governor)EXECUTOR_ROLE: Can execute operations (Governor)ADMIN_ROLE: Can manage roles
ConfidentialTreasury
Manages DAO funds with confidential accounting.
Features:
- Encrypted balance tracking
- Multi-token support
- Timelock-only access
- Confidential transfers
Security:
modifier onlyTimelock() {
require(msg.sender == address(timelock), "only timelock");
_;
}
ConfidentialEmissionsController
Manages token distribution and vesting schedules.
Functionality:
- Configurable emission rates
- Encrypted reward distributions
- Time-based vesting
- Multiple recipient support
Complete Governance Flow
Phase 1: Voting Power Acquisition
User Journey:
- Acquire governance tokens
- Approve Governor as operator
- Encrypt deposit amount
- Deposit to Governor contract
Code Flow:
// 1. Approve governor
await governanceToken.approveOperator(governorAddress);
// 2. Encrypt amount
const input = fhevm.createEncryptedInput(governorAddress, userAddress);
input.add64(amount);
const encrypted = await input.encrypt();
// 3. Deposit
await governor.depositVotingPower(encrypted.handles[0], encrypted.inputProof);
Governor Processing:
function depositVotingPower(
externalEuint64 encryptedAmount,
bytes calldata inputProof
) external returns (euint64) {
euint64 amount = FHE.fromExternal(encryptedAmount, inputProof);
euint64 transferred = token.confidentialTransferFrom(msg.sender, address(this), amount);
_balances[msg.sender] = FHE.add(_balances[msg.sender], transferred);
_available[msg.sender] = FHE.add(_available[msg.sender], transferred);
emit VotesDeposited(msg.sender, transferred);
return transferred;
}
Result: User has encrypted voting power in Governor
Phase 2: Proposal Creation
Proposal Structure:
{
targets: [treasuryAddress],
values: [0],
calldatas: [encodedFunctionCall],
description: "Proposal: Fund Development"
}
Governor Processing:
function propose(
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
string memory description
) external returns (uint256 proposalId) {
// 1. Validate arrays
require(targets.length == values.length && targets.length == calldatas.length);
// 2. Generate proposal ID
proposalId = hashProposal(targets, values, calldatas, keccak256(bytes(description)));
// 3. Create proposal
Proposal storage proposal = _proposals[proposalId];
proposal.proposer = msg.sender;
proposal.targets = targets;
proposal.values = values;
proposal.calldatas = calldatas;
proposal.voteStart = uint64(block.timestamp) + votingDelay;
proposal.voteEnd = proposal.voteStart + votingPeriod;
// 4. Initialize encrypted tallies
proposal.forVotes = FHE.asEuint64(0);
proposal.againstVotes = FHE.asEuint64(0);
proposal.abstainVotes = FHE.asEuint64(0);
emit ProposalCreated(proposalId, msg.sender, targets, values, calldatas, description);
}
State: Proposal is Pending (waiting for votingDelay)
Phase 3: Voting Period
Vote Types:
0= Against1= For2= Abstain
User Voting:
// Encrypt vote weight
const input = fhevm.createEncryptedInput(governorAddress, voterAddress);
input.add64(voteWeight);
const encrypted = await input.encrypt();
// Cast vote
await governor.castVote(
proposalId,
1, // VoteType.For
encrypted.handles[0],
encrypted.inputProof
);
Governor Processing:
function castVote(
uint256 proposalId,
VoteType support,
externalEuint64 encryptedAmount,
bytes calldata inputProof
) external returns (ebool) {
Proposal storage proposal = _proposals[proposalId];
// 1. Validate voting period
require(block.timestamp >= proposal.voteStart && block.timestamp < proposal.voteEnd);
// 2. Decrypt vote weight
euint64 weight = FHE.fromExternal(encryptedAmount, inputProof);
// 3. Consume available voting power
(ebool success, euint64 applied) = _tryConsumeAvailable(msg.sender, weight);
// 4. Lock votes for this proposal
_votesUsed[proposalId][msg.sender] = FHE.add(_votesUsed[proposalId][msg.sender], applied);
// 5. Add to proposal tally (encrypted)
if (support == VoteType.For) {
proposal.forVotes = FHE.add(proposal.forVotes, applied);
} else if (support == VoteType.Against) {
proposal.againstVotes = FHE.add(proposal.againstVotes, applied);
} else {
proposal.abstainVotes = FHE.add(proposal.abstainVotes, applied);
}
emit VoteCast(msg.sender, proposalId, support, applied, success);
return success;
}
Privacy: All tallies remain encrypted
Phase 4: Tally Request and Decryption
After Voting Ends:
await governor.requestTally(proposalId);
Governor Processing:
function requestTally(uint256 proposalId) external {
Proposal storage proposal = _proposals[proposalId];
// 1. Validate voting ended
require(block.timestamp >= proposal.voteEnd);
require(!proposal.finalized);
// 2. Calculate encrypted outcome
euint64 totalParticipation = FHE.add(proposal.forVotes, proposal.abstainVotes);
ebool quorumMet = FHE.gte(totalParticipation, FHE.asEuint64(quorumVotes));
ebool forBeatsAgainst = FHE.gt(proposal.forVotes, proposal.againstVotes);
ebool succeeded = FHE.and(quorumMet, forBeatsAgainst);
// 3. Request decryption of only final outcome
bytes32[] memory cts = new bytes32[](2);
cts[0] = FHE.toBytes32(quorumMet);
cts[1] = FHE.toBytes32(succeeded);
uint256 requestId = FHE.requestDecryption(cts, this.finalizeTally.selector);
proposal.pendingRequestId = requestId;
}
Oracle Callback:
function finalizeTally(
uint256 requestId,
bytes memory cleartexts,
bytes memory decryptionProof
) external {
// 1. Verify proof
FHE.checkSignatures(requestId, cleartexts, decryptionProof);
// 2. Decode results
(bool quorumReached, bool succeeded) = abi.decode(cleartexts, (bool, bool));
// 3. Update proposal
proposal.quorumReached = quorumReached;
proposal.succeeded = succeeded;
proposal.finalized = true;
emit ProposalFinalized(proposalId, quorumReached, succeeded);
}
Result: Only final outcome revealed (passed/failed), vote counts remain encrypted
Phase 5: Unlock Voting Power
After Finalization:
await governor.unlockVotes(proposalId);
Governor Processing:
function unlockVotes(uint256 proposalId) external returns (ebool) {
require(_proposals[proposalId].finalized);
euint64 used = _votesUsed[proposalId][msg.sender];
_votesUsed[proposalId][msg.sender] = FHE.asEuint64(0);
_available[msg.sender] = FHE.add(_available[msg.sender], used);
emit VotesUnlocked(proposalId, msg.sender, used);
return FHE.asEbool(true);
}
Result: Voting power available for new proposals
Phase 6: Queue in Timelock
If Proposal Succeeded:
await governor.queue(proposalId);
Governor Processing:
function queue(uint256 proposalId) external {
Proposal storage proposal = _proposals[proposalId];
// 1. Validate succeeded
require(proposal.finalized && proposal.succeeded);
require(!proposal.queued);
// 2. Schedule in timelock
bytes32 salt = bytes32(proposalId);
timelock.scheduleBatch(
proposal.targets,
proposal.values,
proposal.calldatas,
bytes32(0),
salt,
timelock.getMinDelay()
);
// 3. Mark as queued
proposal.queued = true;
proposal.queueTimestamp = block.timestamp;
emit ProposalQueued(proposalId, block.timestamp + timelock.getMinDelay());
}
Result: Proposal scheduled for execution after delay
Phase 7: Execute
After Timelock Delay:
await governor.execute(proposalId);
Governor Processing:
function execute(uint256 proposalId) external payable {
Proposal storage proposal = _proposals[proposalId];
// 1. Validate can execute
require(proposal.queued && !proposal.executed);
require(block.timestamp >= proposal.queueTimestamp + timelock.getMinDelay());
require(block.timestamp <= proposal.queueTimestamp + executionGracePeriod);
// 2. Execute via timelock
bytes32 salt = bytes32(proposalId);
timelock.executeBatch{value: msg.value}(
proposal.targets,
proposal.values,
proposal.calldatas,
bytes32(0),
salt
);
// 3. Mark as executed
proposal.executed = true;
emit ProposalExecuted(proposalId);
}
Result: Proposal executed, actions performed (e.g., treasury transfer)
Privacy Mechanisms
Encrypted Vote Tallying
All vote counts computed homomorphically:
// Adding encrypted votes
proposal.forVotes = FHE.add(proposal.forVotes, encryptedVoteWeight);
// Comparing encrypted tallies
ebool forWins = FHE.gt(proposal.forVotes, proposal.againstVotes);
// Checking quorum
ebool quorumMet = FHE.gte(totalVotes, quorumThreshold);
Privacy: Individual vote weights never revealed
Selective Decryption
Only decrypt final aggregate outcomes:
Decrypted:
- Quorum reached: bool
- Proposal succeeded: bool
Never Decrypted:
- Total for votes count
- Total against votes count
- Individual vote weights
Voting Power Locking
Prevents double-voting across proposals:
// Track usage per proposal
mapping(uint256 proposalId => mapping(address voter => euint64)) private _votesUsed;
// Invariant maintained
_balances[user] = _available[user] + sum(_votesUsed[*][user])
Security: User cannot vote with same power on multiple active proposals
Timelock Security
Time Delays
Purpose: Give community time to react to malicious proposals
Configuration:
minDelay: Minimum wait before execution (e.g., 2 days)executionGracePeriod: Window to execute (e.g., 3 days)
Flow:
Proposal Passes → Queue → Wait minDelay → Execute within gracePeriod
Emergency Cancellation
Admin can cancel malicious proposals:
function cancel(uint256 proposalId) external onlyRole(GOVERNOR_ADMIN_ROLE) {
_proposals[proposalId].canceled = true;
emit ProposalCanceled(proposalId);
}
Governance Parameters
Current Configuration (Sepolia)
{
votingDelay: 300, // 5 minutes
votingPeriod: 600, // 10 minutes
proposalThreshold: 100n, // 100 tokens
quorumVotes: 1000n, // 1000 tokens
executionGracePeriod: 86400, // 24 hours
timelockDelay: 300 // 5 minutes
}
Typical Mainnet Configuration
{
votingDelay: 7200, // 2 hours
votingPeriod: 50400, // 14 days
proposalThreshold: 100000n, // 100K tokens
quorumVotes: 1000000n, // 1M tokens
executionGracePeriod: 259200, // 3 days
timelockDelay: 172800 // 2 days
}
Security Considerations
Access Control
Governor Admin:
- Cancel proposals
- Adjust parameters
- Emergency operations
Timelock Admin:
- Manage roles
- Update delays
Proposers (Governor):
- Schedule operations in timelock
Executors (Governor):
- Execute scheduled operations
Encrypted State
All sensitive values encrypted:
- User balances (
_balances[address]) - Available power (
_available[address]) - Votes used (
_votesUsed[proposalId][address]) - Proposal tallies (
forVotes,againstVotes,abstainVotes)
Reentrancy Protection
All state-changing functions use nonReentrant modifier.
Gas Costs
| Operation | Approximate Gas | Notes |
|---|---|---|
| Token Deposit | ~300K | Transfer + accounting |
| Proposal Creation | ~300K | Store proposal data |
| Vote Casting | ~250K | Update encrypted tallies |
| Request Tally | ~400K | Oracle request |
| Finalize Tally | ~200K | Oracle callback |
| Queue Proposal | ~150K | Timelock scheduling |
| Execute Proposal | Varies | Depends on calls |
Integration Examples
Depositing Voting Power
const input = fhevm.createEncryptedInput(governorAddress, userAddress);
input.add64(ethers.parseUnits("100", 18));
const encrypted = await input.encrypt();
await governor.depositVotingPower(encrypted.handles[0], encrypted.inputProof);
Creating a Proposal
const targets = [treasuryAddress];
const values = [0];
const calldatas = [
treasury.interface.encodeFunctionData("transfer", [
recipientAddress,
encryptedAmount,
proof,
]),
];
const description = "Fund development: 10,000 tokens";
await governor.propose(targets, values, calldatas, description);
Casting a Vote
const input = fhevm.createEncryptedInput(governorAddress, voterAddress);
input.add64(ethers.parseUnits("50", 18));
const encrypted = await input.encrypt();
await governor.castVote(
proposalId,
1, // VoteType.For
encrypted.handles[0],
encrypted.inputProof
);
Complete Lifecycle
// 1. Deposit voting power
await depositVotingPower(amount);
// 2. Create proposal
const proposalId = await createProposal(
targets,
values,
calldatas,
description
);
// 3. Wait for voting to start
await sleep(votingDelay);
// 4. Cast vote
await castVote(proposalId, VoteType.For, voteWeight);
// 5. Wait for voting to end
await sleep(votingPeriod);
// 6. Request tally
await requestTally(proposalId);
// 7. Wait for oracle (automatic)
// 8. If succeeded, queue
await queue(proposalId);
// 9. Wait for timelock delay
await sleep(timelockDelay);
// 10. Execute
await execute(proposalId);
Related Documentation
- DAO System - Complete contract reference
- Privacy Protocol - FHE implementation
- Deployed Addresses - Contract addresses