Developing quadratic voting system
Quadratic voting solves a fundamental problem of token-weighted voting: a whale with 1000x tokens gets 1000x influence. QV sets a different rule: voting power is proportional to the square root of spent resources. 100 voting credits give 10 units of voting power. 900 credits — 30. To match the power of 9 people with 1 credit each, you need to spend 81 credits.
Math: influence = √credits. This makes it economically inefficient for one participant to dominate compared to a broad coalition.
Theoretical foundation
QV was proposed by economists Eric Posner and Glen Weyl in "Radical Markets". The intuition: in standard voting, 1 person = 1 vote, but preference intensity isn't measured. Someone who cares deeply and someone indifferent have equal weight. QV lets you express intensity through spent credits — closer to real economic preferences.
Example: 100 people slightly prefer option A. 10 people strongly want option B. In standard voting, A wins. In QV, if each of the 10 spends 100 credits on B (power = 10 × √100 = 100), they can override a coalition of 100, each spending 1 credit (power = 100 × √1 = 100). With balanced preferences, the more intensely desired outcome wins.
Voting credit systems
Voice Credits model
Standard scheme: each participant gets fixed voice credits budget per voting period. Credits don't carry over and aren't transferred — each period gets fresh allocation.
contract QuadraticVoting {
struct VotingRound {
uint256 startTime;
uint256 endTime;
mapping(uint256 => int256) optionVotes; // optionId => total votes (sqrt-weighted)
}
uint256 public constant CREDITS_PER_ROUND = 100;
// Spent credits of participant in current round
mapping(address => mapping(uint256 => uint256)) public creditsSpent;
function vote(
uint256 roundId,
uint256 optionId,
uint256 credits, // credits to spend on this option
bool support
) external {
VotingRound storage round = rounds[roundId];
require(block.timestamp >= round.startTime && block.timestamp < round.endTime, "Round inactive");
uint256 totalSpent = creditsSpent[msg.sender][roundId] + credits;
require(totalSpent <= CREDITS_PER_ROUND, "Exceeds budget");
// QV: voting power = sqrt(credits)
uint256 votes = sqrt(credits);
creditsSpent[msg.sender][roundId] = totalSpent;
if (support) {
round.optionVotes[optionId] += int256(votes);
} else {
round.optionVotes[optionId] -= int256(votes);
}
emit VoteCast(msg.sender, roundId, optionId, credits, votes, support);
}
// Integer square root (Babylonian method)
function sqrt(uint256 x) internal pure returns (uint256 y) {
if (x == 0) return 0;
uint256 z = (x + 1) / 2;
y = x;
while (z < y) {
y = z;
z = (x / z + z) / 2;
}
}
}
Token-weighted credits
Alternative scheme: credits are proportional to token balance (as in Gitcoin Grants). Holder of 1000 tokens has 1000 credits. But voting power is still √ of spent credits. This gives wealthy participants more credits, but not linear influence.
Comparison: at token-proportional credits with 10,000 vs 100 balance (100x difference) — voting power differs by √100 = 10x, not 100x. Significantly better than standard token-weighted.
Delegated QV
A participant can delegate credits to another. Delegate votes with a batch of credits, but √ is still applied to total credits per option from each original owner.
Important nuance: credit aggregation from delegates must preserve source information. If you just sum delegatee credits into a pool — you lose QV property.
// Correct aggregation: each delegation entry separate
struct Delegation {
address delegator;
uint256 credits;
}
// Delegate voting: applies QV to each delegation separately
function voteAsDelegate(
uint256 roundId,
uint256 optionId,
Delegation[] calldata delegations
) external {
int256 totalVotes = 0;
for (uint i = 0; i < delegations.length; i++) {
require(
delegatedCredits[delegations[i].delegator][msg.sender][roundId]
>= delegations[i].credits,
"Insufficient delegated credits"
);
totalVotes += int256(sqrt(delegations[i].credits));
}
rounds[roundId].optionVotes[optionId] += totalVotes;
}
Sybil resistance: QV's main problem
QV breaks completely without Sybil defense. One participant with 100 credits gets power 10. Hundred participants with 1 credit each get total power 100. By splitting identity into N addresses, an attacker multiplies power by √N.
Proof of Humanity
Registered unique person in Proof of Humanity or Worldcoin gets 1 credit allocation. Not vulnerable to Sybil — can't create thousand real people.
Integration via on-chain verification:
interface IProofOfHumanity {
function isRegistered(address _submissionID) external view returns (bool);
}
contract QVWithPoH {
IProofOfHumanity public poh;
function registerForRound(uint256 roundId) external {
require(poh.isRegistered(msg.sender), "Not registered in PoH");
require(!registeredForRound[roundId][msg.sender], "Already registered");
registeredForRound[roundId][msg.sender] = true;
creditsBalance[roundId][msg.sender] = CREDITS_PER_ROUND;
}
}
Problem with PoH: limited coverage, registration takes time, disputeable. For DAO with global audience — entry barrier.
Worldcoin World ID
More scalable solution. Iris scan → ZK proof of uniqueness. On-chain verification through semaphore protocol without personal data disclosure.
import "@worldcoin/world-id-contracts/src/interfaces/IWorldID.sol";
import { ByteHasher } from "@worldcoin/world-id-contracts/src/helpers/ByteHasher.sol";
contract QVWithWorldID {
using ByteHasher for bytes;
IWorldID internal worldId;
uint256 internal groupId = 1; // Orb-verified
uint256 internal externalNullifierHash;
function registerWithWorldID(
address signal,
uint256 root,
uint256 nullifierHash,
uint256[8] calldata proof
) external {
// Verifies ZK proof of uniqueness
worldId.verifyProof(
root,
groupId,
abi.encodePacked(signal).hashToField(),
nullifierHash,
externalNullifierHash,
proof
);
require(!nullifierUsed[nullifierHash], "Already registered");
nullifierUsed[nullifierHash] = true;
// grant credits
}
}
Stake-based anti-sybil
For DeFi-oriented DAO: require token stake for participation. Creating many Sybil accounts becomes expensive. Combined with QV: stake determines base credit, but voting power is still √ of spent.
Quadratic Funding (QV extension)
Gitcoin uses QF (Quadratic Funding) — a derivative of QV. Funding mechanism for public goods: projects receive matching funds proportional to square of sum of square roots of individual contributions.
Matching = (Σ √contribution_i)²
Example: project A got 100 contributions of $1. Matching ∝ (100 × √1)² = 10,000. Project B got 1 contribution of $100. Matching ∝ (1 × √100)² = 100. With same contribution sum, broad-support project gets 100x more matching.
Gitcoin Grants implemented this on Ethereum (Optimism to reduce gas costs). This is the most well-known production implementation of QV/QF in crypto.
Full system architecture
On-chain components
- QuadraticVoting.sol: core contract with credit logic and voting
- IdentityRegistry.sol: linking addresses to verified identities (PoH/Worldcoin)
- VotingRoundFactory.sol: creating rounds with parameters (duration, options, credit allocation)
Off-chain components
Snapshot integration: most DAOs use Snapshot for off-chain QV voting — free and unlimited participants. Snapshot supports QV strategy.
Results calculator: off-chain service for complex QF calculations, then publish results on-chain.
Frontend: interface where participant sees credit budget, sliders to distribute across options, live preview voting power for each decision.
Parameters and tuning
| Parameter | Recommendation | Rationale |
|---|---|---|
| Base credits | 99-100 | Convenient for √ calculation |
| Round duration | 7-14 days | Time for thoughtful participation |
| Credit carry-over | No | Prevents accumulation and attacks |
| Minimum stake | 0.01-0.1% supply | Basic anti-sybil without high barrier |
| Sybil protection | PoH + stake | Multi-layer defense |
Limitations and honest view
QV doesn't solve all governance problems. Weaknesses:
Collusion: group of participants can coordinate credit distribution to maximize combined influence. Harder than in standard voting, but not impossible. MACI (Minimum Anti-Collusion Infrastructure) solves this via ZK cryptography.
Rational ignorance: most participants won't spend time studying 20 proposals in a round. QV lowers the cost of ignoring, but doesn't eliminate it.
Optimal strategy: mathematically optimal strategy for participants isn't obvious. Can reduce participation among non-technical participants.
QV fits better for limited option sets (priority selection, grant distribution) than for binary yes/no protocol decisions.
Developing full QV system with PoH/Worldcoin integration: 8-12 weeks. Basic on-chain QV without anti-sybil — 3-4 weeks.







