zkgov.app ·
Contract on HashKey Testnet ·
Zero-knowledge governance platform built for the On-Chain Horizon Hackathon (ZKID track). Voters cast anonymous, Groth16-verified votes on governance proposals — your vote is mathematically unlinkable to your identity.
- Register once — Your wallet is checked for a KYC SBT, then your Semaphore identity commitment is added to the on-chain group
- Browse proposals — Created on-chain with title, description, quorum, and voting period
- Vote anonymously — A Groth16 ZK proof is generated locally (3–5s) proving group membership without revealing who you are. A nullifier prevents double-voting per proposal
- Finalize — Anyone can finalize a proposal after the voting period ends, recording the outcome on-chain
Chain: HashKey Chain Testnet (ID: 133) · Contract: 0xEa625841E031758786141c8b13dD1b1137C9776C
packages/
├── contracts/ Solidity — ZKVoting (Semaphore v4 integration)
├── backend/ Fastify API + relayer + server-side ZK proof generation
├── web/ Next.js 15 web UI
├── telegram-bot/ grammY bot — proposals in group chats, "Vote Now" button
├── discord-bot/ discord.js — slash commands, ephemeral voting
├── mcp/ MCP server — AI agents vote via Claude Code, Cursor, etc.
├── cli/ CLI + MCP dual entry point (`zkgov` command)
└── shared/ Types, ABIs, contract addresses
# Install globally — https://www.npmjs.com/package/@zkgov/cli
npm install -g @zkgov/cli
# Or use via npx (MCP server mode) — https://www.npmjs.com/package/@zkgov/mcp
claude mcp add zkgov npx @zkgov/mcp
# Commands
zkgov stats # Platform stats
zkgov proposals # List all proposals
zkgov proposal <id> # Proposal detail
zkgov wallet # Your wallet + balance
zkgov register # Register as voter (one-time)
zkgov vote <id> for|against|abstain # Cast anonymous ZK vote
zkgov finalize <id> # Finalize ended proposal
zkgov activity # Recent on-chain events
# JSON output for scripting/agents
zkgov proposals --jsonpnpm install
# Copy and fill in env vars
cp .env.example .env
# Start everything
pnpm devRequired env vars:
| Variable | Description |
|---|---|
DATABASE_URL |
PostgreSQL connection string |
KEK |
32-byte hex key for encrypting Semaphore identities |
RELAYER_PRIVATE_KEY |
Funded HashKey testnet wallet for gas |
TELEGRAM_BOT_TOKEN |
From @BotFather |
DISCORD_BOT_TOKEN |
From Discord Developer Portal |
Get testnet HSK from the HashKey faucet.
The castVote function on-chain does not check msg.sender — only the ZK proof matters. This means:
- A relayer can submit on your behalf without learning your identity
- The vote is unlinkable to any wallet address
nullifier = hash(identity, proposalId)— unique per voter per proposal, but not reversible to an identity
Identity (private key) ──► Semaphore commitment (public, on-chain in group)
│
Generate Groth16 proof:
"I am a member of this group AND
I vote YES on proposal 3"
(without revealing WHICH member)
│
Relayer submits proof on-chain
│
Semaphore verifies ──► nullifier stored ──► tally updated
| Layer | Choice |
|---|---|
| Chain | HashKey Chain Testnet (ID: 133) |
| ZK | Semaphore v4 (Groth16, snarkjs WASM) |
| Contracts | Solidity 0.8.23 + Hardhat |
| Backend | Fastify + Drizzle ORM + PostgreSQL |
| Frontend | Next.js 15 + shadcn/ui + Reown AppKit |
| Telegram | grammY bot |
| Discord | discord.js |
| AI Integration | MCP server + zkgov skill |
AI agents (Claude Code, Cursor, Windsurf) can participate in governance directly:
# Add ZKGov as an MCP server — https://www.npmjs.com/package/@zkgov/mcp
claude mcp add zkgov npx @zkgov/mcpAgents get their own wallet (auto-generated at ~/.zkgov/config.json), register as voters, and cast anonymous ZK-verified votes — indistinguishable from human votes on-chain.
See packages/mcp/ and .claude/skills/zkgov/ for details.
