The Human Zoo

Prove you're human, learn which animal you are, and mint it as an ENS subname. No rerolls.

The Human Zoo

Created At

ETHGlobal Lisbon 2026

Project Description

The Human Zoo answers a question nobody asked: if you are provably a unique human, what animal are you?

Verify with World ID Selfie Check — no Orb needed, so anyone with a phone can use it — and the app derives your species deterministically from the resulting nullifier hash. You might be a Recently Divorced Blobfish, a Legally Distinct Capybara, or an Unreasonably Confident Shoebill. There is no reroll button, and that is the entire point: your animal is cryptographically bound to your personhood rather than to a database row that could be edited, re-rolled, or farmed. Refresh all you like — you are still a pigeon.

Your beast is then minted as an ENS subname under our parent domain, and its whole identity lives in ENS text records: a generated avatar, a bio, its species, rarity and vibe score. The joke is the delivery mechanism for a real mechanic — sybil resistance is what makes "one human, one animal" enforceable at all. A login wall could not do this, because anyone can make a hundred email accounts, but nobody can make a hundred faces.

The zoo is a public gallery of every verified human's animal, read live from ENS. (If you don't get a live mint done: change "is then minted as an ENS subname" to "is then issued as an ENS subname via NameStone's gasless API".)

How it's Made

Next.js 16 on Bun, deployed on Railway.

World ID: we use the current IDKit v4 stack (@worldcoin/idkit + @worldcoin/idkit-server) with the selfieCheckLegacy preset. Verification requires a server-signed rp_context, so we sign with signRequest() in a /api/rp-context route and the RP signing key never reaches the client. Proofs go to POST /api/v4/verify/{rp_id}, and we only ever trust the nullifier that the verify API returns — never one sent by the client, since accepting a client-supplied hash would let anyone claim any animal.

ENS: the interesting architectural decision is that the app has NO DATABASE. Every inhabitant IS an ENS subname, and all state lives in its text records — avatar, description, and custom zoo.* keys for species, rarity and vibe. The gallery is a read of ENS. There is no backend row that can disagree with what the name resolves to. Subnames are issued gaslessly through NameStone, behind a two-function adapter — NameStone shuts down on 2026-08-03, so the migration path to Durin (ENS Labs' L2 subname registry) is already documented and nothing above the adapter would change.

The species engine is a seeded PRNG (xmur3 + sfc32) keyed on the nullifier, deliberately never Math.random(), because the premise collapses if one human can yield two animals. Verified deterministic across runs, with a 2% legendary / 9% rare distribution over 3000 samples.

Privacy: the raw nullifier is never written to a public ENS record. We store a salted, truncated SHA-256 of it (zoo.hid), which is enough to enforce one-animal-per-human but useless for correlating a person across applications.

Animal art is generated with Gemini (gemini-2.5-flash-image) as a fixed set of per-species illustrations — per species rather than per user, so every animal stays reproducible from the nullifier alone. Anything missing falls back to a self-contained SVG data URI, so the app is fully functional with no art at all.

Notable friction worth flagging: the v4 verify API returns nullifier while most docs still say nullifier_hash, and reading the wrong field fails silently — which for an app whose whole security model rests on that value would have let every user claim the same animal. We read both defensively. Full developer + user testing notes are in TESTING.md. (I dropped the Identity Check paragraph from the original draft since it isn't implemented.)

background image mobile

Join the mailing list

Get the latest news and updates