Architecture
Certichain is a browser application in front of a small set of smart contracts, with a thin server beside it. This section explains how those parts are arranged, what each one is trusted with, and why. The other sections go deeper into each part: Smart contracts, Web application and Server and APIs.
| Page | What it covers |
|---|---|
| Containers | Every runtime part (browser app, workers, server routes, contracts, wallets, RPC, IPFS, Redis), what it does, what it stores and how they talk. |
| Trust boundaries and data | Where trust changes hands, which data crosses each boundary, and what each party has to trust. |
| Repository map | Where everything lives in the repository today. |
| Contract versions and sets | v0.2 against v0.3, versioned immutable contract sets, the manifest and routing between sets. |
| Key decisions | The decisions that shaped the architecture, with links to the decision log. |
Goals and constraints
The architecture follows from a short list of requirements.
| Requirement | Consequence in the design |
|---|---|
| Anyone can verify, without an account, a wallet or trust in the operator. | Facts that a verifier relies on live in public contracts. The verify page and the offline script read the chain directly. |
| No personal data on a public ledger. | The chain holds commitments and hashes only. Names, student ids and documents stay in the holder's package and in encrypted containers. |
| An issuer's approval must be portable. | Every credential carries the issuer's EIP-712 signature, so a package can be checked away from the website. |
| Nobody, including the operator, can quietly rewrite history. | Anchors have no edit function. Status changes are events. Admin power sits behind a Safe, and a timelock is deployed for it. |
| Keep the operator's server small and replaceable. | No application database. Lists are rebuilt from contract events in the browser. The server checks sign-in, pins encrypted files and, when configured, signs verification answers. |
| Change contracts without stranding issued credentials. | Contracts are immutable. A change is a new set deployed next to the old ones, and every verification surface reads all listed sets. |
| Testnet first. | Everything runs on Sepolia with synthetic data until an external audit. |
Layers
- Pages compose services and never talk to a provider or a contract themselves (they use ethers only for input helpers such as address validation). Each page reads the services from one React provider (
src/services/react/services-provider.tsx). - Services hold the rules of each domain. They depend on small interfaces (a gateway, a wallet, a log source), so tests swap in fakes from
src/services/testing/. src/libis pure, side-effect-free code: hashing, commitments, typed data, the document container, the formats. The same code runs in the browser, in Node scripts and in the server routes, which is what keeps the TypeScript and Solidity digests identical.- Server helpers in
src/lib/server/are imported only by API routes, so server secrets never reach a client bundle.
The two paths through the system
Almost every feature is a combination of a read path and a write path.
Read path (anyone, no wallet)
Reads go through an RPC provider with failover across the configured URLs (src/lib/rpc/provider.ts). No wallet is needed, and the server is not involved.
Write path (a role holder with a wallet)
The server only sees an encrypted container and the issuer's address. The chain only sees commitments, hashes and the signature. The browser holds the plaintext, the document key and the salts, and hands them to the holder in the package. The full step list is in Issuing a credential.
Where state lives
| State | Lives in | Written by | Read by |
|---|---|---|---|
| Issuers, names, roles, pause flag, key lineage | IssuerRegistry | Admin, guardian | Everyone |
| Credential anchors and signatures | CredentialRegistry storage and events | Issuers | Everyone |
| Status and its history | StatusRegistry storage and events | Issuers, admin | Everyone |
| Wallet to enrolment bindings | HolderRegistry | Students, through issuer invitations | Issuers, the app |
| Issuer-defined record types | RecordTypeRegistry | Issuers | Everyone |
| Access groups and receipts | CertichainAccessGate and Semaphore | Holders, issuers, requesters | Everyone |
| Encrypted documents | IPFS, pinned by the server | Issuers (through the upload route) | Holders and grantees with a key |
| Credential packages, disclosure files, grants | The holder's device | The app, for the holder | Verifiers the holder chooses |
| Phone wallet entries | IndexedDB on the phone | The holder | The holder |
| Scan caches | Browser storage | The app | The app |
| Sign-in nonces, rate limits, monitor history | Redis (memory store outside production) | Server routes | Server routes |
| Contract addresses per chain | src/contracts/v03/deployment.<chainId>.json | Deployment scripts | App, scripts, server |
Source files
src/services/index.ts,src/services/react/services-provider.tsxsrc/lib/rpc/provider.tssrc/contracts/v03/manifest.tsblockchain/contracts/v03/src/app/api/