A wallet-authenticated, single-page web client for the Aqua Protocol identity server. Users connect an Ethereum wallet, authenticate with SIWE (Sign-In with Ethereum), and manage their identity claims, signatures, attestations, and identity cards from a sidebar dashboard.
The entire app is gated behind wallet authentication — there is no public or anonymous access. It is a desktop-first power-user tool (the layout collapses the sidebar at the md breakpoint but is not mobile-optimized).
Once authenticated, the client lets a user:
- View a Dashboard overview of which claim types are claimed and which are missing, plus their identity cards.
- Create and manage identity claims across several verification flows:
- DNS — add a TXT record, then verify the domain.
- ENS — resolve the connected address to an ENS name and create the claim.
- Email / Phone — one-time-code (OTP) flows.
- GitHub — device-code flow.
- Google — OAuth ID-token flow.
- Sign any claim with the connected wallet via a two-phase
prepare/completeMetaMask flow. - View and create attestations on claims.
- Group claims into identity cards (create, list, view, delete).
- Inspect a single claim's full revision/signature/attestation tree and export it.
- Adjust settings: theme toggle, connected-wallet/session info, and server discovery details from
/.well-known/aqua-identity.
Claim types that the backend reports as unconfigured (via the GitHub/Google/Twilio status endpoints) are surfaced as unavailable.
Defined in src/App.tsx:
| Path | Page |
|---|---|
/ |
Redirects to /dashboard |
/dashboard |
Claims + identity-cards overview |
/claims/dns |
DNS claim management |
/claims/ens |
ENS claim management |
/claims/email |
Email claim (OTP) |
/claims/phone |
Phone claim (OTP) |
/claims/github |
GitHub claim (device flow) |
/claims/google |
Google claim (OAuth) |
/claims/:tip |
Single claim detail / tree view |
/identity-cards |
List and create identity cards |
/identity-cards/:tip |
Single identity card detail |
/attestations |
Attestations given / received |
/settings |
Theme, server info, session |
All server communication goes through a thin fetch wrapper in src/api/client.ts, with endpoints organized by domain under src/api/ (auth, claims, signing, attestations, identity-cards, discovery).
- The base URL comes from
VITE_IDENTITY_SERVER_URL(defaults tohttp://localhost:3030). - Every request attaches a
nonceheader carrying the current SIWE session nonce. - A global
401interceptor triggers the re-authentication flow.
Authentication flow: the wallet connects via Reown AppKit, the client fetches a nonce from the identity server, builds an EIP-4361 SIWE message, signs it through the wallet, and posts the signature back to establish a session. The session nonce is persisted in localStorage; the wallet connection is persisted by AppKit. If the session expires, the user re-signs without reconnecting the wallet.
From package.json:
- React 19 + TypeScript + Vite (
@vitejs/plugin-react) - React Router v7 (
react-router) for routing - Reown AppKit (
@reown/appkit,@reown/appkit-adapter-wagmi) for wallet connection and SIWE - wagmi + viem for Ethereum interaction (networks: mainnet, sepolia)
- @tanstack/react-query for async/query state
- Zustand for cross-cutting client state (auth session, theme, lightweight claims cache)
- Tailwind CSS v4 (
@tailwindcss/vite) with shadcn/ui components (radix-ui,class-variance-authority,clsx,tailwind-merge,lucide-react), Geist variable font
Copy .env.example to .env and fill in:
VITE_IDENTITY_SERVER_URL=http://localhost:3030 # base URL of the Aqua identity server
VITE_REOWN_PROJECT_ID=your_project_id_here # project ID from https://cloud.reown.comVITE_REOWN_PROJECT_ID is required for wallet connection to work; get a free one at cloud.reown.com. Without it, AppKit logs an error in dev.
pnpm install # install dependencies (a pnpm-lock.yaml is committed)
pnpm dev # start the Vite dev server (default http://localhost:5173)
pnpm build # type-check (tsc -b) and build for production
pnpm preview # preview the production build
pnpm lint # run ESLint(npm also works — a package-lock.json is present.)
You will need a running Aqua identity server reachable at VITE_IDENTITY_SERVER_URL for the app to function.
src/
├── api/ # fetch client + per-domain endpoint modules + types
├── store/ # Zustand slices: auth, theme, claims
├── components/
│ ├── ui/ # shadcn/ui primitives
│ ├── layout/ # app-shell, sidebar, auth-guard
│ ├── auth/ # connect-screen, sign-prompt
│ └── claims/ # claim-card, sign-button
├── lib/ # appkit setup, formatting/utils
├── pages/ # one component per route
├── App.tsx # router
└── main.tsx # Wagmi + React Query providers, root render
A fuller design document lives at docs/superpowers/specs/2026-04-10-identity-client-design.md.