Skip to main content

Web application

The web application is the Next.js project in src/. It is the only user interface Certichain has: verifiers, holders, issuers, guardians and v0.2 administrators all use it. It does most of the work in the browser. Hashing, encryption, EIP-712 signing, commitments and zero-knowledge proofs run on the user's device, chain reads go straight to a public RPC endpoint, and transactions go through the user's wallet. The server side of the same project (the API routes under src/app/api/) is described in Server routes.

Two contract generations in one build

The same pages serve both contract generations. readContractVersion() in src/lib/contract-version.ts returns v03 only when NEXT_PUBLIC_CONTRACT_VERSION is exactly v03, and v02 otherwise. Pages with version-specific behaviour pick a v0.3 component or a legacy component from that value at render time. See Contract versions and sets.

Stack​

The versions below are the ones declared in the root package.json as of 2026-10-24.

ConcernPackageVersion
Frameworknext (App Router, next dev --turbopack)16.1.5
UI libraryreact, react-dom^19.0.0
Stylingtailwindcss with @tailwindcss/postcss^4
Component variantsclass-variance-authority, clsx, tailwind-merge, @radix-ui/react-slot^0.7.1, ^2.1.1, ^3.3.1, ^1.2.3
Iconslucide-react^0.522.0
Chain accessethers^6.14.4
Sign-in messagessiwe^3.0.0
Document encryption@hpke/core, @hpke/dhkem-x25519, @noble/curves, @noble/hashes1.9.0, 1.8.0, 1.2.0, 1.3.2
Zero-knowledge access@semaphore-protocol/group, identity, proof; snarkjs4.14.3; 0.7.5
Verifiable credentialsjsonld, canonicalize9.0.0, ^2.1.0
QR codesqrcode.react (draw), jsqr (read)^4.2.0, 1.4.0
Server-onlyjose (session tokens), @upstash/redis (shared store)^6.2.12, ^1.39.0
Unit testsvitest, @testing-library/react, jsdom, fast-check^3.2.4, 16.3.3, 26.1.0, 4.10.2
Browser tests@playwright/test^1.64.0

npm run dev and npm run build first run npm run zk:public (scripts/zk-public.mjs), which copies the hash-checked circuit files the browser prover needs into public/zk/. See Command line tools.

Directory map​

PathWhat it holds
src/app/Routes. Each folder with a page.tsx is a page; src/app/api/ holds the server routes. Page-specific components sit next to their page.
src/app/layout.tsxThe root layout: fonts, metadata and the ServicesProvider.
src/components/layout/The page shell: MainLayout, Header, NetworkBanner, DemoBanner, Footer, navigation.ts, LegalDocument.
src/components/ui/Shared primitives (Button, Notice, Seal, StatusStamp, CheckLine and others). See Design system.
src/components/ (other folders)Feature components shared by several pages: access/, home/, present/, vc/, plus ConfirmDialog, PausedNotice, RecordTypeLabel, RedirectTo, VerificationQr.
src/services/The service layer: wallet, gateways, domain services, indexing, React glue. See Service layer.
src/lib/Framework-free logic: hashing, commitments, EIP-712 types, encryption (doc-crypto/), VC and status list code (vc/), RPC (rpc/), zero-knowledge helpers (zk/), the seal geometry (seal/) and the server modules (server/).
src/contracts/ABIs and deployment manifests: v03/deployment.<chainId>.json, zk/deployment.<chainId>.json, and the v0.2 ABI.
public/manifest.webmanifest, sw.js (the offline shell worker for the phone pages), icons and zk/ circuit files.

How a page reaches the chain​

  1. src/app/layout.tsx is a server component. It loads the three fonts, sets the page metadata and wraps every page in ServicesProvider.
  2. ServicesProvider (src/services/react/services-provider.tsx) is a client component. On first render it calls createBrowserServices() once and keeps the result in state, so every component on the page shares one set of services. Tests pass a services prop with fakes instead.
  3. Components call useServices() and use a domain service, for example services.locator.locate(id) on the verify page. They never build contracts or providers themselves.
  4. Domain services read through gateways. Gateways read over the public RPC endpoints (no wallet needed) and ask the wallet only for signatures and transactions. See Reading the chain and Wallet and network.

Server and client components​

KindPagesWhy
Server component that renders a client component/, /about, /verify, /present, /wallet, /open, /access, /records/add, /records/import, /privacy-policy, /terms-conditionsThe page file stays small, can export metadata (/present, /wallet) or wrap a client component in Suspense (/verify, /records/add, which read query parameters).
Client component ("use client" in page.tsx)/login, /student, /dashboard, /admin, /records, /records/[id], /records/types, /students/inviteThese pages read the wallet session and redirect on role, so they run in the browser.

Every page that touches a wallet, a contract or the browser's storage is a client component, because the services use window.ethereum, window.location and localStorage. The home and about pages render their copy on the server from a per-version table (see Design system).

Page shell​

Every page renders inside MainLayout (src/components/layout/MainLayout.tsx), in this order:

  1. A Skip to content link to #main.
  2. DemoBanner: a role="note" strip with the text "Test network demo. Do not upload real personal documents. Data written to the blockchain is public."
  3. Header: the role workspaces, the area tabs and the wallet chip. See Design system.
  4. NetworkBanner: shown only while a connected wallet is on another chain. See Wallet and network.
  5. <main id="main"> with the page.
  6. Footer.

Browser configuration​

The browser bundle reads only NEXT_PUBLIC_* variables. Next.js inlines them at build time, so changing one needs a rebuild. Names are listed in example.env; values are never shown in these docs.

VariableUsed forWhen missing
NEXT_PUBLIC_NETWORK_IDThe expected chain id: network guard, chain checks on writes, manifest lookupWrites fail with "Blockchain service is not configured"; the network banner is disabled
NEXT_PUBLIC_RPC_URLS, NEXT_PUBLIC_RPC_URLPublic RPC endpoints for wallet-free reads (comma list first, then the single URL)Wallet-free reads fail with an explanation
NEXT_PUBLIC_CONTRACT_VERSIONv03 selects the v0.3 pages; anything else is v0.2v0.2 pages
NEXT_PUBLIC_CONTRACT_ADDRESSThe v0.2 AcademicRecords address (v0.3 addresses come from the manifest)v0.2 reads fail
NEXT_PUBLIC_START_BLOCKFirst block of the v0.2 share history scanv0.2 share lists report that history is not configured
NEXT_PUBLIC_PINATA_GATEWAY_URLFirst IPFS gateway for reading documentsOnly the public gateways are used
NEXT_PUBLIC_STATUS_LIST_BASE_URL, NEXT_PUBLIC_STATUS_LIST_PUBLISHERStatus list entries in exported VCs and their checksVC export carries the chain status only
NEXT_PUBLIC_API_SIGNERThe published verification API signer (read by the server, the monitor and callers)Not used by the pages themselves

The full list, including server-only names, is in Configuration.

Pages in this section​

PageWhat it covers
RoutesEvery route: who sees it, v0.2 and v0.3 behaviour, purpose, main components
Service layerThe composition root, every service and how they depend on each other
Wallet and networkThe wallet service, its errors, the session hook, the network guard and chain switching
Reading the chainRPC failover, log scans, caches, the start block, the credential and share indexes, signature lookup
Contract-set routingHow the app finds the contract set that holds a credential and sends every check there
Design systemTokens, fonts, primitives, the seal, navigation workspaces, version-aware copy, accessibility rules

Source files​

  • src/app/layout.tsx
  • src/components/layout/MainLayout.tsx, src/components/layout/DemoBanner.tsx
  • src/services/react/services-provider.tsx, src/services/react/browser-services.ts
  • src/services/index.ts
  • src/lib/contract-version.ts, src/lib/chain.ts
  • next.config.ts, package.json

Tests​

  • src/components/layout/MainLayout.test.tsx, src/components/layout/DemoBanner.test.tsx
  • src/services/react/services-provider.test.tsx, src/services/index.test.ts
  • src/lib/contract-version.test.ts
  • Browser journeys under e2e/journeys/ exercise the pages end to end; see End-to-end tests.