Skip to content

How to Deploy a Private Registry

For a step-by-step tutorial with visible results at each step, see Get started: registry operator.

This page covers the full deployment with all options and explains how each component connects to the others.

Terminal window
./scripts/deploy.sh \
--network testnet \
--rpc-url https://testnet.ckb.dev \
--from-address <YOUR_CKT1_ADDRESS> \
[--no-build] # skip cargo build if binaries are fresh
[--dry-run] # generate txs without signing

Outputs:

  • deploy/info.json — deployment metadata, Type IDs, tx hashes
  • deploy/migrations/ — migration files for ckb-cli deploy apply-txs

To reuse the community contract binaries without redeploying, use the community Type IDs directly and skip this step. Your registry will have a unique type_id_value from your bootstrap transaction regardless.

The bootstrap creates both the registry cell and the treasury seed in a single transaction. Run:

Terminal window
node scripts/build_treasury_bootstrap_tx.mjs deploy/bootstrap-tx.json

Then sign and submit:

Terminal window
ckb-cli --url https://testnet.ckb.dev tx sign-inputs \
--tx-file deploy/bootstrap-tx.json \
--add-signatures
ckb-cli --url https://testnet.ckb.dev tx send \
--tx-file deploy/bootstrap-tx.json

Record the bootstrap transaction hash. This is the initial --registry-tx you pass to all CLI commands. As governance updates run, the registry cell moves — the current outpoint is auto-discovered via the indexer.

The treasury-lock contract can only be spent when the transaction produces a valid proposal-anchor cell or returns capacity to itself. What “valid” means is encoded in the treasury-lock args:

treasury-lock args = governance_lock_type_id (32 bytes)
| proposal_anchor_type_id (32 bytes)

These must match the contracts you deployed in step 1. The bootstrap computes the treasury address from these IDs and embeds the full treasury lock script in the registry cell’s v3 governance header.

This means the CLI and GUI automatically discover the treasury address by reading the registry cell — no extra configuration is needed after bootstrap.

See How to deploy and seed the treasury for treasury management details.

The CLI defaults to the canonical testnet registry. To use your private registry, pass flags to every governance command:

Terminal window
ckb-firewall propose ... --registry-tx <tx> --registry-index 0
ckb-firewall anchor ... --registry-tx <tx> --registry-index 0
ckb-firewall vote ... --registry-tx <tx> --registry-index 0
ckb-firewall execute ... --registry-tx <tx> --registry-index 0
ckb-firewall inspect ... --registry-tx <tx> --registry-index 0
ckb-firewall check ... --registry-tx <tx> --registry-index 0

The GUI is started with:

Terminal window
ckb-firewall gui \
--registry-tx <bootstrap tx hash> \
--registry-index 0

Connect the TypeScript SDK to your registry

Section titled “Connect the TypeScript SDK to your registry”

Use RegistrySpecLike to find the live cell via the indexer, rather than hardcoding an outpoint:

import { findLiveRegistryCell } from "@ckb-firewall/sdk";
const registrySpec = {
// The code hash of your deployed blacklist-registry contract
// (from deploy/info.json under blacklist-registry.code_hash)
codeHash: "<blacklist-registry code hash>",
hashType: "type" as const,
// The type_id_value printed by `ckb-firewall inspect`
// or found in deploy/bootstrap-tx.json under registryCell.typeIdValue
typeIdValue: "<your registry type_id_value>",
required: true,
};
const cell = await findLiveRegistryCell(rpcUrl, registrySpec);

typeIdValue is the stable 32-byte identifier computed at bootstrap time. It never changes across governance updates, so you can embed it in your application and SDK configuration permanently.

If you are using the community contract binaries (not redeploying), use the community codeHash and only supply your own typeIdValue.

See Step 4: Run a governance round for a complete worked example.

See Step 5: Configure a firewall lock and How to build a firewall lock script.