Skip to main content

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.

RoleWhereCan doCannot do
AdminIssuerRegistry and StatusRegistry of a setaddIssuer, 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 delayChange the admin instantly; act as an issuer
Guardian (GUARDIAN_ROLE = keccak256("CERTICHAIN_GUARDIAN"))IssuerRegistrypause(), 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 registriesHolds the admin role directly, or proposes and cancels on the timelockSkip the timelock delay once the timelock is admin
Timelock (CertichainTimelock)Separate contractBecomes the admin; runs an action only after its delayAct without a scheduled operation
AnyoneExecute 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 pausedNever stopped
Issuing a credentialRevoking, suspending, reinstating, superseding
Accepting or re-linking an invitationUnlinking a wallet, cancelling invitations
Registering a record typeSwitching 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:

  1. The institution completes the onboarding playbook (docs/onboarding/) through chapter 3.
  2. An accreditor signs a sign-off from docs/governance/signoff-template.md. None is named yet, so every admission is a recorded exception.
  3. The operator proposes addIssuer with SIGNOFF_FILE=<path>, which makes the operation salt the hash of the sign-off file. The operation on chain is then tied to that file.
  4. The sign-off and a log entry are published before the Safe schedules.
  5. 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:sepolia reports whether a sign-off matches a scheduled, executed or cancelled admission.
  • npm run governance:log:check checks every log entry in docs/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:

  1. The current admin calls beginDefaultAdminTransfer(newAdmin) on both registries.
  2. The admin delay passes (3 days on the 0.3.0 Sepolia registries; 3,600 seconds on the 0.5.0 rehearsal set).
  3. The nominee calls acceptDefaultAdminTransfer() on both. Until then the current admin can cancelDefaultAdminTransfer().

The scripts:

Script (in blockchain/)DoesSends
handover:v03:sepoliaNominates the Safe on both registries (refuses a wrong chain, the zero address, a non-Safe target)Transactions from the deployer key
timelock:handover:sepoliaReads 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:sepoliaFor 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:sepoliaRead-only status of every registry: admin, pending admin, delay, timelock roles; warns if two registries differNothing

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:

ItemValue
Safe0x9cdd688d0e7682C6aBC8691425003Ef9F23fC288 (Safe 1.5.0, 2 of 3)
Guardian0xc389b2830680980fa24b5A78e2f20fc322D64667
Timelock0x33a22Cc4D86A62Ae141349B768C054b1DD794f33, delay 3,600 seconds, the Safe as proposer and canceller, open executor, no admin
Active set0.3.0
SetRegistries' adminNotes
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).

Check the chain, not this page

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​

  • /admin reads 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 to docs/process/runbooks/admin-keys.md.
  • Every page and server route that needs to know whether an account is an issuer asks the IssuerRegistry of the relevant set (isIssuer and not retired).
  • The VersionAnnouncer is 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 /admin covers 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.sol
  • blockchain/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.ts
  • src/app/admin/GovernancePage.tsx, GovernancePanel.tsx, TimelockSection.tsx, RetiredKeysSection.tsx
  • src/services/domain/platform-governance-service.ts, src/services/domain/issuer-access-service.ts
  • src/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