Skip to main content

Deployment manifest

The app never hard-codes contract addresses for v0.3. It reads a deployment manifest per chain: a JSON file in the repository, bundled into the build, that lists every contract set deployed on that chain and which one is active. A second manifest records the ZK access gate. The deploy scripts write these files; the browser, the server routes and the scripts read them.

FileContentsParser
src/contracts/v03/deployment.<chainId>.jsonThe v0.3 contract sets of one chain, the active set, the timelocksrc/contracts/v03/manifest.ts
src/contracts/zk/deployment.<chainId>.jsonThe access gate, its Semaphore contracts and pinned code hashessrc/contracts/zk/manifest.ts
src/contracts/v03/deployment.example.json, src/contracts/zk/deployment.example.jsonShape examples with zero addresses
blockchain/deployments/<network>.jsonThe legacy v0.2 deployment recordRead by the v0.2 scripts only

Tracked in the repository as of 2026-10-24: the v0.3 manifest and the access gate manifest for chain 11155111 (Sepolia), plus the two examples. The local chain files (deployment.31337.json) are written by the local deploy scripts and are gitignored. The addresses are listed in Deployments.

v0.3 manifest, schema 2​

Schema 2 lists several sets. It is what Sepolia uses.

FieldTypeRequiredMeaning and validation
schemaVersionnumberyes2
networkstringyesName of the network, for people (sepolia)
chainIdnumberyesMust equal the chain the loader asked for, else "chainId ... does not match ..."
activeSetstringyesLabel of the set new credentials are issued in; must be one of the listed labels
timelockstringnoAddress of the CertichainTimelock recorded for this chain. Once recorded it is never replaced by another address (withTimelock).
setsarrayyesAt least one set
sets[].labelstringyes1 to 32 letters, digits, dots, dashes or underscores, starting with a letter or digit; unique
sets[].deployedAtstringyesISO timestamp of the deployment
sets[].deploymentBlocknumberyesBlock where log scans for this set start
sets[].abiVersionstringyesABI version of the set (0.3.0, 0.5.0)
sets[].contractsobjectyesIssuerRegistry, CredentialRegistry, StatusRegistry, HolderRegistry, RecordTypeRegistry, each a 20-byte hex address. No two sets may share a CredentialRegistry (two sets may share an IssuerRegistry).

The timelock field means "the timelock that administers the registries of this chain once the handover is recorded". On Sepolia it was written when the timelock was deployed (commit a721863); whether every registry has actually accepted it must be read from the chain (see Governance).

Example: Sepolia​

This is the real file src/contracts/v03/deployment.11155111.json as of 2026-10-24.

{
"schemaVersion": 2,
"network": "sepolia",
"chainId": 11155111,
"activeSet": "0.3.0",
"timelock": "0x33a22Cc4D86A62Ae141349B768C054b1DD794f33",
"sets": [
{
"label": "0.3.0",
"deployedAt": "2026-10-13T08:23:03.914Z",
"deploymentBlock": 11904844,
"abiVersion": "0.3.0",
"contracts": {
"IssuerRegistry": "0xf401fDe78884bd2a1809BC479Bcb67e0217578fA",
"CredentialRegistry": "0x53Fb56822E507bAf5c0221D993108495CE1931ea",
"StatusRegistry": "0x59A4298f3aACb6E5EA001C5a94DA7DE731bcB54E",
"HolderRegistry": "0x58659C14c955B31B442f23822ae82d37961423d3",
"RecordTypeRegistry": "0x1ce73AF9DF31935d7F7231217a64660030670b3B"
}
},
{
"label": "0.5.0",
"deployedAt": "2026-10-17T07:58:53.131Z",
"deploymentBlock": 11932876,
"abiVersion": "0.5.0",
"contracts": {
"IssuerRegistry": "0x5c1cc061675BF1b83e16102F074c3f2c444bf94a",
"CredentialRegistry": "0x1969950754144Af2ecEAa3A78F96e58Bf19D4297",
"StatusRegistry": "0x5df854C1380c8A48DE158ee02480309b837554A2",
"HolderRegistry": "0x882817b7f50C3be9c878b1E1146589570eB5DF96",
"RecordTypeRegistry": "0xa93Cf8f90CbFBd326A929D808432c8CB7216bA93"
}
}
]
}

v0.3 manifest, schema 1​

Schema 1 is one set with its fields at the top level (schemaVersion, network, chainId, deployedAt, deploymentBlock, abiVersion, contracts). The parser reads it as a list of one set, labelled with its abiVersion, which is also the active set. A first local deployment (deployment.31337.json) and the example use it. A schema 1 file has no timelock.

Access gate manifest​

FieldTypeMeaning
schemaVersionnumber1
network, chainId, deployedAt, deploymentBlock, abiVersionAs above; abiVersion is 0.3.0-access.1 on Sepolia
credentialRegistryaddressThe one credential registry this gate serves
verifierCodeHashbytes32Code hash of the Semaphore verifier the deploy script checked against the pinned value
poseidonCodeHashbytes32Code hash of the Poseidon library, checked the same way
contractsobjectPoseidonT3, SemaphoreVerifier, Semaphore, CertichainAccessGate

The access features in the app appear only where this manifest exists and its credentialRegistry matches the credential's registry. See ZK access.

Legacy v0.2 record​

blockchain/deployments/sepolia.json records the v2.0.0 deployment (RecordStorage, AcademicRecords, StudentManagement, the super admin, a timestamp). The v0.2 frontend does not read it: it takes the AcademicRecords address from NEXT_PUBLIC_CONTRACT_ADDRESS. The older blockchain/deployment-info.json (2025) is kept as history. See Legacy v0.2 contracts.

How the files are read​

FunctionUse
loadManifestSets(chainId)Imports ./deployment.<chainId>.json, parses it and checks the chain id. Returns null only when no file exists (the app then says it is not deployed on this network); any other problem throws.
loadManifest(chainId)The active set only
parseManifestSets(raw)Validates schema 1 or 2; errors read "Invalid deployment manifest: ..."
findSetByRegistry(sets, registry)The set that owns a CredentialRegistry, any letter case, or null
addSet, withActiveSet, withTimelockReturn a new manifest object; the scripts then write it with serializeManifestSets (two-space JSON, trailing newline)

The browser picks the chain from NEXT_PUBLIC_NETWORK_ID, so a build serves exactly one chain. Because the file is bundled, a manifest change (a new set, a new active set) reaches users only after a rebuild.

Who writes them​

Script (in blockchain/)Change
deploy:v03:local, deploy:v03:sepoliaWrites a new manifest, or with ADD_SET=1 adds a set (and with ACTIVATE_SET=1 makes it active). A plain deployment never replaces a public chain's file.
deploy:timelock:sepoliaRecords timelock after reading the roles back from the chain
set:activate:sepoliaRewrites activeSet after the readiness check; sends no transaction
deploy:access:local, deploy:access:sepoliaWrites the access gate manifest

Every write is a reviewed commit, so the history of the files is the history of what the app trusts.

Source files​

  • src/contracts/v03/manifest.ts, src/contracts/zk/manifest.ts
  • src/contracts/v03/deployment.11155111.json, src/contracts/zk/deployment.11155111.json, the two deployment.example.json files (local deployment.31337.json files are gitignored)
  • blockchain/scripts/lib/record-deployment.ts, blockchain/scripts/deploy-v03.ts, blockchain/scripts/deploy-timelock.ts, blockchain/scripts/activate-set.ts, blockchain/scripts/deploy-access-gate.ts
  • src/services/credentials/credential-gateway.ts, src/services/credentials/access-gateway.ts

Tests​

  • src/contracts/v03/manifest.test.ts, src/contracts/zk/manifest.test.ts
  • src/services/credentials/credential-gateway.test.ts