Quadratic Funding (QF) is a democratic mechanism for allocating a matching pool of funds to public goods projects based on the number of contributors, not just the total amount contributed. This amplifies the impact of small, grassroots donations. However, standard QF is vulnerable to collusion and Sybil attacks, where a single entity can create many fake identities to manipulate the results. The Minimal Anti-Collusion Infrastructure (MACI) solves this by using zero-knowledge proofs and cryptographic techniques to ensure that each vote is cast by a unique, verified user, making collusion economically impractical.
Launching a Quadratic Funding Round with MACI
Launching a Quadratic Funding Round with MACI
A step-by-step tutorial for deploying a secure, anti-collusion Quadratic Funding round using the MACI framework.
To launch a QF round with MACI, you must first set up the core components. This involves deploying a suite of smart contracts on your target blockchain (like Ethereum or a Layer 2). The key contracts are the MACI coordinator contract, the Poll factory, and the QF strategy contract. You'll also need to generate a coordinator key pair for managing the round. The process is typically scripted using the @qfi or @maci CLI tools. For example, a basic deployment command might look like: npx qfi deploy --network sepolia --roundDuration 7d. This handles the contract deployment and initial configuration.
Once the contracts are live, you need to configure the round parameters. This includes setting the matching pool size (e.g., 100,000 DAI), the registration and voting deadlines, and the project eligibility criteria. You must also fund the matching pool contract and set up a user sign-up mechanism, often via a Semaphore group or an interep attestation, to prevent Sybil attacks. Projects can then apply to be included in the round by submitting their details to the QF contract, which stores their metadata and receiving address on-chain.
The voting phase begins after registration closes. Users must first sign up to get a MACI identity, which generates a cryptographic key pair. To vote, they submit encrypted commands to the Poll contract, specifying their chosen projects and contribution amounts. A critical feature of MACI is that users can change their votes during the voting period, with only the final vote being counted. All votes are encrypted, so the contents are hidden from everyone, including the coordinator, until the tally phase, preserving privacy and preventing coercion.
After the voting deadline, the coordinator initiates the tally phase. This is where MACI's cryptographic magic happens. The coordinator processes all the encrypted votes off-chain, generates a zero-knowledge proof (zk-SNARK) that the tally was computed correctly without revealing any individual vote, and submits this proof to the MACI contract. The contract verifies the proof and publishes the final results: the calculated matching amount for each project. The funds are then distributed automatically from the matching pool to the project addresses based on the QF formula.
Post-round, you should publish the tally file and circuit proofs for public verification, ensuring complete transparency and trust in the outcome. For developers, the main resources are the MACI documentation and the QF Interface (QFI) repository. Launching a MACI round requires careful planning but provides a uniquely robust and fair funding mechanism for community-driven projects, setting a new standard for on-chain governance.
Prerequisites and Setup
Before launching a MACI-based quadratic funding round, you must configure your development environment and understand the core dependencies. This guide covers the essential tools, accounts, and initial setup steps.
To work with MACI (Minimal Anti-Collusion Infrastructure), you need a foundational understanding of Ethereum development. You should be comfortable with smart contracts in Solidity, using Hardhat or Foundry for local testing, and interacting with the Ethereum blockchain via a provider like Alchemy or Infura. Familiarity with zero-knowledge proofs (ZKPs) and cryptographic primitives like zk-SNARKs is beneficial, as MACI uses them for private voting and result tallying. Ensure you have Node.js (v18 or later) and npm or yarn installed on your system.
The primary software dependency is the MACI monorepo, which contains the core contracts, CLI, and circuits. Clone the repository using git clone https://github.com/privacy-scaling-explorations/maci.git. After cloning, install the dependencies by running npm run bootstrap in the root directory. This process compiles the Circom circuits, which can take 15-30 minutes. You will also need to set up a funded Ethereum wallet (with testnet ETH) for deploying contracts and a coordinator's keypair to manage the voting round.
Key accounts must be generated before deployment. First, create the coordinator's keypair using the MACI CLI: npx ts-node cli/index.ts genMaciKeypair. This outputs a public and private key; secure the private key, as it decrypts votes and generates proofs. Next, fund a separate Ethereum wallet (e.g., via a MetaMask seed phrase) to act as the deployer. You'll need this wallet's private key or mnemonic configured in your .env file for your development framework. For testing, obtain Goerli or Sepolia ETH from a faucet.
Configure your environment variables. Create a .env file in your project root with variables like DEPLOYER_PRIVATE_KEY, RPC_URL (pointing to an Ethereum node), and COORDINATOR_PRIVATE_KEY. The MACI system requires several contracts: MACI.sol, Poll.sol, MessageProcessor.sol, and Tally.sol. You will deploy these, along with a verifier contract for the zk-SNARK proofs and a user-friendly voting contract (often a QFContribution contract) that participants interact with. A local test using Hardhat's network is recommended before proceeding to a testnet.
Finally, understand the workflow. The setup establishes the coordinator (who runs the round), the voting contracts (where users submit encrypted votes), and the tally process (where the coordinator decrypts votes and proves the result). All user contributions are encrypted using the coordinator's public key, ensuring privacy. After the round ends, the coordinator uses their private key to decrypt, tally votes quadratically, and generate a zk-SNARK proof that the tally is correct without revealing individual votes, which is then verified on-chain.
Core Concepts
Understand the foundational components for launching a secure, private, and sybil-resistant quadratic funding round using the MACI framework.
What is Quadratic Funding?
Quadratic Funding (QF) is a democratic mechanism for allocating matching funds to public goods. It amplifies small contributions, making funding more equitable. Key principles include:
- Plural funding: The matching pool is distributed based on the square of the sum of square roots of contributions.
- Optimal matching: This formula maximizes the aggregate utility of contributors' preferences.
- Real example: Gitcoin Grants has distributed over $60M to open-source projects using QF, demonstrating its impact.
The Role of MACI
Minimal Anti-Collusion Infrastructure (MACI) is a set of smart contracts and cryptographic tools that brings collusion resistance and privacy to on-chain voting and funding mechanisms. For QF, MACI prevents sybil attacks and bribery by:
- Using zero-knowledge proofs (zk-SNARKs) to hide individual votes while proving correct tallying.
- Employing a central coordinator to aggregate and process votes off-chain before a final, verifiable on-chain result.
- Ensuring users cannot prove how they voted, making vote-buying impractical.
Core System Components
A MACI-based QF round is built from several interoperating parts. Understanding each is crucial for deployment.
- Poll Smart Contracts: Handle user sign-ups, message submission, and vote processing.
- Coordinator Service: A trusted off-chain server that tallies votes and generates zk-SNARK proofs of correct execution.
- User Client (CLI/UI): Allows participants to submit encrypted votes and manage keys.
- Verification Smart Contracts: On-chain verifiers that check the coordinator's zk-SNARK proofs before finalizing results.
Key Cryptographic Primitives
MACI's security relies on specific cryptographic constructions. Developers must understand these to audit or customize a system.
- Elliptic Curve Cryptography: Used for public/private key pairs (e.g., BabyJubJub curve in Circom).
- zk-SNARKs: Generate succinct proofs that votes were tallied correctly without revealing them. Often implemented with the Groth16 proving scheme.
- Public Key Encryption: Votes are encrypted to the coordinator's key to ensure privacy until tallying.
- Merkle Trees: Efficiently manage the state of registered users and their commitments.
The Funding Round Lifecycle
A complete MACI QF round follows a strict, multi-phase sequence. Critical phases include:
- Setup: Deploy contracts, set the coordinator, and define the matching pool.
- Registration: Users sign up, generating a MACI identity keypair.
- Voting: Contributors send encrypted votes (funding allocations) via signed messages.
- Processing: The coordinator tallies votes off-chain and generates a zk-SNARK proof.
- Tallying: The proof is submitted on-chain; results are published and funds are distributed.
System Architecture Overview
A technical breakdown of the components and data flow required to deploy a secure, on-chain quadratic funding round using MACI.
Launching a quadratic funding (QF) round with Minimal Anti-Collusion Infrastructure (MACI) involves a multi-layered system designed to ensure collusion resistance and sybil resistance. The core architecture consists of three primary layers: the smart contract layer on Ethereum (or an EVM-compatible L2), the client application layer for user interaction, and the coordinator service layer for cryptographic processing. This separation of concerns allows for a trust-minimized process where the coordinator's role is constrained by zero-knowledge proofs, preventing any single party from manipulating the final results.
The process begins with the deployment of several key smart contracts. The QFFactory contract is used to instantiate a new round, which deploys a QFContract (the main round contract), a MACI contract for vote management, and associated verifier contracts for zk-SNARKs. Users (voters and projects) interact with the system through a frontend client, which uses the MACI kit or MACI CLI to generate cryptographic key pairs. A critical component is the user signup: voters must sign up to the MACI contract, which records their public key on-chain, before they can submit votes.
Voting is conducted through encrypted messages. When a user submits a vote via the client, the application creates an EncryptedCommand and posts it as a Message to the MACI smart contract. These messages are encrypted with a shared key between the voter and the coordinator, ensuring vote secrecy during the round. The coordinator cannot decrypt individual votes until the voting period ends. This mechanism prevents bribery and coercion, as neither the coordinator nor anyone observing the chain can link a vote to a voter during the active round.
After the voting period concludes, the coordinator service—an off-chain process—begins the tallying phase. The coordinator downloads all messages and state leaves from the chain, then runs the processMessages and tallyVotes procedures locally. This generates new state roots and the tally result, but most importantly, it produces a zk-SNARK proof (a ProcessMessagesProof and a TallyVotesProof). These proofs cryptographically verify that the coordinator performed the computations correctly according to MACI's rules, without revealing any individual vote.
The final step is on-chain verification and fund distribution. The coordinator submits the final state root, the tally result, and the zk-SNARK proofs to the Verifier and MACI contracts. The contracts verify the proofs; if valid, the results are finalized. The QFContract then uses the verified tally to calculate the quadratic funding match for each project. Funds are distributed from the matching pool to project owners based on the QF formula (sum of square roots of contributions)^2, effectively allocating capital to projects with the broadest community support.
Key operational considerations include managing the signup gatekeeper (which can restrict participation), setting the coordinator's public key, and ensuring the coordinator service is reliably operational during the tally phase. For developers, tools like the clrfund suite or MACI's node.js package abstract much of this complexity. The entire architecture ensures that even if the coordinator is malicious, they cannot alter votes without producing an invalid proof, which would be rejected by the verifier contract, making the system cryptographically secure.
Step 1: Deploy Smart Contracts
This step establishes the on-chain infrastructure for your Quadratic Funding round, deploying the core contracts that will manage contributions, voting, and fund distribution.
Begin by setting up your development environment. You'll need Node.js (v18+), Git, and a package manager like Yarn or npm. Clone the MACI repository from GitHub (privacy-scaling-explorations/maci) and install its dependencies. This repository contains the core smart contracts written in Solidity and the necessary CLI tools. You will also need to configure a .env file with your Ethereum RPC URL (e.g., from Alchemy or Infura) and a deployer private key for the network of your choice, such as Sepolia or Optimism.
The deployment process uses a series of scripts. First, run the setup command to compile the contracts and generate the required zk-SNARK circuit files and proving keys. This is a computationally intensive step that may take several minutes. Next, execute the deployment script. This will deploy several key contracts to your chosen network: the MACI contract itself, which coordinates the entire process; the PollFactory for creating voting rounds; the MessageProcessor for tallying votes; and the QFI (Quadratic Funding Infrastructure) contracts, which include the RoundFactory and ProjectRegistry.
After deployment, the script will output the addresses of all deployed contracts. Securely record these addresses, as they are essential for the next steps. Key addresses to note are the MACI coordinator contract, the RoundFactory, and the VkRegistry (which holds verification keys). You will pass these addresses as parameters when initializing the CLI tool and creating your funding round. Verify the contracts on a block explorer like Etherscan to provide transparency to your community.
This step creates the immutable, trustless backbone of your funding round. The smart contracts ensure that the rules of Quadratic Funding—where the impact of a contribution matters more than its size—are enforced programmatically. All subsequent actions, from project registration to final fund distribution, will be mediated through these contracts, guaranteeing fairness and auditability without requiring a centralized authority to manage funds or votes.
Step 2: Set Up the Coordinator
The Coordinator is the trusted entity that tallies votes and determines the final results of your Quadratic Funding round. This step involves generating cryptographic keys and deploying the core MACI smart contracts.
The Coordinator is the central administrative role in a MACI round. It is responsible for two critical, sequential tasks: processing encrypted votes during the voting period and performing the trusted setup ceremony to compute the final results. While the Coordinator cannot alter individual votes due to the use of zero-knowledge proofs, it must be trusted to perform the tallying process correctly and to keep its private key secure. This role is typically managed by the round's organizer or a designated service provider.
Your first action is to generate the Coordinator's cryptographic keypair. This is done using the maci-cli tool. Run the command npx maci-cli genMaciKeypair to create a new key. The output will provide a public key and a private key. The public key is used to configure the MACI smart contract so it can accept votes encrypted for this specific Coordinator. The private key must be kept absolutely secure, as it is required to decrypt votes and generate the zk-SNARK proof for the final tally.
Next, you will deploy the core MACI smart contracts to your chosen network. The primary contract is the MACI.sol itself, which manages the voting process. You will also need to deploy a Verifier contract (e.g., Verifier_10_2_8.sol) that validates the zk-SNARK proofs, and a SignUpGatekeeper contract that controls voter eligibility. Common gatekeepers include a simple free-signup contract or one that checks a user's holdings of a specific NFT or token. Deployment is typically handled via a script, such as deploy.ts in the MACI repository, which requires your Coordinator's public key and the addresses of the other helper contracts.
After deployment, you must initialize the MACI contract by funding the MessageAq (Message Accumulator Queue) with enough ETH to cover the state tree depth and message tree depth you configured. This prepayment covers the gas costs for the Coordinator to process votes. Finally, publish the contract addresses and, crucially, the Coordinator's public key to your round's frontend application. Voters will use this public key to encrypt their votes, ensuring only this Coordinator can decrypt them during the tally phase.
Step 3: User Voting Process
This step details how contributors can cast votes to allocate matching funds to projects within a MACI-powered Quadratic Funding round.
The user voting process is the core mechanism for distributing the matching pool. Contributors interact with the deployed MACI and QFI (Quadratic Funding Interface) smart contracts to submit their preferences. To vote, a user must first sign up with MACI, which generates a cryptographic identity key pair. This process involves calling signUp on the MACI contract, paying a small sign-up deposit, and receiving a state index and an identity commitment. The sign-up deposit is refundable upon key change or after the voting period ends, disincentivizing Sybil attacks.
Once signed up, a user can submit their vote allocation. This is done by creating a command and a Message. The command contains the encrypted vote data: the user's state index, their vote for each project (a non-negative integer representing the square root of the allocated voice credits), and a new public key for the next message. This command is then encrypted using a shared key derived from the user's private key and the coordinator's public key, ensuring vote secrecy. The encrypted command is packaged into a Message and submitted to the MACI contract via the publishMessage function.
A critical feature of MACI is the ability for users to change their votes. Since votes are encrypted on-chain, no observer can see a user's choices. To change a vote, a user simply submits a new message with the same state index but updated vote allocations. Only the latest valid message from each user before the voting deadline is processed. This allows for fluid decision-making while maintaining coercion-resistance, as a user can always override a vote made under pressure.
The QFI contract manages the project list and vote interpretation. When a user allocates voice credits, they are effectively distributing a budget. The quadratic formula is applied later during tallying: the funding received by a project is proportional to the square of the sum of the square roots of contributions. For example, if Alice allocates 4 credits and Bob allocates 9 credits to Project X, the summed square roots are 2 + 3 = 5. The quadratic match is then 5² = 25 voice credits' worth of matching funds.
To submit a vote in practice, a user typically interacts with a frontend client library like maci-cli or a custom UI. The process involves: 1) Generating an Ethereum signature to prove ownership of the sign-up identity, 2) Creating the encrypted command and message off-chain, and 3) Sending the transaction to the MACI contract. Developers can find example scripts for voting in the MACI repository.
After the voting period ends, the voting deadline passes, and no more messages are accepted. The process then moves to the next phase: the coordinator downloading all messages, decrypting them, and generating a zk-SNARK proof of correct tallying. The secrecy of individual votes is preserved throughout, as only the final, aggregated results and a validity proof are published on-chain.
Step 4: Tallying Votes and Generating Proofs
After the voting period ends, the round coordinator must process all encrypted votes to calculate the final results and generate cryptographic proofs of the tally's integrity.
The tallying process is the core privacy-preserving mechanism of MACI. The coordinator uses their private key to decrypt all submitted messages (votes and key changes) from the Poll contract. This decryption reveals the votes but, crucially, only to the coordinator who cannot link them to individual voters due to the zero-knowledge proofs submitted during voting. The process involves sequentially processing messages to reconstruct the final state of the state tree, which holds each user's public key and vote weight, ensuring only their last valid vote is counted.
With the decrypted data, the coordinator runs the tallying circuit. This zk-SNARK circuit takes the processed votes and the project funding distribution as inputs. Its job is to compute the quadratic funding results according to the CLR formula, while simultaneously generating a zk-SNARK proof that the computation was performed correctly. This proof, once published on-chain, allows anyone to verify that the results match the valid votes without revealing any individual voter's choices. The key output files are the tally.json (the results) and the proof.json (the cryptographic proof).
To execute the tally, use the maci-cli command: maci-cli tally. You must provide the Poll contract address, the coordinator's private key, and the tally.json file from the previous proof generation step. The CLI interacts with the Poll contract to submit the final tally and the proof. Successful execution will emit a TallyPublished event. At this point, the results are immutably recorded on-chain, and the round moves to its final stage where funds can be distributed.
Verification is critical for trustlessness. Anyone can independently verify the tally proof using the maci-cli verify command, supplying the proof.json and the public signals. This checks the zk-SNARK proof against the verifying key stored in the Poll contract. A successful verification confirms that the published results are a correct computation of all valid, private votes. This step ensures the round's outcome is cryptographically guaranteed, fulfilling MACI's promise of a verifiably fair and private voting process.
Common issues during tallying include insfficient gas for the transaction, as generating and submitting proofs is computationally expensive, and mismatched contract states if the coordinator uses an incorrect tally.json file. Always ensure you are using the same coordinator key that was set during contract deployment. The entire process, from decryption to proof generation, can take several minutes to hours depending on the number of votes, as the zk-SNARK proving time scales with circuit size.
MACI QF vs. Traditional On-Chain Voting
Key technical and security differences between a MACI-based Quadratic Funding round and a standard on-chain voting mechanism.
| Feature / Metric | Traditional On-Chain Voting | MACI Quadratic Funding |
|---|---|---|
Vote Secrecy (Privacy) | ||
Collusion Resistance | ||
Sybil Attack Resistance | Low (1 token = 1 vote) | High (1 person = 1 voice via ZK proofs) |
Vote Buying/Coercion Risk | High (votes are public) | Low (votes are encrypted) |
On-Chain Gas Cost per Vote | ~$5-20 (Simple transfer) | ~$0.50-2 (ZK proof submission) |
Final Tally Computation | On-chain, transparent | Off-chain, with on-chain ZK proof of correctness |
Data Availability | All votes on-chain | Only encrypted votes and final ZK proof on-chain |
Implementation Complexity | Low (standard smart contract) | High (requires coordinator, ZK circuits, key management) |
Common Issues and Troubleshooting
Addressing frequent technical hurdles and developer questions when deploying and managing a MACI-based quadratic funding round.
This error typically occurs when there is a mismatch between the zk-SNARK verification keys generated for your circuit and the ones your contract expects. The Verifier contract is highly specific to the circuit parameters.
Common causes and fixes:
- Mismatched circuit parameters: Ensure the
processMessagesandtallyVotes.zkeyand.wasmfiles you generated match thestateTreeDepth,messageTreeDepth,voteOptionTreeDepth, andcoordinatorPubKeyused in your deployment script. - Incorrect file paths: Double-check that your deployment script (e.g., using
hardhatorfoundry) is correctly reading the verification key JSON files from thezkBuild/directory. - Regenerate artifacts: If you changed any circuit constants, you must re-run the entire setup:
npx hardhat run scripts/buildProcessMessages.tsandnpx hardhat run scripts/buildTallyVotes.tsto create new.zkeyfiles, then run the key generation scripts.
Always verify the circuit and verificationKey parameters in your deploy.ts script match the newly generated files.
Resources and Further Reading
Key technical resources and reference material for designing, deploying, and operating a Quadratic Funding round using MACI. These links focus on protocol mechanics, operational setup, and real-world implementations.
Frequently Asked Questions
Common technical questions and solutions for developers launching and managing MACI-based Quadratic Funding rounds.
A standard Quadratic Funding round calculates matching funds based on the square root of contributions, but it operates on-chain with fully transparent votes. This allows for vote buying and coercion. A MACI (Minimal Anti-Collusion Infrastructure) round uses zero-knowledge proofs to add a privacy layer. Votes and contributions are encrypted, and a trusted coordinator uses a private key to tally them off-chain, producing a zk-SNARK proof of the correct result. This prevents anyone from linking a contributor to their vote, eliminating financial coercion. The final merkle root and proof are published on-chain for verification. Use MACI when voter privacy is critical; use standard QF for maximum simplicity and transparency.