Deployment and scripts
Everything that compiles, deploys, verifies or administers the contracts lives in blockchain/. This page lists the compiler settings, the network configuration, and every script with its npm command, its inputs and whether it sends transactions. Step-by-step operating procedures are in the handbook runbooks (see Runbooks); this page is the reference.
Any script that sends a transaction to Sepolia (or a future production chain) is run only with the project owner's explicit go-ahead. Read-only scripts and local runs need no approval. Keys come from the root .env, which is never committed or printed; example.env lists the variable names.
Compiler profiles
blockchain/scripts/lib/solidity-profiles.ts defines two profiles, both Solidity 0.8.28 with the optimizer on (200 runs) and viaIR: true:
| Profile | EVM version | Applies to |
|---|---|---|
PARIS_PROFILE (default) | Paris (Hardhat's default; the build info records paris) | v0.2 contracts in blockchain/contracts/ (OpenZeppelin ^4.9.3), blockchain/contracts/zk/ and the Semaphore packages |
CANCUN_PROFILE (override) | cancun | Every file under blockchain/contracts/v03/ and every file of @openzeppelin/contracts-v5 |
OpenZeppelin 5 needs Cancun opcodes. The v0.2 contracts stay on Paris because they are deployed and verified with it. @openzeppelin/contracts-v5 is an npm alias of OpenZeppelin 5, so both major versions live side by side. The cancunOverrides helper lists the files at config time, because Hardhat builds one compile job per imported library file.
Hardhat configuration
From blockchain/hardhat.config.ts (it loads ../.env):
| Setting | Value |
|---|---|
| Networks | hardhat (in-process), localhost (Hardhat's built-in default, a local node on port 8545), sepolia (Infura URL built from INFURA_API_KEY, account from PRIVATE_KEY) |
| Etherscan | ETHERSCAN_API_KEY |
| Gas reporter | On when REPORT_GAS=true, written to REPORT_GAS_FILE |
| Paths | contracts/, test/, cache/, artifacts/ |
blockchain/scripts/lib/network-env.ts makes Sepolia scripts fail fast when PRIVATE_KEY or INFURA_API_KEY is missing.
Deploying a v0.3 set
npm run deploy:v03:local or deploy:v03:sepolia runs blockchain/scripts/deploy-v03.ts (logic in blockchain/scripts/lib/deploy-v03.ts):
- Deploy
IssuerRegistry(adminDelay, admin), or reuse an existing one whenISSUER_REGISTRYis set. A shared issuer registry must have key lineage (contract version 0.5.0 or later), because the new registries ask it whether a key is active; an older one is refused before anything is sent. - Deploy
StatusRegistry(adminDelay, admin, issuers),HolderRegistry(issuers),RecordTypeRegistry(issuers), thenCredentialRegistry(issuers, statuses, holders, recordTypes). - Call
StatusRegistry.bindCredentialRegistry(credentialRegistry). - Grant
GUARDIAN_ROLEtoGUARDIAN_ADDRESSif given (only when the issuer registry is new). - Record the set in
src/contracts/v03/deployment.<chainId>.jsonand export the ABIs.
| Variable | Meaning | Default |
|---|---|---|
SET_LABEL | Label of the set in the manifest, 1 to 32 letters, digits, dots, dashes or underscores, starting with a letter or digit | The ABI version, 0.5.0 |
ISSUER_REGISTRY | Share an existing issuer registry (0.5.0 or later) | A new one |
GUARDIAN_ADDRESS | Account to receive the guardian role | None |
ADMIN_DELAY_SEC | Admin transfer delay of the two admin registries, a positive whole number up to the uint48 maximum | 3 days |
ADD_SET=1 | Add this deployment beside the sets already in the manifest | Off |
ACTIVATE_SET=1 | With ADD_SET=1 only: also make it the active set | Off |
The deployer is the first admin. A plain deployment (no ADD_SET) refuses to overwrite an existing manifest on a non-local chain, and every manifest check runs before the first transaction (assertRecordable in blockchain/scripts/lib/record-deployment.ts). The manifest format is described in Deployment manifest, and the reasoning for side-by-side sets in Contract set upgrade.
Script reference
All commands run from blockchain/. "Sends" means it signs transactions with PRIVATE_KEY.
Deploy
| npm command | Script | Inputs | Sends |
|---|---|---|---|
deploy:local, deploy:sepolia | deploy.ts | none | Yes: v0.2 RecordStorage and linked AcademicRecords; writes deployments/<network>.json, exports ABIs |
deploy:v03:local, deploy:v03:sepolia | deploy-v03.ts | Variables above | Yes |
deploy:access:local, deploy:access:sepolia | deploy-access-gate.ts | The v0.3 manifest of the chain | Yes: PoseidonT3 at its published address if missing, SemaphoreVerifier, Semaphore, the gate; checks the verifier and Poseidon code hashes against pinned values; writes src/contracts/zk/deployment.<chainId>.json |
deploy:timelock:sepolia | deploy-timelock.ts | ADMIN_MULTISIG, TIMELOCK_DELAY_SEC | Yes: one transaction; refuses a non-Safe, a delay under 1 hour or over 30 days, a wrong chain, an existing timelock |
export-abis | export-abis.ts | none | No: compiles and copies ABIs to src/contracts/ |
Verify on Etherscan
These submit source code and send no transaction. They need ETHERSCAN_API_KEY.
| npm command | Script | Notes |
|---|---|---|
verify:sepolia | verify.ts | v0.2 contracts, with the library link |
verify:v03:sepolia | verify-v03.ts | One set (SET=label); run before the admin moves, because constructor arguments name the admin at deployment |
verify:access:sepolia | verify-access-gate.ts | The verifier, Semaphore (linked to Poseidon) and the gate, in deployment order; not the shared Poseidon library |
(no npm alias) npx hardhat run scripts/verify-timelock.ts --network sepolia | verify-timelock.ts | ADMIN_MULTISIG; rebuilds the timelock's constructor arguments |
Issuers and smoke tests
| npm command | Script | Inputs | Sends |
|---|---|---|---|
issuers:v03:local, issuers:v03:sepolia | add-issuers-v03.ts | ISSUERS="0xAddress=Name;0xAddress=Name" | Yes; the first account must be the issuer registry admin; registered addresses are skipped. After a handover, issuers are added through timelock:propose instead |
smoke:v03:local, smoke:v03:sepolia | smoke-v03.ts | none | Yes: a write smoke signed by the first account (the admin right after deployment) |
smoke:sepolia | smoke.ts | SMOKE_WRITE, SMOKE_STUDENT_ADDRESS, SMOKE_UNIVERSITY_NAME | Read checks always; writes a demo record only with SMOKE_WRITE=true (v0.2) |
(none) npx ts-node scripts/verify-credential.ts <file> [--rpc <url> --registry <address>] | verify-credential.ts | A credential package, VC or Open Badge file | No: checks the file offline and, with --rpc, against the chain (anchor, issuer registration, signature) |
Governance
| npm command | Script | Inputs | Sends |
|---|---|---|---|
handover:v03:sepolia | handover-v03.ts | ADMIN_MULTISIG, optional GUARDIAN_ADDRESS | Yes: begins the two-step admin transfer of both registries to the Safe (Safe-era handover, before the timelock) |
governance:v03:sepolia | governance-status.ts | Optional ADMIN_MULTISIG and TIMELOCK_DELAY_SEC (without them the timelock roles and delay are not checked) | No: admins, pending admins, guardians and timelock state of every set, read from logs in 5,000 block ranges |
timelock:nominate:sepolia | nominate-timelock.ts | SET, ADMIN_MULTISIG, optional TIMELOCK_DELAY_SEC | Yes: the deployer of a new set nominates the timelock on its two registries |
timelock:handover:sepolia | timelock-handover.ts | ADMIN_MULTISIG, optional TIMELOCK_DELAY_SEC, SET | No: reads the chain, says the next step and writes Safe batch files |
timelock:propose:sepolia | timelock-propose.ts | TARGET, FUNCTION, ARGS, optional SET, SALT or SIGNOFF_FILE, DELAY_SEC, LABEL | No: writes schedule, execute and cancel batch files and prints the operation id |
admission:check:sepolia | admission-check.ts | INSTITUTION, SIGNOFF_FILE, optional FROM_BLOCK | No: finds the scheduled addIssuer whose salt is the sign-off hash and reports scheduled, executed or cancelled |
governance:log:check | governance-log-check.ts | Optional log directory (default ../docs/governance/log) | No: every dated entry records its decision and its file hash matches |
set:ready:sepolia | check-set-ready.ts | SET | No: lists what blocks a set from becoming active (for example issuers not yet registered in it) |
set:activate:sepolia | activate-set.ts | SET | No transaction: runs the readiness check and, with no blockers, marks the set active in the manifest file |
The governance flow these scripts serve is in Governance and Timelock.
Access gate tooling
| npm command | Script | Notes |
|---|---|---|
access | access.ts | Command-line prover and submitter for the access gate; ACCESS_SUBMIT_KEY signs the proveAccess transaction |
Proof system checks
| npm command | Script | Checks |
|---|---|---|
check:zk-pins | check-zk-pins.ts | Proof system packages (Semaphore, ZK-Kit, snarkjs and helpers) are exact versions matched by the lock file, with integrity and a registry address; no overrides; the lock file matches the reviewed snapshot |
check:no-mock-verifier | check-no-mock-verifier.ts | No mock or trivially-true verifier in the sources, and none in the compiled ABIs |
check:no-dev-keys | check-no-dev-keys.ts | No zkey, ptau, r1cs, verification key or verifier contract files committed |
zk:artifacts | fetch-zk-artifacts.ts | Downloads the pinned circuit files (.wasm, .zkey) into blockchain/.zk-artifacts, each checked against test/zk/fixtures/artifact-hashes.json |
check:zk-keys | check-zk-keys.ts | The verification key of every pinned zkey (depths 1 to 32) equals the points inside the pinned verifier |
zk:reproduce | reproduce-zk-setup.ts | Rebuilds the zkey files from the circuit source and the Perpetual Powers of Tau; needs --workdir |
zk:measure | measure-proving.ts | Times proof generation |
What each check proves is explained in Zero-knowledge tests.
Tests, coverage, sizes and gas
| npm command | What it runs |
|---|---|
test | The whole Hardhat suite |
compile | hardhat compile |
coverage | hardhat coverage, then check-coverage.ts enforces lines 95 percent and branches 90 percent (interfaces excluded) |
sizes | Compile, then the size budgets (check-sizes.ts) |
gas, gas:check, gas:snapshot | Gas report, snapshot check, snapshot refresh (see Gas and limits) |
invariants | The invariant suite with 500 runs |
network:measure, network:report | Operation replay with calldata sizes, and the network cost report |
The root package.json adds app-side commands that touch contract code: npm run zk:public (places the circuit files the browser prover loads into public/zk, each checked against src/lib/zk/circuit-hashes.json) and npm run import:check (validates a bulk import file). See Commands.
Manifests and ABIs the app reads
| File | Written by | Read by |
|---|---|---|
src/contracts/v03/deployment.<chainId>.json | deploy-v03.ts, deploy-timelock.ts (adds timelock), activate-set.ts (sets activeSet) | The app, the server routes, every script |
src/contracts/v03/*.json | ABI export | The app |
src/contracts/zk/deployment.<chainId>.json, src/contracts/zk/CertichainAccessGate.json | deploy-access-gate.ts | The app's access services |
src/contracts/*.json (v0.2 ABIs) | deploy.ts, export-abis | The legacy app paths |
blockchain/timelock-files/<chainId>/ | Timelock scripts | The Safe Transaction Builder |
On Sepolia the manifest lists set 0.3.0 (active, deployed 2026-10-13 at block 11904844) and set 0.5.0 (deployed 2026-10-17 at block 11932876, not active), plus the timelock. See Deployments for every address.
Source files
blockchain/hardhat.config.ts,blockchain/package.jsonblockchain/scripts/*.tsandblockchain/scripts/lib/*.tsblockchain/scripts/lib/solidity-profiles.ts,deploy-v03.ts,record-deployment.ts,network-env.tssrc/contracts/v03/manifest.ts
Tests
blockchain/test/v03/Deploy.test.ts,RecordDeployment.test.ts,Manifest.test.ts,ManifestFile.test.ts,SetReadiness.test.ts,IssuerList.test.ts,VerifyTargets.test.ts,VerifyCredential.test.ts,VerifyVc.test.tsblockchain/test/v03/TimelockDeploy.test.ts,TimelockHandover.test.ts,TimelockProposal.test.ts,TimelockTargets.test.ts,Handover.test.ts,Admission.test.ts,GovernanceLog.test.tsblockchain/test/Deployment.ts,blockchain/test/Smoke.test.tsblockchain/test/zk/DeployAccessGate.test.ts,VerifyAccessTargets.test.ts,ZkPins.test.ts,MockVerifierScan.test.ts,DevKeyFiles.test.ts,ZkArtifacts.test.ts,KeyCheck.test.ts,SetupReproduction.test.ts