Configuration
Certichain is configured only through environment variables. example.env in the repository root lists the names with empty values; real values live in .env or .env.local on a laptop (gitignored) and in the Vercel project settings for the hosted site. This page never shows a value.
Rules that apply to every variable
- Public or server only. A name starting with
NEXT_PUBLIC_is copied into the browser bundle at build time and is visible to every visitor. Anything else stays on the server. A secret must never get aNEXT_PUBLIC_name. - Build time.
NEXT_PUBLIC_values are inlined when the app is built (src/lib/chain.tsreads them by literal name for that reason), so changing one needs a rebuild, not only a restart. - Features are off until configured. A server feature whose required settings are missing answers as switched off (usually
503orCONFIG_MISSING) instead of failing the build. A malformed optional setting is an error that names the setting, never its value. - Sensitive in Vercel. Keys, tokens and secrets are stored as Sensitive variables. The agent rules of the project forbid reading
.envfiles at all.
Chain and contracts (public)
| Variable | Read by | Default | Meaning |
|---|---|---|---|
NEXT_PUBLIC_NETWORK_ID | src/lib/chain.ts, src/services/contracts/address-book.ts, and the config readers of sign-in, the API, status lists and the monitor | none | Chain id the build serves (11155111 Sepolia, 31337 local). Picks the deployment manifest. Required by every chain feature |
NEXT_PUBLIC_CONTRACT_VERSION | src/lib/contract-version.ts | v02 | Exactly v03 selects the v0.3 contract sets from the manifest; any other value or none selects the legacy v0.2 contract |
NEXT_PUBLIC_CONTRACT_ADDRESS | src/lib/chain.ts, src/services/contracts/address-book.ts, src/lib/server/config.ts | none | The v0.2 AcademicRecords address. Required for v0.2 sign-in; unused on v0.3 |
NEXT_PUBLIC_START_BLOCK | src/services/contracts/address-book.ts, src/services/domain/sharing-service.ts | none | Block of the v0.2 deployment, where v0.2 share history scans start. v0.3 sets carry their own deploymentBlock in the manifest |
Chain reads (RPC)
| Variable | Public | Read by | Meaning |
|---|---|---|---|
NEXT_PUBLIC_RPC_URLS | yes | src/lib/rpc/env.ts, browser gateways | Comma-separated endpoints for the browser, tried in order. Visible to visitors: use only keys you accept being public, restricted by domain at the provider |
NEXT_PUBLIC_RPC_URL | yes | same | Single endpoint, tried last |
RPC_URLS | no | src/lib/rpc/env.ts (serverRpcUrls) | Server endpoints, in order. May carry provider keys |
RPC_URL | no | same | Server last resort, and the older single setting |
The server uses RPC_URLS, or NEXT_PUBLIC_RPC_URLS when that is empty, followed by RPC_URL or NEXT_PUBLIC_RPC_URL. Each endpoint gets 8 seconds; a failed one is skipped for 30 seconds. A list containing an address that is not http or https switches off the feature that needs it. Details: Reading the chain.
Issuer sign-in and sessions (server)
| Variable | Read by | Meaning |
|---|---|---|
SESSION_SECRET | src/lib/server/config.ts (readAuthConfig) | Signs issuer sessions. At least 32 characters, or sign-in is off. Changing it signs everyone out |
SIWE_ALLOWED_DOMAINS | same | Comma-separated domains a Sign-In with Ethereum message may name. Required in production (NODE_ENV=production), optional in development |
Sign-in also needs NEXT_PUBLIC_NETWORK_ID, a server RPC endpoint and, on v0.2, NEXT_PUBLIC_CONTRACT_ADDRESS. Sign-in protects the upload and document routes; see Sign-in and sessions.
Shared store (server)
| Variable | Read by | Meaning |
|---|---|---|
UPSTASH_REDIS_REST_URL, UPSTASH_REDIS_REST_TOKEN | src/lib/server/config.ts (readStoreConfig), src/lib/server/rate-limit.ts | Upstash Redis for rate limits, used nonces, monitor state and history, and the pin ledger. KV_REST_API_URL and KV_REST_API_TOKEN are accepted as fallbacks |
Without them, development keeps everything in the memory of one server process. In production getStore returns nothing and the routes that need a shared store answer CONFIG_MISSING; the verification API accepts per-instance counters only with API_ALLOW_LOCAL_LIMITS=true.
Documents and pinning
| Variable | Public | Read by | Meaning |
|---|---|---|---|
PINATA_JWT | no | src/lib/server/config.ts (readPinataCredentials) | Pinata token for uploads. Preferred. Needs file read and write access for key replacement and removal of old copies |
PINATA_API_KEY, PINATA_API_SECRET_KEY | no | same | Older key pair, used only when PINATA_JWT is empty |
NEXT_PUBLIC_PINATA_GATEWAY_URL | yes | src/lib/pinata.ts, src/services/react/browser-services.ts, src/lib/server/pinning/providers.ts | Gateway host the browser fetches containers from, and the first gateway the monitor reads pins back from |
SECOND_PIN_PROVIDER | no | src/lib/server/pinning/providers.ts | Name of a second pin service adapter. Empty keeps one pin. The adapter table is empty as of 2026-10-24, so any name is a configuration error |
PIN_READ_GATEWAYS | no | same | Extra gateways the monitor reads pins from, comma separated |
PINATA_JWT_SECRET_ACCESS_TOKEN and PINATA_GATEWAY_ACCESS_KEY are listed in example.env but no code reads them. See Upload and documents.
Status lists
| Variable | Public | Read by | Meaning |
|---|---|---|---|
STATUS_LIST_SIGNER_KEY | no | src/lib/server/status-lists/config.ts, src/lib/server/ops/wiring.ts | Private key (0x and 64 hex characters) of a funds-free publisher that signs the lists |
NEXT_PUBLIC_STATUS_LIST_BASE_URL | yes | status list config, src/services/index.ts | Site address the list URLs in exported VCs point to |
NEXT_PUBLIC_STATUS_LIST_PUBLISHER | yes | src/lib/server/status-lists/wiring.ts, src/services/index.ts, monitor | The publisher's address that checkers pin |
STATUS_LIST_CACHE_SECONDS | no | status list config | Server cache, 0 to 300 seconds, default 300 |
The feature is on only with the signer key, the base address, a chain id and an RPC endpoint. See Status lists.
Verification API
| Variable | Public | Read by | Meaning |
|---|---|---|---|
API_SIGNER_KEY | no | src/lib/server/api/config.ts, wiring.ts, monitor | Funds-free key that signs every answer. Without it the API answers 503 |
NEXT_PUBLIC_API_SIGNER | yes | API wiring, monitor and probe | Its address, which clients and the SDK pin |
API_KEYS | no | src/lib/server/api/config.ts | JSON list of { "id", "sha256", "tier" }, hashes only (make entries with npm run api:key -- <id>). Ids 1 to 40 lowercase letters, digits and hyphens; no repeats |
API_CACHE_SECONDS | no | same | Server cache, 0 to 60, default 15 |
API_ALLOW_LOCAL_LIMITS | no | src/lib/server/api/instance.ts | true lets production run without the shared store, with per-instance limits (single-host setups) |
See Verification API.
Monitor and reports
| Variable | Read by | Default | Meaning |
|---|---|---|---|
CRON_SECRET | src/lib/server/ops/config.ts, instance.ts, slo/report-instance.ts | none | Bearer secret for /api/ops/check and /api/ops/report, at least 32 characters. Without it the monitor is off |
OPS_WEBHOOK_URL | same | none | https incoming webhook for alerts and the monthly report |
OPS_MAX_LAG_BLOCKS | ops config | 20 | An endpoint further behind warns |
OPS_LOOKBACK_BLOCKS | ops config | 600, at most 50,000 | Window for administrative events; keep it longer than the schedule interval |
OPS_WARN_DAYS | ops config | 14 | Days before a key expiry to warn |
OPS_KEY_EXPIRIES | ops config, checks/key-expiry.ts | none | JSON object of names to YYYY-MM-DD dates |
OPS_PROBE_API_URL | ops config | none | Base address of the verification API the canary probe calls (https, or http on loopback) |
OPS_PROBE_CANARY_ID | ops config | none | Credential id of the canary (0x and 64 hex) |
OPS_PROBE_API_KEY | ops config | none | Optional API key for the probe, 43 to 128 characters |
The probe needs OPS_PROBE_API_URL, OPS_PROBE_CANARY_ID and NEXT_PUBLIC_API_SIGNER together. See Monitoring.
Build and runtime
| Variable | Read by | Meaning |
|---|---|---|
NODE_ENV | Next.js, src/lib/server/config.ts | production makes SIWE_ALLOWED_DOMAINS and the shared store required |
NEXT_DIST_DIR | next.config.ts | Build folder, default .next; the E2E harness uses .next-e2e |
Contract scripts (blockchain/)
blockchain/hardhat.config.ts loads the root .env (../.env). None of these are read by the app.
| Variable | Used by | Meaning |
|---|---|---|
PRIVATE_KEY | Sepolia deploys and admin scripts, the access tool | Deployer key. Testnet only; retired after the admin handover |
INFURA_API_KEY | Sepolia network URL | Required with PRIVATE_KEY for any Sepolia script |
ETHERSCAN_API_KEY | verify* scripts | Contract verification on the explorer |
GUARDIAN_ADDRESS, ADMIN_DELAY_SEC | deploy:v03:* | Guardian of a new issuer registry; admin transfer delay (default 3 days) |
ADD_SET, SET_LABEL, ISSUER_REGISTRY, ACTIVATE_SET | deploy:v03:* | Add a set beside the others, its label, share an issuer registry, make it active |
ADMIN_MULTISIG | handover, timelock and governance scripts | The Safe's address |
TIMELOCK_DELAY_SEC | deploy:timelock:sepolia, governance checks | The timelock delay to deploy with or expect |
SET | verify, timelock, readiness and activation scripts | Label of the set the command is about |
TARGET, FUNCTION, ARGS, DELAY_SEC, SALT, SIGNOFF_FILE, LABEL | timelock:propose:sepolia | The call to schedule, its delay and salt, and the batch file label |
INSTITUTION, SIGNOFF_FILE, FROM_BLOCK | admission:check:sepolia | Which admission to look for, and from which block |
ISSUERS | issuers:v03:* | 0xAddr=Name;0xAddr=Name |
SMOKE_WRITE, SMOKE_STUDENT_ADDRESS, SMOKE_UNIVERSITY_NAME | smoke:sepolia (v0.2) | Optional write checks |
ACCESS_SUBMIT_KEY | access submit | Key of the account a proof was made for |
INVARIANT_RUNS, INVARIANT_ACTIONS, INVARIANT_SEED | invariants | Size of a run, or a seed to reproduce one |
REPORT_GAS, REPORT_GAS_FILE, GAS_SNAPSHOT_WRITE, CI | gas, gas:snapshot | Gas reporter output; snapshot writing, refused when CI is set |
Source files
example.envsrc/lib/chain.ts,src/lib/contract-version.ts,src/lib/rpc/env.ts,src/lib/pinata.tssrc/lib/server/config.ts,src/lib/server/rate-limit.ts,src/lib/server/pinning/providers.tssrc/lib/server/status-lists/config.ts,src/lib/server/api/config.ts,src/lib/server/api/instance.ts,src/lib/server/ops/config.tsnext.config.ts,blockchain/hardhat.config.ts,blockchain/scripts/lib/network-env.ts,blockchain/scripts/lib/deploy-v03.ts,blockchain/scripts/lib/record-deployment.ts- Handbook:
docs/process/runbooks/keys.md(the key inventory)
Tests
src/lib/server/config.test.ts,src/lib/rpc/env.test.ts,src/lib/contract-version.test.tssrc/lib/server/status-lists/config.test.ts,src/lib/server/api/config.test.ts,src/lib/server/ops/config.test.tsblockchain/test/v03/RecordDeployment.test.ts,blockchain/test/v03/Deploy.test.ts