Skip to main content

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.

Public networks need approval

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:

ProfileEVM versionApplies 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)cancunEvery 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):

SettingValue
Networkshardhat (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)
EtherscanETHERSCAN_API_KEY
Gas reporterOn when REPORT_GAS=true, written to REPORT_GAS_FILE
Pathscontracts/, 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):

  1. Deploy IssuerRegistry(adminDelay, admin), or reuse an existing one when ISSUER_REGISTRY is 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.
  2. Deploy StatusRegistry(adminDelay, admin, issuers), HolderRegistry(issuers), RecordTypeRegistry(issuers), then CredentialRegistry(issuers, statuses, holders, recordTypes).
  3. Call StatusRegistry.bindCredentialRegistry(credentialRegistry).
  4. Grant GUARDIAN_ROLE to GUARDIAN_ADDRESS if given (only when the issuer registry is new).
  5. Record the set in src/contracts/v03/deployment.<chainId>.json and export the ABIs.
VariableMeaningDefault
SET_LABELLabel of the set in the manifest, 1 to 32 letters, digits, dots, dashes or underscores, starting with a letter or digitThe ABI version, 0.5.0
ISSUER_REGISTRYShare an existing issuer registry (0.5.0 or later)A new one
GUARDIAN_ADDRESSAccount to receive the guardian roleNone
ADMIN_DELAY_SECAdmin transfer delay of the two admin registries, a positive whole number up to the uint48 maximum3 days
ADD_SET=1Add this deployment beside the sets already in the manifestOff
ACTIVATE_SET=1With ADD_SET=1 only: also make it the active setOff

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 commandScriptInputsSends
deploy:local, deploy:sepoliadeploy.tsnoneYes: v0.2 RecordStorage and linked AcademicRecords; writes deployments/<network>.json, exports ABIs
deploy:v03:local, deploy:v03:sepoliadeploy-v03.tsVariables aboveYes
deploy:access:local, deploy:access:sepoliadeploy-access-gate.tsThe v0.3 manifest of the chainYes: 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:sepoliadeploy-timelock.tsADMIN_MULTISIG, TIMELOCK_DELAY_SECYes: one transaction; refuses a non-Safe, a delay under 1 hour or over 30 days, a wrong chain, an existing timelock
export-abisexport-abis.tsnoneNo: compiles and copies ABIs to src/contracts/

Verify on Etherscan​

These submit source code and send no transaction. They need ETHERSCAN_API_KEY.

npm commandScriptNotes
verify:sepoliaverify.tsv0.2 contracts, with the library link
verify:v03:sepoliaverify-v03.tsOne set (SET=label); run before the admin moves, because constructor arguments name the admin at deployment
verify:access:sepoliaverify-access-gate.tsThe 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 sepoliaverify-timelock.tsADMIN_MULTISIG; rebuilds the timelock's constructor arguments

Issuers and smoke tests​

npm commandScriptInputsSends
issuers:v03:local, issuers:v03:sepoliaadd-issuers-v03.tsISSUERS="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:sepoliasmoke-v03.tsnoneYes: a write smoke signed by the first account (the admin right after deployment)
smoke:sepoliasmoke.tsSMOKE_WRITE, SMOKE_STUDENT_ADDRESS, SMOKE_UNIVERSITY_NAMERead 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.tsA credential package, VC or Open Badge fileNo: checks the file offline and, with --rpc, against the chain (anchor, issuer registration, signature)

Governance​

npm commandScriptInputsSends
handover:v03:sepoliahandover-v03.tsADMIN_MULTISIG, optional GUARDIAN_ADDRESSYes: begins the two-step admin transfer of both registries to the Safe (Safe-era handover, before the timelock)
governance:v03:sepoliagovernance-status.tsOptional 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:sepolianominate-timelock.tsSET, ADMIN_MULTISIG, optional TIMELOCK_DELAY_SECYes: the deployer of a new set nominates the timelock on its two registries
timelock:handover:sepoliatimelock-handover.tsADMIN_MULTISIG, optional TIMELOCK_DELAY_SEC, SETNo: reads the chain, says the next step and writes Safe batch files
timelock:propose:sepoliatimelock-propose.tsTARGET, FUNCTION, ARGS, optional SET, SALT or SIGNOFF_FILE, DELAY_SEC, LABELNo: writes schedule, execute and cancel batch files and prints the operation id
admission:check:sepoliaadmission-check.tsINSTITUTION, SIGNOFF_FILE, optional FROM_BLOCKNo: finds the scheduled addIssuer whose salt is the sign-off hash and reports scheduled, executed or cancelled
governance:log:checkgovernance-log-check.tsOptional log directory (default ../docs/governance/log)No: every dated entry records its decision and its file hash matches
set:ready:sepoliacheck-set-ready.tsSETNo: lists what blocks a set from becoming active (for example issuers not yet registered in it)
set:activate:sepoliaactivate-set.tsSETNo 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 commandScriptNotes
accessaccess.tsCommand-line prover and submitter for the access gate; ACCESS_SUBMIT_KEY signs the proveAccess transaction

Proof system checks​

npm commandScriptChecks
check:zk-pinscheck-zk-pins.tsProof 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-verifiercheck-no-mock-verifier.tsNo mock or trivially-true verifier in the sources, and none in the compiled ABIs
check:no-dev-keyscheck-no-dev-keys.tsNo zkey, ptau, r1cs, verification key or verifier contract files committed
zk:artifactsfetch-zk-artifacts.tsDownloads the pinned circuit files (.wasm, .zkey) into blockchain/.zk-artifacts, each checked against test/zk/fixtures/artifact-hashes.json
check:zk-keyscheck-zk-keys.tsThe verification key of every pinned zkey (depths 1 to 32) equals the points inside the pinned verifier
zk:reproducereproduce-zk-setup.tsRebuilds the zkey files from the circuit source and the Perpetual Powers of Tau; needs --workdir
zk:measuremeasure-proving.tsTimes proof generation

What each check proves is explained in Zero-knowledge tests.

Tests, coverage, sizes and gas​

npm commandWhat it runs
testThe whole Hardhat suite
compilehardhat compile
coveragehardhat coverage, then check-coverage.ts enforces lines 95 percent and branches 90 percent (interfaces excluded)
sizesCompile, then the size budgets (check-sizes.ts)
gas, gas:check, gas:snapshotGas report, snapshot check, snapshot refresh (see Gas and limits)
invariantsThe invariant suite with 500 runs
network:measure, network:reportOperation 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​

FileWritten byRead by
src/contracts/v03/deployment.<chainId>.jsondeploy-v03.ts, deploy-timelock.ts (adds timelock), activate-set.ts (sets activeSet)The app, the server routes, every script
src/contracts/v03/*.jsonABI exportThe app
src/contracts/zk/deployment.<chainId>.json, src/contracts/zk/CertichainAccessGate.jsondeploy-access-gate.tsThe app's access services
src/contracts/*.json (v0.2 ABIs)deploy.ts, export-abisThe legacy app paths
blockchain/timelock-files/<chainId>/Timelock scriptsThe 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.json
  • blockchain/scripts/*.ts and blockchain/scripts/lib/*.ts
  • blockchain/scripts/lib/solidity-profiles.ts, deploy-v03.ts, record-deployment.ts, network-env.ts
  • src/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.ts
  • blockchain/test/v03/TimelockDeploy.test.ts, TimelockHandover.test.ts, TimelockProposal.test.ts, TimelockTargets.test.ts, Handover.test.ts, Admission.test.ts, GovernanceLog.test.ts
  • blockchain/test/Deployment.ts, blockchain/test/Smoke.test.ts
  • blockchain/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