Search projects, hackathons, sponsors and tech

    ← All projects

    fuda

    Membership as an on-chain right — issued in seconds, verified at the door with no server to trust.

    Demo

    About

    🎫 **Membership as an on-chain right.** A venue issues a right, the member saves a standard pass to Apple Wallet, Google Wallet or the browser, and a scanner checks admission through fuda's API against the on-chain record. +Private adds a separate browser flow: unlock with a passkey, discover your rights and sign to enter. The right's on-chain validity can be checked independently of fuda. 🚀 **Try a standard pass — no crypto wallet setup, no member account, no app install** - Claim your ETHOnline 2026 pass: https://app.fuda.sh/@ethonline2026/pass [→](https://app.fuda.sh/@ethonline2026/pass) - The display a venue puts on its counter (scan it with your phone): https://app.fuda.sh/@ethonline2026/home [→](https://app.fuda.sh/@ethonline2026/home) - Slides (PDF): https://s.oboro.xyz/ethonline2026 [→](https://s.oboro.xyz/ethonline2026) - Member web app: https://app.fuda.sh [→](https://app.fuda.sh) · Operator dashboard: https://dash.fuda.sh [→](https://dash.fuda.sh) · Gate scanner: https://gate.fuda.sh [→](https://gate.fuda.sh) - Code: https://github.com/oboroxyz/fuda-sh [→](https://github.com/oboroxyz/fuda-sh) · ENS: https://github.com/oboroxyz/fuda-sh/blob/main/docs/integrations/ens.md [→](https://github.com/oboroxyz/fuda-sh/blob/main/docs/integrations/ens.md) · The Graph: https://github.com/oboroxyz/fuda-sh/blob/main/docs/integrations/thegraph.md [→](https://github.com/oboroxyz/fuda-sh/blob/main/docs/integrations/thegraph.md) 🧩 **The problem.** Memberships live on a plastic card, a dedicated app or a vendor's database, so verification and revocation depend on the issuing system. fuda puts the right on-chain so another verifier can check it. The right should outlive the app. - ⛓️ **Independently verifiable rights.** Each right is an EAS `Entitlement` on Base Sepolia, issued under an on-chain `IssuerDelegation`. The hosted gate calls the API, which reads both by `eth_call` and checks operational state in D1; required chain reads fail closed. Revoke on-chain and the same QR is rejected on the next scan. Anyone can check the on-chain records; reproducing the hosted admission verdict also needs its operational state. - 🔐 **Standard and +Private.** Standard demos use `Bearer` verification: save a pass, present its QR. `Signed` adds holder proof through challenge-response, and +Private extends it with a fresh ERC-5564 stealth holder address per issued right, unlocked by a separate fuda PRF passkey prepared before issuance. Real iPhone captures show discovery of rights with distinct stealth holders, ADMIT for an active right and REJECT / REVOKED for a different, previously revoked one. Private issuance currently uses the admin API; its card-claim UI is not connected. - **Portable attendance records.** Non-private admissions schedule an EAS `Attendance` attestation asynchronously, best-effort; a failed attestation does not undo admission. +Private emits no public Attendance; entry logs stay in D1. - 🏷️ **ENS names the relationship; EAS proves the right.** `ethonline2026.fuda.eth` is an entry the venue claimed and owns in an ENSv2 User Registry on Sepolia — non-transferable, expiring, gas sponsored. The member number printed on the pass, `<member-no>.ethonline2026.fuda.eth`, is a free offchain name answered by a signed CCIP-Read gateway. Both resolve through the hackathon Universal Resolver from a plain viem client, and a manually provisioned +Private member name resolved to two different stealth addresses across two queries. Automated private issuance and naming remain unconnected. - 📊 **The Graph is on the read path, not beside it.** The rights subgraph is what the member app queries for +Private discovery and what the dashboard queries for on-chain status; the API's own announcement cache was deleted when it landed. The app, dashboard, gate and API are deployed at `*.fuda.sh`, with rights on Base Sepolia and ENS on the hackathon Ethereum Sepolia deployment. Built in the event window on a fresh repo.

    How it's made

    All four surfaces are Cloudflare Workers — api, member app, dashboard, gate scanner — on Hono, Drizzle over D1, R2, valibot, viem. EAS on Base Sepolia holds rights and delegations; D1 holds cards, member records, challenges, single-use consumption, entry logs and stamp credits. Any required chain read that fails, fails closed. Code: https://github.com/oboroxyz/fuda-sh/tree/main/apps [→](https://github.com/oboroxyz/fuda-sh/tree/main/apps) · Architecture: https://github.com/oboroxyz/fuda-sh/blob/main/docs/architecture.md [→](https://github.com/oboroxyz/fuda-sh/blob/main/docs/architecture.md) - **Bearer and Signed verification.** Standard Bearer rights use a counterfactual Coinbase Smart Wallet address computed by the factory with fuda's signer as its sole owner, so no member wallet setup or account deployment is needed to receive a pass; ownership transfer to the member is not implemented. Signed entry uses EIP-191 challenge-response through viem's `verifyMessage`, supporting ECDSA, ERC-1271 and ERC-6492. - **+Private: passkey before issuance.** WebAuthn PRF output derives viewing and spending keys and a stealth meta-address on-device (`@noble/curves`, WebCrypto). The issuer derives a fresh stealth address per right, attests the EAS right to it and publishes an ERC-5564 Announcement. The app fetches raw announcements from The Graph, matches locally, recovers the stealth signing key and signs an API challenge; the verdict shows in a modal on the member's device. Addresses differ per right, not per visit: issuer mappings and repeated presentations of the same UID are not hidden. `packages/stealth-address`. - **Wallet passes built from scratch.** Google save links are RS256 JWTs; the Apple `.pkpass` is signed with a detached CMS `SignedData` assembled with pkijs on WebCrypto. **The hacky bit:** signing a `.pkpass` normally means shelling out to OpenSSL, which a Worker can't do, so the whole manifest-hash-and-sign path is pure WebCrypto at the edge. `packages/pass`. - 🏷️ **ENS — one hybrid resolver, two kinds of name.** One `FudaResolver` (ENSIP-10 wildcard) serves the whole tree: an active claimed venue's `addr()` is read from the ENSv2 User Registry on-chain, while member names are answered offchain by a signed CCIP-Read gateway in the api, and the resolver verifies that signature before answering. The venue claims its name with its own transaction, gated by an EIP-712 voucher fuda signs; gas is sponsored through fuda's ERC-7677 paymaster endpoint (Alchemy's verifying paymaster pays). viem's built-in Universal Resolver is overridden to the hackathon one. `packages/ens-contracts`, spec: https://github.com/oboroxyz/fuda-sh/blob/main/docs/specs/ens-naming.md [→](https://github.com/oboroxyz/fuda-sh/blob/main/docs/specs/ens-naming.md) - 📊 **The Graph — composed Substreams.** `fuda_erc5564` extracts raw ERC-5564 `Announcement` events from the canonical singleton Announcer, parameterized by address, with zero fuda policy in it, so any ERC-5564 app can import the same `.spkg` without writing Rust. `erc5564_eas_pipeline` imports it by `.spkg` and composes it with an EAS `Attested`/`Revoked` module into one `fuda_events` stream, captured live from The Graph Market on Base Sepolia. The rights subgraph indexes EAS and the Announcer directly; the product's discovery path does not consume Substreams output. Spec: https://github.com/oboroxyz/fuda-sh/blob/main/docs/specs/substreams.md [→](https://github.com/oboroxyz/fuda-sh/blob/main/docs/specs/substreams.md) - **Raw and unfiltered on purpose.** +Private discovery scans _every_ announcement on-device; filtering per issuer or member inside the indexer would shrink the anonymity set. ADR: https://github.com/oboroxyz/fuda-sh/blob/main/docs/adr/0003-graph-push-query-lanes.md [→](https://github.com/oboroxyz/fuda-sh/blob/main/docs/adr/0003-graph-push-query-lanes.md) - **Another hacky one.** The subgraph's `subgraph.yaml` and schema-UID map are generated from the API's Sepolia configuration in `wrangler.jsonc`, and `graph:prepare` fails loudly while `EAS_SCHEMAS` or `ANNOUNCER_FROM_BLOCK` are unset, so the indexer and the Worker can't drift on which schema UIDs are live. Built spec-first: `docs/specs/` and `docs/adr/` are in the repo and the code comments cite them by section. https://github.com/oboroxyz/fuda-sh/tree/main/docs [→](https://github.com/oboroxyz/fuda-sh/tree/main/docs)