Privacy-preserving ranked-choice voting powered by Fully Homomorphic Encryption (FHE) on Fhenix / Arbitrum Sepolia. Vote totals accumulate homomorphically under encryption.




Privacy-preserving ranked-choice voting powered by Fully Homomorphic Encryption (FHE) on Fhenix / Arbitrum Sepolia. Vote totals accumulate homomorphically under encryption the plaintext is only revealed after the poll closes via the Threshold Network.
Deployed contract (Arbitrum Sepolia): 0xE663beAE94fc6eF11f8C37c866D439272dEE7e6c
Step | Actor | Action |
|---|---|---|
1 | Creator |
|
2 | Voter | Connects wallet / OAuth → Verifier checks requirements off-chain |
3 | Verifier | Returns EIP-712 attestation to voter |
4 | Voter |
|
5 | Creator |
|
6 | Voter |
|
7 | On-chain | Poll closes when |
8 | Verifier |
|
9 | Verifier |
|
10 | Threshold Network | Returns |
11 | Verifier |
|
12 | Voter | Reads |
13 | Member |
|
Communities
Create a community with name, description, logo, and credential type
Define membership requirements with ANDOR group logic mix token balance, NFT ownership, social follows, Discord roles, GitHub accounts, and more
Community metadata pinned to IPFS via Pinata (required for persistence)
Only the community creator can create polls
Credentials
Verifier checks requirements off-chain, returns an EIP-712 signed attestation
Voter's wallet submits attestation to issueCredential() on-chain
Credentials have an on-chain expiry block
Credentials Hub shows per-community eligibility and lets users claim or renew
Voting
Flat polls ranked-choice ballot, voters rank up to 32 options
Hierarchical polls options have sub-options (up to 4 levels deep, 8 per parent); option tree stored on-chain with parentId and childCount; sub-category rollup tallies computed on-chain via rolledUpTallies
Simple polls single-choice (pick exactly one option); radio button UI, castSimpleVote() encrypts [0,...,votingPower,...,0]
Each ranking maps to an encrypted weight submitted to castVote() the contract adds it homomorphically to the running FHE tally
Double-vote prevention via on-chain mapping
Surveys
Anonymous multi-question surveys each question has 2–10 answer options
Responses are FHE-encrypted as euint32(0/1) per answer slot individual responses are never revealed
createSurvey() registers questions on-chain with answerCounts[] and labelHashes[]
castSurveyVote() accumulates encrypted answers into _surveyTallies[pollId][questionId][answerId]
requestSurveyReveal() + publishSurveyResult() same Threshold Network pattern as polls
Results page shows per-question bar charts with counts and percentages
Tally & Results
After poll closes, requestTallyReveal() calls FHE.allowPublic per option (no FHE.decrypt new pattern)
Tally runner calls decryptForTx off-chain, then publishTallyResult with Threshold Network signature
Results page reads revealedTallies directly from the contract
Hierarchical polls show nested tree results with rolledUpTallies for parent nodes
Automated tally runner checks every 60 seconds; manual trigger via POST /admin/tally/:pollId
Community Posts & Articles
Credentialed members can publish articles (title + markdown body + cover image)
Markdown editor with toolbar (bold, italic, headings, code, links, lists)
Article cards with preview text, thumbnail, author, and reading time
Full article view with rendered markdown
Content stored on IPFS; keccak256(CID) stored on-chain via createPost()
Gated communities require a valid credential to post
Social Sharing
Share buttons on poll/survey creation success (Twitter/X, Telegram, Copy Link)
Share icon on every poll card - one-click copy link
Mobile native share via Web Share API
Poll Time Indicators
Live countdown timer on poll cards 2d 14h 32m
Progress bar showing time elapsed vs total duration (green → amber → red)
Pulsing red dot for polls closing within 24 hours
"Closed Xm ago" with relative time instead of just "Closed"
"Results ready" badge when tally is complete
Voting power decay
Period 1: 100% → Period 2: 50% → Period 3: 25% → Period 4: 12.5% → Period 5: 6.25% → deactivated
CountedVotes (CV) = EligibleVotes (EV) × VotingPower% (VP)
Function | Caller | Description |
|---|---|---|
| Community creator | Register community on-chain |
| Community creator | Create flat ranked-choice poll (2-32 options) |
| Community creator | Create poll with on-chain option tree |
| Community creator | Create single-choice poll |
| Community creator | Create multi-question anonymous survey |
| Voter | Submit EIP-712 attestation to get credential |
| Voter | Submit FHE-encrypted per-option weights (ranked) |
| Voter | Submit FHE-encrypted single choice |
| Voter | Submit FHE-encrypted answers (flat 0/1 array) |
| Anyone (after poll ends) |
|
| Tally runner | Verify Threshold Network sig + write plaintext |
| Anyone (after survey ends) |
|
| Tally runner | Verify sig + write survey answer count |
| Credentialed member | Post content hash on-chain |
Vote totals accumulate homomorphically under FHE, plaintext never exposed until poll ends
msg.sender (voter address) is public privacy is about what you voted, not that you voted
publishTallyResult verifies the Threshold Network's signature results cannot be forged
Survey responses are FHE-encrypted individual answers are never revealed, only aggregate counts
FHE.allowPublic (not FHE.decrypt) is the correct pattern per Fhenix SDK v0.5+
Creator-only guardrail: only community creator can create polls/surveys (enforced on-chain + UI warning)