Commands
Every script below is taken from the four package.json files as of 2026-10-24. Run root scripts from the repository root and contract scripts from blockchain/. The Network column says what a script reaches:
- none: works offline (apart from installs and the first download of pinned files);
- local: needs a local Hardhat node;
- reads Sepolia: read only;
- writes Sepolia: sends transactions; run only with the human's go-ahead for that run.
Root app (package.json)
| Script | Runs | Network | What it does |
|---|---|---|---|
zk:public | node scripts/zk-public.mjs | none | Places the circuit files for tree depths 1 to 6 in public/zk/, each checked against src/lib/zk/circuit-hashes.json |
predev, prebuild | npm run zk:public | none | Run before dev and build |
dev | next dev --turbopack | as configured | Development server on port 3000 |
build | next build | none | Production build. Passes with no environment set; features without settings are off at run time |
start | next start | as configured | Serves a build |
lint | eslint . | none | |
typecheck | tsc --noEmit | none | Does not need the contracts compiled |
test | vitest | none | Watch mode |
test:run | vitest run --passWithNoTests | none | All unit tests once |
test:coverage | vitest run --coverage | none | Fails under the coverage thresholds for src/services and src/lib |
test:e2e | playwright test | local (own node) | Builds its own chain and app; see Local development |
interop:generate | vite-node ... interop/generate.ts | none | Regenerates the fixtures in interop/fixtures; CI fails if they differ from the committed ones |
interop | npm --prefix interop test | none | The independent verifier library tests (Interoperability and SDK tests) |
api:key | node scripts/api-key.mjs <id> | none | Makes a verification API key: prints the key once, its SHA-256 and the API_KEYS entry to add |
import:check | vite-node ... scripts/import-check.ts -- | none | npm run import:check -- <file> [--adapter csv|json|template] [--types "Name, Name"] [--folder <dir>]: validates a bulk import file without a wallet (Bulk import) |
sdk:build | npm --prefix packages/sdk run build | none | Compiles the SDK |
sdk:test | vitest run packages/sdk | none | SDK tests |
Contracts (blockchain/package.json)
Build, test and checks
| Script | Network | What it does |
|---|---|---|
compile | none | hardhat compile (Solidity 0.8.28) |
test | none | hardhat test, the whole contract suite |
coverage | none | Coverage, then fails under 95% lines or 90% branches (scripts/check-coverage.ts) |
sizes | none | Compiles and checks runtime sizes: 24,576 bytes (EIP-170) for the v0.2 contracts, a 20,000 byte budget for v0.3 |
gas | none | Tests with the gas reporter, writes gas-report.txt |
gas:check | none | Fails if a tracked operation costs more than 10% above gas-snapshot.json |
gas:snapshot | none | Rewrites gas-snapshot.json; refused when CI is set |
invariants | none | 500 random action sequences against the reference model; INVARIANT_RUNS, INVARIANT_ACTIONS, INVARIANT_SEED change or reproduce a run |
export-abis | none | Compiles and copies the ABIs into src/contracts/ |
Zero-knowledge checks
| Script | Network | What it does |
|---|---|---|
check:zk-pins | none | Fails when a proof system package is not pinned exactly or the lock file differs from the reviewed snapshot |
check:no-mock-verifier | none | Fails on a mock verifier in the sources and, after compile, on an unexpected Groth16-shaped function in the ABIs |
check:no-dev-keys | none | Fails when a tracked file is a proving key, powers of tau file, constraint system or generated verifier |
zk:artifacts | downloads | Fetches the 32 pairs of circuit files (about 163 MB) into .zk-artifacts, each checked against its recorded hash |
check:zk-keys | none | After zk:artifacts: the verification key of each of the 32 proving keys must match the pinned verifier |
zk:measure | none | Proof time, memory and file sizes at depths 2, 4 and 6 |
zk:reproduce | none | -- --workdir <dir> [--depth <1-32|all>]: rebuilds the proving keys from the pinned circuit source with circom and checks them against the Perpetual Powers of Tau. On demand, never in CI |
access | as given | The access tool: identity new <file>, identity show <file>, prove ..., submit ... (submit sends a transaction from ACCESS_SUBMIT_KEY). See ZK access |
More detail on these checks: ZK tests.
Local deployment
| Script | Network | What it does |
|---|---|---|
deploy:v03:local | local | Deploys a v0.3 set, writes src/contracts/v03/deployment.31337.json, exports ABIs. Takes GUARDIAN_ADDRESS, ADMIN_DELAY_SEC, ADD_SET, SET_LABEL, ISSUER_REGISTRY, ACTIVATE_SET |
issuers:v03:local | local | ISSUERS="0xAddr=Name;0xAddr=Name": registers issuers through the admin account, skipping registered ones |
smoke:v03:local | local | Registers the first account as issuer, issues a valid and a revoked credential, reads both back |
deploy:access:local | local | Deploys Poseidon, the Semaphore verifier, Semaphore and the access gate; writes src/contracts/zk/deployment.31337.json |
deploy:local | local | Legacy v0.2 deployment to blockchain/deployments/localhost.json |
Sepolia
Sepolia scripts load PRIVATE_KEY, INFURA_API_KEY and ETHERSCAN_API_KEY from the root .env (hardhat.config.ts reads ../.env). A deploy refuses to start when PRIVATE_KEY or INFURA_API_KEY is missing.
| Script | Network | What it does |
|---|---|---|
deploy:v03:sepolia | writes Sepolia | Deploys a v0.3 set. With ADD_SET=1 adds it to the manifest (a public chain's manifest is never replaced otherwise) |
verify:v03:sepolia | explorer only | Verifies a set's contracts on Etherscan (SET=<label> for a later set) |
issuers:v03:sepolia | writes Sepolia | Registers issuers from the deployer key; only useful before the admin moved to the Safe |
smoke:v03:sepolia | writes Sepolia | The v0.3 smoke on Sepolia |
handover:v03:sepolia | writes Sepolia | ADMIN_MULTISIG=<Safe>: nominates the Safe as admin of both registries after preflight checks |
governance:v03:sepolia | reads Sepolia | Admin, pending admin, delay, pause and guardians of every registry, and the timelock roles when ADMIN_MULTISIG is set |
deploy:timelock:sepolia | writes Sepolia | ADMIN_MULTISIG, TIMELOCK_DELAY_SEC: deploys CertichainTimelock, reads the roles back and records it in the manifest |
timelock:nominate:sepolia | writes Sepolia | SET=<label>: the deployer nominates the timelock on a new set's registries |
timelock:handover:sepolia | reads Sepolia | Says which handover step is next and writes the Safe batch file for it |
timelock:propose:sepolia | reads Sepolia | TARGET, FUNCTION, ARGS (and SET, DELAY_SEC, SALT or SIGNOFF_FILE, LABEL): writes the schedule, execute and cancel batch files for the Safe |
set:ready:sepolia | reads Sepolia | SET=<label>: lists what blocks activating a set |
set:activate:sepolia | reads Sepolia | Runs the readiness check, then rewrites activeSet in the manifest. Sends nothing |
admission:check:sepolia | reads Sepolia | INSTITUTION, SIGNOFF_FILE (and FROM_BLOCK): whether that sign-off matches a scheduled, executed or cancelled admission |
governance:log:check | none | Checks every entry of the governance log under docs/governance/log/ |
deploy:access:sepolia | writes Sepolia | Deploys the access gate for the active set's credential registry |
verify:access:sepolia | explorer only | Verifies the Semaphore verifier, Semaphore and the gate on Etherscan |
network:measure | none | Replays the tracked operations on Hardhat and writes gas and calldata sizes to docs/network/operations.json |
network:report | none | Combines operations.json with dated fee readings in docs/network/fee-levels.json into docs/network/cost-report.md |
deploy:sepolia, verify:sepolia, smoke:sepolia | writes Sepolia | Legacy v0.2 deployment, verification and smoke (SMOKE_WRITE=true SMOKE_STUDENT_ADDRESS=0x... adds writes) |
scripts/verify-timelock.ts has no npm script: npx hardhat run scripts/verify-timelock.ts --network sepolia with ADMIN_MULTISIG set. The governance scripts are explained in Governance and Contract set upgrade; the deploy scripts in Deployment and scripts.
Without an npm script
npx ts-node scripts/verify-credential.ts <file> [--rpc <url> --registry <address>] checks a credential package, a VC or an Open Badge file offline, and on chain when both flags are given. Exit code 0 only when everything checked is valid. See Command-line tools.
SDK (packages/sdk/package.json)
| Script | What it does |
|---|---|
build | tsc -p tsconfig.build.json into dist/ |
prepack | Builds before packing. The package is private and unpublished |
The SDK's tests run from the root (npm run sdk:test). See SDK.
Interoperability tests (interop/package.json)
| Script | What it does |
|---|---|
test | node --test over interop/test/*.test.mjs: an independent verifier library checks Certichain VCs, badges and status lists |
What CI runs
The single workflow .github/workflows/ci.yml (Node 22) has six jobs:
| Job | Steps |
|---|---|
| Contracts | check:zk-pins, check:no-mock-verifier, check:no-dev-keys, compile, zk:artifacts, check:zk-keys, sizes, gas:check, invariants, gas, coverage |
| Slither | Static analysis of the contracts |
| Frontend | lint, typecheck, zk:public, test:coverage, build |
| Interop | interop:generate (fixtures must be unchanged), interop |
| SDK | sdk:build, sdk:test |
| E2E | npx playwright install --with-deps chromium, test:e2e |
Source files
package.json,blockchain/package.json,packages/sdk/package.json,interop/package.jsonscripts/zk-public.mjs,scripts/api-key.mjs,scripts/import-check.tsblockchain/scripts/(one file per script above),blockchain/scripts/lib/network-env.ts.github/workflows/ci.yml- Handbook:
docs/process/commands.md
Tests
- Script logic under
blockchain/test/v03/:Deploy.test.ts,RecordDeployment.test.ts,Handover.test.ts,TimelockDeploy.test.ts,TimelockHandover.test.ts,TimelockProposal.test.ts,SetReadiness.test.ts,Admission.test.ts,GovernanceLog.test.ts,VerifyTargets.test.ts,VerifyCredential.test.ts,VerifyVc.test.ts src/lib/import/check-cli.test.ts,e2e/support/guards.test.ts