CellScript - A DSL for Cell-Based Contracts

Hi Ckromer,

Spot on. When I started this ‘doppelganger’ CellScript project, I actually had not found the older cell-labs/cell-script project. I’ve summed up a bit and organised a table for comparison

Topic Older cell-labs/cell-script This CellScript
Implementation Go Rust
Style More like a general smart-contract language CKB-first verifier DSL
Entry model func main() selected action or lock
Main focus Easier contract programming Explicit Cell transitions and verifier obligations
Core concepts General program logic resource, shared, receipt, action, lock, witness, lock args, metadata

To answer your questions,

Firstly, for .cell files, there are two different ‘entry’ concepts.

In Cell.toml:

entry = "src/main.cell"

means the source entry file for the package.

The actual contract entry compiled into a CKB artifact is an action or a lock. At this stage, I recommend selecting it explicitly:

cellc examples/nft.cell --target riscv64-elf --target-profile ckb --entry-action transfer
cellc examples/nft.cell --target riscv64-elf --target-profile ckb --entry-lock nft_ownership

The compiler then generates a _cellscript_entry wrapper. That wrapper decodes witness data or Script.args according to the entry ABI, then dispatches to the selected action or lock.

There are fallback rules if no entry is specified: prefer action main, then the first zero-arg action, then the first action, then the first lock. But for real contract artifacts, explicit entry selection is much clearer.

Secondly, compared with pure Rust CKB contracts, CellScript is intentionally more constrained.

Rust gives full low-level freedom: arbitrary syscalls, custom parsers, custom data structures, and manual transaction scanning. CellScript narrows that surface so the compiler can understand the contract shape. Cell state changes go through explicit primitives like consume, create, destroy, named outputs, read, protected, witness, and lock_args.

That trade-off is deliberate:

Rust CKB contract CellScript
Maximum freedom More structure
Manual tx parsing Explicit Cell inputs / outputs
Custom low-level logic Compiler-visible verifier obligations
Easy to express anything Easier to audit specific Cell transitions
Developer controls everything Compiler can reject more unsafe or ambiguous patterns

So I would not so far describe CellScript as functional, at least not purely so:

It supports ordinary imperative local code: let mut, loops, match, helper functions, and local vectors. But at the contract boundary it is deliberately verifier-oriented:

  • action describes a proposed Cell transition;
  • lock describes a spend predicate;
  • ordinary fn helpers are intentionally effect-free: they may compute values, validate data, transform local structs, or share reusable logic, but they cannot directly perform Cell effects such as consume, create, destroy, read, or protected. Those operations must remain visible inside action or lock bodies, so the compiler and auditors can clearly see where the contract touches Cells.

The project is still at an early stage, and if anyone is interested, I would be very happy to have people test & break it, review the design, and suggest how it could fit better into the CKB developer workflow.

5 Likes

CellScript 0.14 Release Notes

CellScript 0.14 is the CKB semantic-completeness milestone. It exposes more of
CKB’s concrete transaction surface in source syntax, metadata, constraints, and
tooling while keeping authorization boundaries explicit.

0.14 adds/completes the following features:

  • Spawn/IPC verifier composition
  • typed CKB Source
  • WitnessArgs views
  • fixed-width lock_args binding
  • explicit sighash digest surface
  • TYPE_ID and outputs_data evidence
  • declarative since/time and capacity surfaces
  • a formal CKB target-profile ABI contract.

Highlights

CKB Source, Witness, And Lock Args

0.14 makes CKB data sources visible instead of hiding them behind ordinary
parameters:

  • source::input, source::output, source::cell_dep, source::header_dep,
    source::group_input, and source::group_output;
  • witness::raw, witness::lock, witness::input_type, and
    witness::output_type;
  • lock_args T for fixed-width typed decoding of the executing Script.args;
  • env::sighash_all(source) for an explicit CKB sighash digest surface.

Important boundary: lock_args Address, witness Address, and
env::sighash_all(...) do not create signer authority by themselves. Signature
verification remains explicit future work. There is no hidden signer derivation
from an Address value or parameter name.

Spawn/IPC Verifier Composition

0.14 adds bounded verifier reuse through CKB VM v2-shaped Spawn/IPC helpers:

  • spawn
  • wait
  • process_id
  • pipe
  • pipe_write
  • pipe_read
  • inherited_fd
  • close

Spawn targets must be static string literals or String constants. Metadata
records runtime-required CellDep or DepGroup obligations for the child verifier.
The type checker rejects statically visible file-descriptor use-after-close,
double-close, and unclosed fd paths for pipe() and inherited_fd(...).

Target Profile Contract

The CKB target profile now reports a structured ABI contract in metadata,
constraints, and cellc explain-profile ckb:

  • witness ABI;
  • lock args ABI;
  • Source encoding;
  • Spawn/IPC ABI;
  • since/time ABI;
  • CellDep and script reference ABI;
  • outputs / outputs_data ABI;
  • capacity floor ABI;
  • TYPE_ID ABI;
  • CKB tx version.

Metadata validation rejects mismatched profile ABI fields so release evidence
cannot silently drift from compiler policy.

outputs / outputs_data Boundary

CKB transactions keep Cell output metadata and Cell data in parallel arrays:

outputs[i]      = capacity, lock, type
outputs_data[i] = data bytes for the same output Cell

0.14 records each CellScript-created output’s index-aligned
outputs[i] -> outputs_data[i] binding and validates that those bindings are
present and consistent.

TYPE_ID And Script References

0.14 exposes TYPE_ID output plans and script reference evidence for CKB audit
tooling. constraints.ckb.script_references aggregates:

  • TYPE_ID script references;
  • Spawn/IPC CellDep or DepGroup targets;
  • read_ref CellDep references.

This keeps code_hash, hash_type, and args visible instead of treating a
source-level name as authority.

Dedicated accepted/rejected CKB transaction fixture matrices for TYPE_ID
continue paths, ScriptGroup shapes, and outputs_data negative cases remain
part of the later standard compatibility-suite track. 0.14’s release boundary
is metadata, tamper-validation, strict compilation, and production evidence for
the bundled examples.

Declarative Since/Time And Capacity Surfaces

0.14 adds profile-visible CKB policy helpers:

  • require_maturity
  • require_time
  • require_epoch_after
  • require_epoch_relative
  • with_capacity_floor(shannons)
  • occupied_capacity("TypeName")

with_capacity_floor(...) declares a type-level output-capacity floor. It is
not full capacity evidence: builders still must fund outputs, measure occupied
capacity, measure consensus transaction size, and keep acceptance reports.

Dynamic BLAKE2b Policy

Dynamic fixed-hash Blake2b is now part of the CKB profile surface:

let digest = hash_blake2b(input_hash)

hash_blake2b(input: Hash) -> Hash lowers to an executable RISC-V
Blake2b-256 helper using CKB’s ckb-default-hash personalization. The runtime
access is metadata-visible as CKB_BLAKE2B, and production acceptance covers it
through the real timelock.cell lock_id_commitment lock with valid and
invalid local CKB lock-spend transactions. Arbitrary byte-slice or resource
serialization hashing is still out of scope until its ABI is specified.

Examples And Tooling

0.14 adds language examples for:

  • Spawn/IPC delegate verification;
  • multi-step Spawn/IPC pipelines;
  • witness/source views;
  • TYPE_ID creation;
  • capacity/time policy;
  • canonical style using protected, lock_args, witness, require,
    field shorthand, and [].

LSP and the VS Code extension now cover the 0.14 surface with completions,
snippets, and highlighting for lock_args, CKB Source views, WitnessArgs
helpers, ckb::*, and env::sighash_all.

Verification

Targeted 0.14 gate:

cargo fmt --all
cargo check --locked -p cellscript
cargo test --locked -p cellscript --test v0_14 -- --test-threads=1
cargo test --locked -p cellscript --test examples -- --test-threads=1
cargo test --locked -p cellscript --test cli cellc_explain_profile_reports_ckb_v0_14_contract -- --test-threads=1
cargo test --locked -p cellscript --lib lsp -- --test-threads=1
./scripts/cellscript_0_14_scope_audit.sh
cd editors/vscode-cellscript && npm run validate
git diff --check

Roadmap example gate:

cargo run --locked -p cellscript -- explain-profile ckb --json
cargo run --locked -p cellscript -- constraints examples/language/v0_14_witness_source.cell --target-profile ckb
cargo run --locked -p cellscript -- examples/language/v0_14_delegate_verify.cell --target-profile ckb
cargo run --locked -p cellscript -- examples/language/v0_14_multi_step_pipeline.cell --target-profile ckb
cargo run --locked -p cellscript -- examples/language/v0_14_witness_source.cell --target-profile ckb
cargo run --locked -p cellscript -- examples/language/v0_14_ckb_type_id_create.cell --target-profile ckb
cargo run --locked -p cellscript -- examples/language/v0_14_capacity_time.cell --target-profile ckb
cargo run --locked -p cellscript -- examples/language/canonical_style.cell --target-profile ckb

Next Stage

With 0.14, CellScript is moving out of pure language exploration and into a near dev-preview testing track. The compiler now has enough CKB-native surface area to make the next question concrete: can developers not only write contracts, but inspect, prove, build, debug, and ship them with predictable evidence?

0.15: Scoped Invariants & Covenant ProofPlan

The 0.15 track is about making covenant logic explicit. Instead of hiding protocol behavior behind compiler recognizers, CellScript will model the real CKB questions directly:

  1. when does this verifier run?
  2. which cells does it cover?
  3. what transaction views does it read?
  4. what is checked on-chain?
  5. what remains a builder assumption?

The headline features are scoped aggregate invariants, first-class lock/type trigger semantics, explicit cell identity and TYPE_ID policies, policy-specific destruction, and a new ProofPlan layer that turns source-level intent into auditable obligations before IR and codegen. Protocol helpers like transfer, claim, settle,pools, and covenants should become inspectable stdlib proof macros rather than opaque compiler magic.

The goal is simple: make every serious contract property explainable before it is trusted.

0.16: Formal Semantics, Compatibility, and Production Tooling

The 0.16 track takes the 0.15 proof model and hardens it into quisi-production-grade assurance. It focuses on formal operational semantics, ProofPlan soundness checks, standard CKB compatibility suites, builder assumption schemas, transaction validation, deployment governance, and audit/debug tooling.

This is where the dev-preview story becomes much more interesting: cellc validate-tx, transaction solving, reproducible deploy plans, proof diffs, cycle profiling, tx tracing, audit bundles, and compatibility fixtures for standard CKB patterns such as sUDT, xUDT, ACP, Cheque, Omnilock-style locks, NervosDAO-style since/epoch flows, and TYPE_ID.

The ambition is not just ‘compile to CKB’. The ambition is a contract workflow where source code, ProofPlan, emitted RISC-V, metadata, builder assumptions, transaction fixtures, and audit evidence all line up.

3 Likes