QuadWorldVote

Mini-app built on World Chain that looks into quadratic voting

QuadWorldVote

Created At

ETHGlobal New Delhi

Project Description

QuadWorldVote is a quadratic voting mini-app built on World Chain, designed to let communities, organizations, or classrooms run contests and elections in a fairer and more transparent way. Instead of “1 person = 1 vote” or “whoever has the most money = more power,” this system uses quadratic voting (QV). In QV:

Each voter gets a limited number of credits. Casting more votes on the same option costs quadratically more credits (e.g., 1 vote costs 1 credit, 2 votes cost 4, 3 votes cost 9…). This prevents one participant from dominating, while still letting people show how strongly they feel about an option.

The app uses World ID (proof of personhood) to make sure that:

  1. Every participant is a unique human, not a bot or duplicate account.
  2. But their personal identity remains private — only “uniqueness” is proven.
  3. The app also integrates wallet auth + smart contracts to ensure votes, contest creation, and results are tamper-proof, transparent, and verifiable on-chain.

How it's Made

High-level architecture

QuadWorldVote is a World Chain mini-app that enforces Quadratic Voting (QV) with Proof of Personhood (PoP) via World ID. The stack is split into three planes:

Identity & Session Plane

World ID (via MiniKit Verify) establishes that a signer is a unique human (Sybil resistance) while keeping the user pseudonymous.

Wallet session via WalletConnect / SIWE (MiniKit Wallet Auth) binds actions to an EOAs/public key without exposing identity.

Consensus & Settlement Plane (on-chain)

Solidity smart contracts on World Chain implement the QV budget accounting, contest lifecycle, and canonical tally.

All state transitions that matter (create contest, cast vote, finalize) are settled on-chain to keep results tamper-evident and globally verifiable.

Interaction & Coordination Plane (off-chain)

Next.js front end orchestrates MiniKit commands, renders forms, and performs light validation.

Optional API routes provide a relayer/aggregator for gas-sponsored or batched submissions, while preserving correctness by requiring EIP-712 signatures that the contract verifies.

This separation lets us preserve privacy (PoP ≠ identity), keep integrity (on-chain settlement), and deliver good UX (off-chain aggregation and notifications).

Technologies used (and why)

World Chain MiniKit

Verify → consume World ID proof (nullifier/merkle root) per contest to prevent duplicate votes.

Send Transaction → write to contracts (create/finalize contests, cast votes).

Sign Typed Data (EIP-712) → users sign a structured Vote payload; contracts verify and apply QV math.

Notifications / Get Permissions → nudge users when contests open/close; alert winners.

Share → deep links for viral distribution.

World ID

Provides Proof of Personhood: we enforce “one unique human per contest” by scoping the nullifier to a contestId domain. This gives re-usability across multiple contests without correlating identities.

Solidity + Hardhat + Ethers

Contracts compiled/tested with Hardhat; deployments via network config pointing to World Chain RPC.

OpenZeppelin libraries for access control, safe math patterns, and event ergonomics.

Next.js (App Router) + TypeScript + TailwindCSS

Type-safe client with minimal state management (React hooks).

Tailwind for consistent, shippable UI without CSS sprawl.

(Optional) Gasless Relayer / Batching

API route that accepts EIP-712 signed votes, verifies signature server-side, then submits to the chain in batches to reduce gas and UX friction. If disabled, clients send transactions directly.

How the pieces fit

  1. Identity & anti-Sybil flow (World ID)

User clicks Verify in the mini-app.

MiniKit returns { merkle_root, nullifier_hash, proof }.

We domain-separate the nullifier with the contest to avoid cross-contest linkage: scopedNullifier = keccak256(nullifier_hash, contestId).

Contract checks the World ID proof (through the World ID contract) and marks scopedNullifier as consumed.

Outcome: each unique human can participate once per contest; no PII ever touches the chain.

  1. Quadratic voting semantics

Each voter receives a budget of B credits.

Allocating v votes to a project costs v² credits.

We implement incremental costs to stay gas-efficient. If a user had v votes and wants v', the delta budget is: Δ = v'² − v².

The contract tracks per-voter cumulative spend and reverts if spent + Δ > B.

Because everything is additive and monotone, final tally is deterministic and transparent.

  1. Why we use EIP-712

The app asks users to Sign Typed Data for votes:

// Pseudocode for the typed structure struct Vote { uint256 contestId; uint256 projectId; uint32 votes; // capped; gas-friendly uint256 nonce; // per voter per contest uint256 deadline; // replay/time bound } // Domain: { name: "QuadraticVote", version: "1", chainId, verifyingContract }

Benefits:

Replay protection (nonce + deadline).

Chain & contract binding via domain separator.

Supports gasless flow: users sign, relayer submits.

  1. Contract layout (lean & auditable)

ContestFactory

createContest(params) emits ContestCreated(contestId, …); optional prize pool escrow.

Contest (or a mapping in a single contract)

verifyWorldIdAndMark(nullifierScoped) (internal)

castVote(EIP712 signature, Vote payload)

finalize() → freezes state, computes winners, emits Finalized

Optional: claimPrize() for project owners.

QVMath library

Small, pure math helpers to compute deltas and guard overflows, using uint32/uint64 to keep SSTORE cheap.

  1. Data & events

We log high-signal events (VoteCast, BudgetUpdated, Finalized) that front-ends can stream for real-time leaderboards.

Off-chain indexing (The Graph or simple RPC polling) powers analytics without bloating contract storage.

Partner tech impact

World Chain + World ID:

Gave us trustable anti-Sybil without dragging identity onto chain.

Nullifiers made it trivial to scope uniqueness per contest while preserving anonymity.

MiniKit’s commands let us wire Verify/Auth/Tx/TypedData quickly—less plumbing, more product.

WalletConnect:

Broad wallet coverage out of the box, predictable SIWE flows, and smoother mobile UX.

OpenZeppelin:

Reduced foot-guns on access control and upgrade-safe patterns; saved audit time.

Security & privacy posture

Domain-separated nullifiers: nullifier || contestId prevents linkage across contests.

EIP-712 with chainId & verifyingContract pins signatures to this deployment (no cross-chain replay).

Constant-time checks and checks-effects-interactions ordering; ReentrancyGuard where external calls exist (e.g., prize payout).

Budget invariants enforced on-chain; fuzzed with randomized vote sequences.

Minimal PII: the app never stores identity; only PoP artifacts and public keys touch the chain.

Notable “hacky” bits that were worth it

Scoped nullifier pattern: instead of one global burn per user, we H(K, contestId) so a person can vote in many contests exactly once each—no coordination with external registries.

Incremental QV cost (v'² − v²) avoids re-summing allocations and keeps gas low.

Meta-tx relay: for hackathon demos we allow a server to batch castVote calls using users’ EIP-712 signatures. Users experience “gasless” voting; the contract still verifies everything.

Typed-data “deadline”: prevents signed votes from being replayed after the contest ends (cheap and effective).

UI quirks: Tailwind Forms normalization can override input text color. We forced it via Tailwind !text-black and @layer base—silly but it saved time on UX polish.

Datetime color on WebKit: ::-webkit-datetime-edit override to keep inputs readable in dark UIs.

Dev & deployment workflow Hardhat for builds, scripts, and network config (World Chain RPC via env). .env driven accounts (accounts: [PRIVATE_KEY]) and separate deployer for safety. Scripted deployments produce addresses + ABIs that the Next.js app consumes at build time. Test matrix covers: budget exhaustion, vote updates (increase/decrease), finalize idempotency, nullifier reuse, and signature replay. Static checks with slither/solhint and gas snapshots with Hardhat’s gas reporter. What made World Chain the right substrate?

The project needs strong Sybil resistance without compromising user privacy. World Chain’s Proof of Personhood semantics map exactly to our requirement: “Is this a unique human?”—not “Who is this human?” That single primitive, combined with familiar EVM semantics, made it possible to build a fair, privacy-preserving voting system with a clean mental model and minimal custom cryptography.

background image mobile

Join the mailing list

Get the latest news and updates