How holders.vote works

A web3 take on Reddit: communities anchored to ENS names, where voting weight comes from on-chain holdings and every score is reproducible. Here’s the whole machine, end to end.

ENS-anchored communities

Every community is tied to a single ENS name and lives at /h/<name>.eth. The ENS namehash is the canonical key, so there is exactly one community per name. Names are normalized with ENSIP-15 before anything is stored, so visually confusable names can’t collide.

Who can create one? Only an address with authority over the name:

  • the ENS controller of the name (the NameWrapper is handled, so wrapped names work), or
  • any address listed in the name’s hv text record — a lightweight way to delegate creation without transferring the name.

Admin is never database-authoritative. Whenever an admin action happens, authority is re-derived from the chain at that moment — the DB never becomes the source of truth for who’s in charge. Day-to-day moderation is delegated to moderators, who are database rows that admins add and remove.

Signing in with your wallet (SIWE)

There are no passwords. You authenticate with Sign-In with Ethereum:

  1. The server issues a one-time nonce as a stateless, signed (HMAC) cookie.
  2. Your wallet signs a SIWE message, which the server verifies with viem’s verifySiweMessage. Smart-contract wallets are supported via EIP-1271.
  3. On success you get an opaque, server-authoritative session cookie with a 7-day rolling expiry, revoked on logout.

If you switch accounts in your wallet, the UI notices and prompts you to re-authenticate so your session always matches the connected address.

Voting-power sources

When a community is created, the creator picks one source of voting power. The chain and source are permanent for the life of the community. Supported chains are Ethereum Optimism Base, and the source can be:

  • Native balance — the chain’s native coin (ETH).
  • ERC-20 — any token; the app detects its symbol and decimals.
  • ERC-721 — an NFT collection, counted by holdings.

The app also detects whether a token implements IVotes (getPastVotes) — i.e. ERC20Votes / ERC721Votes with on-chain checkpoints. IVotes detection assumes block-number checkpoints; timestamp-clock (ERC-6372) tokens are out of scope for now.

Weight modes

Each post and comment carries scores under three weight modes:

  • Raw — one vote, one count. The familiar Reddit-style tally.
  • Balance — votes weighted by the voter’s token / NFT / native balance at the snapshot block.
  • Voting Power — for IVotes tokens, weighted by delegated voting power (getPastVotes), so delegation counts.

All three are computed and stored at once, so a community can present whichever lens is most meaningful without recounting anything.

The snapshot model

This is the heart of holders.vote. Creating a post records the chain’s current block as that post’s canonical snapshot block. Every vote and comment under that post is evaluated at the same block.

Because the block is pinned when the post is created, acquiring tokens after a post appears can’t swing its score — you can’t buy your way into influence retroactively. Historical holdings are read through an archive RPC and cached forever, keyed by (chain, source, token, address, block). Those reads are immutable, so the cache never needs to expire and feeds are fully reproducible.

Scoring & feeds

Each post and comment stores up/down counts for all three weight modes, plus precomputed hot and wilson ranks. These are updated transactionally on every vote (locking the target row), so feeds just read the column they sort by — four of the five algorithms are pure indexed SQL.

  • Best — Wilson lower-bound confidence; rewards a strong up/down ratio with enough votes to be sure.
  • Hot — Reddit’s Hot formula. It preserves relative order over time, which is exactly why it can be a stored, sortable column.
  • New — most recent first.
  • Top — highest net score in a time window.
  • Rising — the one live computation: recent velocity, to surface posts gaining steam right now.

Threaded comments offer the same set of sorts.

Posts, comments & votes

Anyone signed in can create posts and threaded comments and cast up/down votes. You get one vote per item: voting again the same way removes your vote, and voting the opposite way flips it. All vote math is unit-tested pure logic, and the one-vote-per-item rule is enforced by database constraints, not just app code.

Open, not censored

Filters control visibility, not the right to write. Anyone signed in may post, comment, and vote. Each community can set two filters — has ENS and has balance — each of which can be required forbidden ignored. These only hide content by default; a reader toggle always reveals what’s been hidden, so nothing is truly censored.

Create your community

Anchor it to an ENS name you control and choose your voting-power source.

Create a community