The firewall checks your outputs, not your counterparties.
It prevents a protected cell from sending to blacklisted destinations — at consensus, inside the lock script — regardless of what your application code does.
Deployed to CKB testnet. Registry cell 0xa3dcb46f · All outpoints in notes/deployments/testnet.registry.json.
The firewall is an outgoing payment filter. It blocks a wallet cell from being spent in a transaction that sends to a blacklisted address. It does not block incoming payments, does not filter what appears in other inputs, and does not affect contracts the wallet interacts with.
Appropriate for:
- An AI agent that should never fund a known-malicious address, even if its runtime is compromised
- A wallet that wants a consensus-layer check on top of an off-chain pre-flight scan
- Any software that builds CKB transactions and wants to be unable to produce outputs to blacklisted destinations
Not for:
- Blocking association with a blacklisted entity in general
- Blocking incoming payments from blacklisted sources
- Screening counterparties in DeFi or other multi-party contracts
┌────────────────────────────────────────┐
│ AI Agent / Wallet Runtime │
└───────────────────┬────────────────────┘
│ constructs transaction
▼
┌────────────────────────────────────────┐
│ Layer 1 — SDK Pre-flight │
│ Fast, synchronous check before sign │
│ Returns structured errors │
└───────────────────┬────────────────────┘
│ passes → sign and broadcast
▼
┌────────────────────────────────────────┐
│ Layer 2 — CKB Consensus Enforcement │
│ Every node validates the firewall │
│ lock; fails closed if dep is missing │
│ or output is blacklisted │
└────────────────────────────────────────┘
The SDK gives you a fast error before broadcast. The lock is what every node enforces — the SDK can be skipped by compromised code, the lock cannot.
npm install @ckb-firewall/sdkimport { fetchRegistryPayload, preflightCheck } from "@ckb-firewall/sdk";
// Fetch the live registry payload from your CKB node
// Use findRegistryCell() if you want to auto-discover the current outpoint
const registry = await fetchRegistryPayload(
"https://testnet.ckb.dev",
"0xa3dcb46fdeb92735e7f9f0393811a8541b71e275e8f713e62ea35f59746c78a8",
0
);
// Check outputs before signing — synchronous, no RPC calls
const result = preflightCheck([{ lockArgs: "0x..." }], [registry]);
if (!result.ok) {
throw new Error(`Blocked: ${result.reason}`); // e.g. "BlacklistedLockArgs"
}See sdk/typescript/ for the full API including buildFirewallLockScript for constructing firewall-protected lock scripts.
[dependencies]
ckb-transaction-firewall-sdk = "0.3"use ckb_transaction_firewall_sdk::{check_transaction, FirewallConfig, RegistrySpec, HashType};
let cfg = FirewallConfig {
registries: vec![RegistrySpec {
code_hash: [/* blacklist_registry code hash */0u8; 32],
hash_type: HashType::Type,
type_id_value: [/* bytes 34-66 of registry type args */0u8; 32],
required: true,
}],
};
// Build your transaction, pass the live registry cell as a dep
match check_transaction(&cfg, &tx, now_secs) {
Ok(()) => { /* safe to sign */ }
Err(e) => eprintln!("blocked: {} (code {})", e, e.code()),
}See sdk/rust/ for the full API.
npm install -g @ckb-firewall/cli
ckb-firewall inspectCheck whether an address is blacklisted:
ckb-firewall check --lock-args 0xabc123...Full governance flow:
# 1. Create a proposal
ckb-firewall propose
# 2. Anchor the proposal on-chain — starts the 72h review window clock
# (treasury-funded, keyless; or with --to-address for non-treasury deployments)
ckb-firewall anchor --proposal <id>
# 3. Export and share with governance participants
ckb-firewall export --proposal <id> --out proposal.json
# 4. Each participant imports and votes with their private key
ckb-firewall import proposal.json
ckb-firewall vote --proposal <id> --vote yes
# 5. Execute on-chain after review window (72h) and vote threshold (3/5)
ckb-firewall execute --proposal <id>For multi-registry deployments or self-managed registries, see Private registry.
Address migration. Using the firewall lock gives you a different lock script and therefore a different CKB address. Existing UTXOs must be migrated to the new address.
Temporary entries require header deps. If a blacklist entry has an expiresAt timestamp, the spending transaction must include header_deps so the firewall can read the chain's median time. Omitting header deps causes time-based entries to behave as permanent — the transaction does not fail with a clear error, it just silently enforces the wrong policy.
Registry updates invalidate in-flight transactions. When a governance update is confirmed, the old registry cell is consumed. Any pending user transaction that references the old cell as a dep will fail at the miner level. Governance updates should be announced and submitted at low-traffic periods.
| Contract | Tx | Index |
|---|---|---|
governance-lock |
0x5033e680... |
0 |
firewall-lock |
0x128193cc... |
0 |
blacklist-registry |
0xa165e5af... |
0 |
proposal-anchor |
0x0daff588... |
0 |
spawn-aware-secp256k1 |
0x0fe5d476... |
0 |
| Registry cell | 0xa3dcb46f... |
0 |
Full outpoints and Type IDs: notes/deployments/testnet.registry.json.
Real entries confirmed on the testnet registry as of 2026-06-02:
| Lock args | Added by | Status |
|---|---|---|
0xababababababababababababababababababababab |
proposal 7a3ebccd |
active |
0x3f54dea35bcc7a0efef541d361799f77bd1b858 |
proposal dbf110bb |
pending execution (review window) |
0x9888a8a74df4e0ce82e7a4604f8fd403fd4622ca |
proposal dbe06fc7 |
pending execution (review window) |
Protects against: sending to blacklisted lock/type args at consensus — regardless of what application code does.
Does not protect against: addresses not yet on the list; non-address exploit classes; cells that do not use the firewall lock; governance key compromise.
Fail-closed: missing, invalid, or ambiguous registry dep → reject. See Two-layer model.
https://ckb-firewall.drreamer.digital
Concepts
| Why this exists | What the firewall does and does not do |
| Two-layer model | Lock, registry, SDK, and governance |
| Governance design | How registry updates are authorised |
| Security model | What is and isn't protected |
How-to guides
| Pre-flight check (TypeScript) | Fetch the registry and reject blacklisted outputs before signing |
| Pre-flight check (Rust) | In-process blacklist check with no network dependency |
| Build a firewall lock script | Wrap a secp256k1 wallet cell with the firewall lock |
| Create a proposal | Full governance lifecycle — propose, anchor, vote, execute |
| Use the governance GUI | Browser dashboard for governance operations |
| Deploy a private registry | Multi-registry deployment and self-managed blacklists |
Reference
| TypeScript SDK API | All types, classes, and functions |
| Rust SDK API | All types and functions |
| CLI reference | All commands and options |
| BLKL format | Binary registry payload layout |
| Firewall lock args | Lock script args encoding |
| GOV1 witness | Governance witness format |
| Error codes | All SDK and on-chain error codes |
| Testnet deployment | Live outpoints and registry values |
| Glossary | Key terms and definitions |
The Transaction Firewall is the enforcement floor for the CKB Agent Control Hub. The Control Hub defines what an agent may do; this enforces what no agent may do at listed destinations when the firewall lock is in use.
MIT
