CellScript - A DSL for Cell-Based Contracts

CellScript 0.22.0 Release Note

CellScript 0.22 is out.

You can install it with a single command, but the more important story lies underneath. Verification logic that once lived in strings, conventions, or implicit assumptions is now typed, finite, and explicit. Every proof obligation identifies who is responsible for discharging it, while the release gate binds clean source, pinned CKB source and binary provenance, generated artefacts, and committed transaction evidence into a single, auditable hand-off.

The CellScript compiler and toolchain also now include preliminary integrations for RGB++, Spore, and Fiber, together with working examples for each.

Install

curl -fsSL https://raw.githubusercontent.com/CellScript-Labs/CellScript/main/scripts/install.sh | sh

(4 platform binaries + install.sh + SHA256SUMS on the GitHub release.)

Source builds and contributors need Rust 1.97.1 — all in-tree crates moved to Edition 2024 this cycle:

git clone --recurse-submodules https://github.com/CellScript-Labs/CellScript.git
cd CellScript
cargo install --locked --path .

1. Verification logic is now typed, finite, and explicit

The 0.22 language line (nightly-0.22) makes a class of intent that 0.21 recorded as metadata into something the compiler and the generated verifier actually check. The headline additions:

  • Typed read-only transaction viewsInputView<T>, OutputView<T>, CellDepView, HeaderDepView, WitnessArgsView, OutPoint, ScriptView. Their metadata records source, ownership, the absence of lifecycle authority, and the checked-static / checked-runtime evidence. The old source::* functions stay as the explicit low-level migration surface.
  • Finite invariant quantifiersforall <role> <binding> in <source_view<T>> { require ... } and count(<source_view<T>> where ...). Closed aggregate target, unbounded and impure bodies rejected.
  • Source-aware bounded collectionsinput-qualified BoundedCellSet<CellType, N> discharged by consume_each, witness-qualified fixed-width BoundedList<Plan, N> driving create_each. Generic Vec<Resource> stays rejected.
  • A closed, versioned capability algebra — no inheritance syntax; destroy derives exactly consume + burn, replace_unique requires replace plus the type’s exact declared identity policy. Schema 51 records per-type capability-set version and required/provided/entailed/missing proofs, and rejects transitive authority from container-like resources.
  • Concrete fixed-width payload enums — constructor calls, exhaustive destructuring, packed one-byte-tag layouts, arm-local linear Cell ownership, and a pure-helper register-pair return ABI up to 16 bytes. Dynamic, recursive, and generic payloads still fail closed.
  • Canonical type validity blocks — pure field predicates lower to fail-closed checks before selected create/constructor instructions. The only approved environment read is env::block_number(), recorded as an explicit builder-evidence-required header-dep obligation, because CKB-VM has no ambient tip-height syscall. Unknown env::*, transaction-view reads, and lifecycle syntax inside validity are rejected.
  • Compile-time-only borrow root as view { ... } regionsView<T> access to linear Cells with no layout, storage, serialization, or ABI representation. Escape, root lifecycle crossing, and calls outside Pure/ReadOnly helpers fail closed.
  • Enum-backed flows with one initial state, explicit terminal states, and checked terminal-by-output-state evidence; checked casts; transitive callable-effect checking; and deterministic participant-role candidates in ProtocolGraph metadata (selected source published, every candidate published, authorization_proven = false, roles intentionally absent from ProofPlan).

If you’ve been encoding these patterns in witness conventions or action-name-specific comments, you can stop. And the compiler still fails closed on dynamic or recursive payload ADTs, unbounded resource iteration, authority borrowed from a container, and escaping borrow views.

2. Every proof obligation now says who has to discharge it

This is the change that matters most for auditors. Every ProofPlan record now identifies exactly one of six evidence tiers:

Tier Who or what must discharge it
checked-static Compiler or static analysis
checked-runtime Generated verifier code
runtime-helper-required A known helper the selected artifact hasn’t emitted
builder-evidence-required Transaction builder or indexer
metadata-only Audit metadata with no executable enforcement
chain-evidence-required Dry-run, tx-pool, commit, capacity, or cycle evidence

--production rejects enforcement-like claims that remain metadata-only. It does not silently promote builder or chain obligations into compiler proof — a successful compile is useful evidence, but it is not by itself proof that a transaction can be built, accepted, or committed.

The hand-off is now explicit:

3. --json is the one machine interface, and diagnostics have stable codes

The CLI ergonomics pass consolidates what 0.21 hinted at:

  • --json is the canonical machine-output switch. Success and failure emit exactly one JSON document on stdout. The hidden --message-format=json spelling stays temporarily for compatibility.
  • Stable E2xxx backend diagnostics. JSON diagnostics carry the code, name, description, and recovery hint; cellc explain E2202 --json exposes the same registry. LSP diagnostics carry the standard code field plus a codeDescription link.
  • Unified run metrics. cellc run --json output for VM and simulator now shares one schema, using null when cycles or steps are unavailable.
  • Unicode source snippets rendered by terminal width, exit codes classified, error causes preserved, core command rendering centralised, and MCP documentation reads made UTF-8-safe.

The VS Code extension ships from the editors/vscode-cellscript submodule with grammar, snippets, hover, completion, and validation coverage for the new syntax, still delegating semantic decisions to cellc. cellscript-mcp remains a read-only compiler/documentation interface — not a second compiler or deployment client. And the website’s provenance and assurance snapshots are regenerated from the 0.22 compiler output; they no longer show stale 0.17 versions.

4. New CKB helpers, with explicit trust boundaries

New runtime helpers land this cycle, each with a deliberately narrow, documented scope:

  • Exact-index and literal-bounded resolved-CellDep data-hash checks. The bounded scan accepts only a literal maximum in 1..=64, uses the real LOAD_CELL_BY_FIELD(DATA_HASH) syscall path, stops on INDEX_OUT_OF_BOUND, and fails with stable runtime code 63 when absent. CKB-VM sees resolved CellDeps; out points, dep types, and original DepGroup identity remain builder or manifest evidence.
  • Fixed-width SHA-256 and SHA256d for 32-byte values and 64-byte pairs, plus a SHA256d Merkle verifier bounded to 16 siblings. Rust reference vectors and positive/negative CKB-VM tests cover the generated RISC-V. This is explicitly not a Bitcoin SPV implementation.
  • verifier::btc::bip340::require_signature_from_cell_dep for an explicit literal CellDep index (the index-0 spelling is retained for compatibility), with a fixed 144-byte VM2 IPC envelope. It verifies only the supplied prehash. The caller still owns message-domain construction, ScriptGroup/WitnessArgs and sighash selection, key authority, replay policy, deployment pinning, and external verifier review.

See the signature verifier ABI.

5. Bundled contracts now model real Cell identities and asset settlement

The examples stopped treating identifiers or witness values as settlement:

  • AMM pools bind both token TypeHashes and derive initial LP supply geometrically;
  • NFT sales consume and relock typed Token payments;
  • timelocks and atomic swaps release actual Token outputs;
  • DAO votes lock and redeem voting Tokens;
  • vesting declares an Active -> Active self-loop for repeatable partial claims, with Active -> FullyClaimed as the terminal transition;
  • field-preserving N-input/N-output resource permutations are checked as runtime conservation — closing the strict ProofPlan gap for NFT royalty/seller payment pairs without action-name-specific backend rules.

The bundled multisig.cell example now says what it actually proves. Its Approval records are explicitly non-cryptographic; discarded 64-byte signature payloads are gone; witness time is labelled as reported rather than chain time; and real signer authentication, sighash binding, WitnessArgs layout, replay policy, and verification belong in an explicit Lock Script or pinned verifier package. The README no longer describes nonexistent CKB signature syscalls. The production example matrix is now 43 business actions and 17 locks, and pure AMM helpers are no longer exposed as transaction entries.

6. Fiber, Spore, and RGB++ — narrow scopes, honestly labelled

Three ecosystem paths land this cycle, each inside a deliberately narrow boundary:

  • Fiber. The new cellscript-fiber-adapter crate derives a dedicated fungible-type-group-v1 artifact and native Fiber UDT configuration from compiler, deployment, live-Cell, and node evidence — without a Fiber profile or a fiber-lib dependency. The executable boundary is narrow: exact 16-byte little-endian u128 data, full Type Script group conservation, closed issuance/destruction authority formats, and rejection of unauthorised mint/burn while ignoring Fiber’s xUDT-compatible witness prefix. Bounded local-devnet runs covered Fiber’s official multi-hop UDT payment and pending-TLC watchtower force-close collections. The clean, pinned full lifecycle/negative matrix is still pending, so this is not a production-readiness claim. Multi-asset packages select one structurally eligible asset with cellscript-fiber ... --asset <Type>. See the Fiber operator guide.
  • Spore and RGB++. Compile-checked identity-adapter packages under examples/ecosystem/ bind exact CKB Script identities and transaction positions while deliberately leaving Spore rules, RGB++ commitments, Bitcoin validation, witnesses, confirmations, and orchestration to pinned protocol packages and builders. See the interop boundary guide.

Try it

NOTE

CellScript 0.22 currentyly does not claim production Fiber readiness without the pinned external lifecycle matrix, builder/capacity/tx-pool/commit/live-chain evidence from compiler-only ProofPlan tiers, dynamic/recursive/generic payload ADTs, unbounded collection iteration, transaction-view reads inside type validity predicates, consensus-checked TemplateLayout commitments, canonical AST/IR receipt hashes, or production Spore/RGB++/Bitcoin-SPV/external-BIP340-verifier assurance without their pinned packages and independent evidence.

If something regresses, open an issue or post on the Nervos Talk thread.

6 Likes