Skip to content

Latest commit

 

History

History

README.md

Web

React frontend for Dotizen. Single-page app using HashRouter (works from any static host) with WebAuthn passkey sign-in and client-side Groth16 ZK proof generation.

Stack

  • React 18 + Vite 6 + TypeScript (strict)
  • Tailwind CSS with a Material Design 3 token palette
  • Polkadot-API (PAPI) for chain interaction
  • Zustand for state management
  • @polkadot-labs/hdkd + @scure/bip39 for sr25519 derivation and mnemonics
  • WebAuthn + PRF extension for passkey-based sign-in (no browser extension needed)
  • WASM ZK module at src/lib/zk/ for Groth16 proof generation

See spec/web.md for the full design.

Pages

Route Purpose
/signin Sign in / register / recover (three tabs); dev-mode quick login as Alice/Bob/Charlie
/ Dashboard: onboarding status, active proposals, voting summary
/proposals List with filter (all / active / passed / failed)
/proposal/:id Description, image, tally, vote buttons, comments
/proposals/new Create proposal: title, description, image, scope, funding source, deadline
/membership Committee view: pending applications, members, elections, Q&A, key rotation
/my-home Home info, linked devices, pairing QR, voting-key registration
/pair, /pair/:seed/:label/:payload Process pairing payload from a scanned QR
/statements Browse on-chain binary statements (text, images, PDFs, WASM)

All routes except /signin and /pair* redirect to /signin if no currentAccount is in the store.

Local development

Frontend only (chain must already be running):

cd web
npm install
npm run dev

Or from the repo root, scripted (sets the right VITE_LOCAL_WS_URL for the active local stack):

./scripts/start-frontend.sh

Full stack in one command:

./scripts/start-all.sh

Environment

cp .env.example .env.local
Variable When to set Notes
VITE_LOCAL_WS_URL Local dev with custom port offset The local-stack scripts export this automatically
VITE_WS_URL Hosted builds Build-time default for the WebSocket endpoint
VITE_PINATA_API_KEY Hosting proposal images / rich descriptions Without it, the proposal form falls back to text-only
VITE_PINATA_SECRET_KEY As above Pair with VITE_PINATA_API_KEY

PAPI descriptors

Generated descriptors live in .papi/ and are committed so a fresh checkout builds without a running chain. Regenerate against a running chain after pallet changes:

npm run update-types   # fetch fresh metadata
npm run codegen        # regenerate descriptors

The repo fails fast if papi generate errors, which makes descriptor drift easier to spot.

Common scripts

npm run dev         # Vite dev server (HTTPS on 5173 by default)
npm run build       # production build to dist/
npm run lint        # ESLint
npm run fmt         # Prettier
npm run fmt:check   # Prettier check-only

Authentication

Three-tab flow on /signin:

  • Register — creates a passkey via WebAuthn, generates a 12-word BIP39 mnemonic for the identity key, asks the user to confirm the first/last words, then submits the unsigned Membership.apply extrinsic with PII encrypted via X25519 + HKDF + AES-GCM.
  • Sign in — WebAuthn get() with the device-PRF salt re-derives the symmetric key and decrypts the device seed.
  • Recover — restores from the BIP39 mnemonic; dev-account mnemonics short-circuit straight into the corresponding dev session.

Keys held client-side: device seed (sr25519, per device, PRF-encrypted), home secret (per home, distributed via pairing QR), identity key (origin device only, PRF-encrypted). See src/lib/auth/passkey.ts and the Authentication section of spec/web.md for details.

ZK voting

Groth16 proofs are generated entirely in-browser via the WASM module in src/lib/zk/. The proving key blob lives at public/zk-proving-key.bin (large; cached after first use). cast_vote is unsigned — the proof is the authorisation, with a small client-side proof-of-work (blakejs) replacing gas. See spec/voting.md for the protocol.