
Nafuda
ENSv2 title names for graded card slabs: tap to verify the slab and its owner; sell by transfer.
Demo
About
At a card show, a graded slab changes hands for cash in seconds, and the buyer has two questions nobody can answer on the spot: is this the slab the grader actually sealed, and is the seller its owner? A cert lookup only proves that the cert number exists. It says nothing about the slab in your hand, and clones copy the label perfectly. Nafuda gives every slab an ENSv2 name that works as its title. When a grader seals a card, it puts a chip inside the slab and issues <cert>.<grader>.nafuda.eth. The name records the chip's public key and resolves to the current owner. At the table, the buyer taps the slab: the chip signs a fresh challenge, and any ENS client checks the signature against the name's slab.chip record and the seller against its addr record. Selling the card is a transfer of the name, with the seller's declared price written into the transfer. ENSv2 Enhanced Access Control is the trust model: - The holder gets exactly one role: transfer. - Each grader's registry is emancipated, so the grader cannot unregister, re-point or claw back an issued title. - Safe transfers only go through when the seller is the title's only role holder. Every title page shows this live. A "who can do what" matrix simulates each party's call against the contracts and shows the contract's answer, and every buyer can run the per-title checks. Several graders share the namespace, each with its own registry and its own record schema. BGS-style subgrades and card categories (Pokémon, Yu-Gi-Oh!, baseball…) are ENS text records. - The collector app: explore, tap-to-verify, provenance flow, market map, signed offers. - The grader console: intake board, issuing, trust panel, live access-control map. - Curvegrid MultiBaas runs as an independent second witness of our index. Everything runs on Sepolia against the ENSv2 Beta. The three graders are simulated: they are modeled after PSA, BGS and CGC, and not affiliated with them. The chip is simulated in the browser. In production it would be an NFC chip, such as an Arx HaLo, sealed inside the slab.
How it's made
Contracts (Solidity, Foundry, 32 tests) - TitleController is each grader's issuing contract and the resolver of <grader>.nafuda.eth. Titles have no resolver of their own. The ENSv2 Universal Resolver walks nafuda.eth → grader registry → cert and finds the grader's resolver by wildcard (ENSIP-10). resolve() computes the addr and text records from registry state, so the holder record can never go stale. - Each grader gets a UserRegistry, deployed through the ENSv2 VerifiableFactory. The controller holds ROLE_REGISTRAR, the holder receives only ROLE_CAN_TRANSFER_ADMIN, and the registry is emancipated. - TitleControllerV2 adds grader-defined attributes (subgrades, card categories) and lists them in an attributes record. - A one-way lock script revokes the remaining subtree powers. It was rehearsed on forks. Everything else is TypeScript, in one npm workspace - A shared, unit-tested core: verification rules, the EAC permission model, EIP-712 intents and per-title integrity checks. - Apps in React 19, Vite and Tailwind, with React Flow for provenance and the access map. viem talks to the chain, through our own EIP-6963 wallet provider (no wagmi). - Title pages read everything from ENS in the browser. The Hono + Postgres server only indexes events for lists and charts, and stores signed off-chain intents (asks, bids, grading submissions). - It all runs in Docker on a Mac mini, behind a Cloudflare Tunnel. Notable hacks - The permissions matrix runs each action as an eth_call from each party's address, and shows the contract's own revert (for example EACUnauthorizedAccountRoles). - The declared price rides in safeTransferFrom's data field, behind a 4-byte tag. - Demo chip keys are derived from the cert, so tapping a slab, and cloning one, can be simulated in the browser. - A market simulator trades between 16 demo collectors. Each has a <name>.nafuda.eth primary name, so the feeds show ENS names by reverse resolution. Curvegrid MultiBaas - We linked the three grader registries in a MultiBaas deployment, and receive every transfer through its HMAC-signed webhook. - The server reconciles both ways, and the result is public. Every event Curvegrid saw must be in our index, and every transfer we indexed inside its window must have been seen by Curvegrid. - Choosing which events to link showed that our indexer ignored TransferBatch, which ENSv2 registries emit when one call moves several names. We fixed it. Testing - Puppeteer e2e suites against the live site, and on Sepolia (submit → grade → issue → offer → transfer). - An index-vs-ENS consistency check. Built solo. The code was written with Claude Code, a coding agent, under the author's direction. The planning docs and an AI-usage log are in the repo.