Governance
Governance in Certichain means the small set of powers that sit above issuers: registering and removing institutions, stopping the system in an emergency, lifting suspensions, and changing who holds those powers. All of it lives on chain in two contracts of each contract set, and all of it is public.
The concepts (roles, multisig, timelock) are introduced in Access control and governance. The contracts are in Issuer registry, Status registry and Timelock. The people are in the Admin and Guardian role pages.
The powers and who holds them
Each v0.3 contract set has two contracts with an admin role (DEFAULT_ADMIN_ROLE, OpenZeppelin AccessControlDefaultAdminRules): the IssuerRegistry and the StatusRegistry. The other registries read paused() and issuer standing from the IssuerRegistry.
| Role | Where | Can do | Cannot do |
|---|---|---|---|
| Admin | IssuerRegistry and StatusRegistry of a set | addIssuer, removeIssuer, setIssuerName, retireIssuer (0.5.0 sets), unpause, lift an issuer suspension, put an admin hold on a single credential, grant and revoke the guardian role, nominate the next admin, change the admin delay | Change the admin instantly; act as an issuer |
Guardian (GUARDIAN_ROLE = keccak256("CERTICHAIN_GUARDIAN")) | IssuerRegistry | pause(), suspend an issuer at once (setIssuerSuspended(issuer, true, reason)), and on 0.5.0 sets also unpause() | Lift a suspension, register issuers, grant roles, touch a single credential |
| Safe (2 of 3 multisig) | Off the registries | Holds the admin role directly, or proposes and cancels on the timelock | Skip the timelock delay once the timelock is admin |
Timelock (CertichainTimelock) | Separate contract | Becomes the admin; runs an action only after its delay | Act without a scheduled operation |
| Anyone | Execute a timelock operation whose delay has passed (open executor) | Schedule or cancel |
isGuardian(account) is true for a guardian and for the admin. The admin role holder is a contract (the Safe or the timelock), so in practice the guardian key is what presses the pause button.
Before the timelock takes over a set, the Safe sits where the timelock is in this picture and its admin calls take effect at once.
What a pause stops
A pause stops new things only. Contract tests pin the classification (PauseClassification.test.ts).
| Stopped while paused | Never stopped |
|---|---|
| Issuing a credential | Revoking, suspending, reinstating, superseding |
| Accepting or re-linking an invitation | Unlinking a wallet, cancelling invitations |
| Registering a record type | Switching a type off |
| Every read and every verification |
Flow 1: emergency pause
The guardian opens /admin (screen Governance, section Platform governance). The page opens only for an account where isGuardian is true; others are sent to /login. It shows Running or Paused, the Admin, the Admin transfer delay, any Pending admin transfer, the Timelock section and Retired issuer keys. While the system runs, a guardian sees Emergency pause with the button Pause the system.
Restarting is an admin action on 0.3.0 registries: the Safe (or, after the handover, the timelock with its delay) calls unpause(). On 0.5.0 sets the guardian may also unpause. If the guardian key may be compromised, the admin runbook asks for one batch that revokes the guardian role, unpauses and lifts suspensions, so a stolen key cannot pause again between steps.
Flow 2: admin actions through the timelock
Once a set is under the timelock, every admin action is scheduled first, waits the delay, then anyone runs it. The Safe can cancel in between. The delay is the notice period in which institutions, observers and the monitor can see what is coming.
npm run timelock:propose:sepolia (in blockchain/) writes the Safe batch files and prints the operation id, salt and earliest execution time. It sends nothing.
TARGET=IssuerRegistry FUNCTION=addIssuer ARGS='["0x<issuer>","Institution name"]' npm run timelock:propose:sepolia
Targets are IssuerRegistry, StatusRegistry (of the active set, or the set named by SET=<label>) and Timelock. Files go to blockchain/timelock-files/<chain>/ as <id>-1-schedule.json, <id>-2-execute.json and <id>-cancel.json, ready for the Safe Transaction Builder.
The governance screen lists operations scheduled in the last 50,000 blocks that are still waiting or ready (Queued admin actions), soonest first. If the queue cannot be read it says "Could not read the queue. Check the timelock on the block explorer." The monitor reports every timelock event (see Monitoring).
Admitting an institution under the charter
docs/governance/charter.md (draft 0.1, not adopted as of 2026-10-24) adds rules on top of the contracts:
- The institution completes the onboarding playbook (
docs/onboarding/) through chapter 3. - An accreditor signs a sign-off from
docs/governance/signoff-template.md. None is named yet, so every admission is a recorded exception. - The operator proposes
addIssuerwithSIGNOFF_FILE=<path>, which makes the operation salt the hash of the sign-off file. The operation on chain is then tied to that file. - The sign-off and a log entry are published before the Safe schedules.
- After the delay anyone executes; the operation id goes into the log.
Two read-only checks support this:
INSTITUTION=0x... SIGNOFF_FILE=<path> npm run admission:check:sepoliareports whether a sign-off matches a scheduled, executed or cancelled admission.npm run governance:log:checkchecks every log entry indocs/governance/log/.
Flow 3: handing the admin role over
The admin role moves in two steps with a delay, so a mistaken or hostile nomination can be cancelled:
- The current admin calls
beginDefaultAdminTransfer(newAdmin)on both registries. - The admin delay passes (3 days on the 0.3.0 Sepolia registries; 3,600 seconds on the 0.5.0 rehearsal set).
- The nominee calls
acceptDefaultAdminTransfer()on both. Until then the current admin cancancelDefaultAdminTransfer().
The scripts:
Script (in blockchain/) | Does | Sends |
|---|---|---|
handover:v03:sepolia | Nominates the Safe on both registries (refuses a wrong chain, the zero address, a non-Safe target) | Transactions from the deployer key |
timelock:handover:sepolia | Reads the chain, says which step is next and writes the Safe batch for it (handover-1-begin.json, handover-2-schedule.json, handover-3-execute.json) | Nothing |
timelock:nominate:sepolia | For a set deployed after the handover: the deployer nominates the timelock on that set's registries (SET=<label>) | Transactions from the deployer key |
governance:v03:sepolia | Read-only status of every registry: admin, pending admin, delay, timelock roles; warns if two registries differ | Nothing |
Every script that sends a transaction to Sepolia needs the human's go-ahead (project rule).
Sepolia state as of 2026-10-24
From src/contracts/v03/deployment.11155111.json, the decision records (ADR-0060, ADR-0092) and docs/progress/PROGRESS.md:
| Item | Value |
|---|---|
| Safe | 0x9cdd688d0e7682C6aBC8691425003Ef9F23fC288 (Safe 1.5.0, 2 of 3) |
| Guardian | 0xc389b2830680980fa24b5A78e2f20fc322D64667 |
| Timelock | 0x33a22Cc4D86A62Ae141349B768C054b1DD794f33, delay 3,600 seconds, the Safe as proposer and canceller, open executor, no admin |
| Active set | 0.3.0 |
| Set | Registries' admin | Notes |
|---|---|---|
0.3.0 (active) | The Safe. It accepted the admin role of the IssuerRegistry and the StatusRegistry in one batch on 2026-10-17. | The Safe nominated the timelock on both (batch executed 2026-10-17); the 3-day delay ended 2026-10-20T08:42:12Z. The repository has no record that the timelock's accept was scheduled or executed, so the handover of this set should be treated as not finished until governance:v03:sepolia shows otherwise. Admin actions here are sent from the Safe directly. |
0.5.0 (listed, not active) | The timelock, on both registries. | Deployed 2026-10-17 for the rehearsal. Two issuers were added and one key retired through scheduled Safe batches. Not active: the readiness check (set:ready:sepolia) blocks activation because the three test issuers of 0.3.0 are not registered in it. See Contract set upgrade. |
Because one set is not under the timelock, the governance screen would show the timelock badge Handover pending with "The handover is not finished: the registries are not yet administered by the timelock." The handover counts as finished only when the issuer and status registries of every listed set have the timelock as admin (administersEveryRegistry in src/services/domain/platform-governance-service.ts).
Governance state changes without code changes. Before relying on any of the above, run npm run governance:v03:sepolia in blockchain/ (read only) or open /admin with the guardian key.
What the app does and does not do
/adminreads state and sends one transaction: the guardian pause. It never sends an admin transaction. The note on the page says admin actions "run from the Safe with the Transaction Builder" and points todocs/process/runbooks/admin-keys.md.- Every page and server route that needs to know whether an account is an issuer asks the
IssuerRegistryof the relevant set (isIssuerand not retired). - The
VersionAnnounceris not moved by the handover script, because the manifest does not list it.
Limits
- A compromised pair of Safe signers can schedule a hostile action. The delay is the window to notice; cancelling needs two honest signers.
- Losing two of three Safe signers leaves the admin role stuck: queued actions still run, nothing new can be scheduled or cancelled. There is no recovery path.
- On 0.3.0 registries a restart after a pause waits for the admin path; only 0.5.0 sets let the guardian unpause.
- The queue on
/admincovers the last 50,000 blocks; older operations are on the explorer.
Source files
blockchain/contracts/v03/IssuerRegistry.sol,blockchain/contracts/v03/StatusRegistry.sol,blockchain/contracts/v03/CertichainTimelock.solblockchain/scripts/handover-v03.ts,governance-status.ts,deploy-timelock.ts,timelock-handover.ts,nominate-timelock.ts,timelock-propose.ts,admission-check.ts,governance-log-check.tssrc/app/admin/GovernancePage.tsx,GovernancePanel.tsx,TimelockSection.tsx,RetiredKeysSection.tsxsrc/services/domain/platform-governance-service.ts,src/services/domain/issuer-access-service.tssrc/contracts/v03/deployment.11155111.json,src/contracts/v03/manifest.ts- Handbook:
docs/process/runbooks/admin-keys.md,docs/process/runbooks/timelock.md,docs/governance/
Tests
- Contract tests under
blockchain/test/for the registries, the pause classification (PauseClassification.test.ts), the Safe handover and signer rotation (SafeGovernance.test.ts) and the timelock (see Contract tests) src/services/domain/platform-governance-service.test.ts,src/app/admin/GovernancePanel.test.tsx,src/app/admin/governance-format.test.ts- E2E:
e2e/journeys/pause-and-restart.journey.ts