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.

6 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.

3 Likes

CellScript 0.15 Release Notes

Release date: 2026-05-26.
Release tag: v0.15.0
GitHub release:
https://github.com/a19q3/CellScript/releases/tag/v0.15.0.

CellScript 0.15 is the scoped-invariant, Covenant ProofPlan, and verifier
soundness hardening release. It closes the known fail-open and
semantic-boundary bugs found during the hardening audit, makes verifier
triggers, scope, coverage, builder assumptions, and enforcement gaps explicit
in source and metadata.

Highlights

Scoped Invariant Syntax

0.15 adds first-class invariant declarations with explicit trigger, scope,
and reads:

invariant udt_amount_non_increase {
    trigger: type_group
    scope: group
    reads: group_inputs<Token>.amount, group_outputs<Token>.amount

    assert_sum(group_outputs<Token>.amount) <= assert_sum(group_inputs<Token>.amount)
}

Supported triggers: explicit_entry, lock_group, type_group.
Supported scopes: selected_cells, group, transaction.

Invariants are preserved through AST, type checking, IR, module metadata,
formatting, LSP symbols, hover/completions, docs, and scoped CKB entry
compilation.

Aggregate Invariant Primitives

0.15 adds scoped aggregate assertion primitives for common covenant-style
relations:

assert_sum(group_outputs<Token>.amount) <= assert_sum(group_inputs<Token>.amount)
assert_conserved(Token.amount, scope = group)
assert_delta(Token.amount, witness.delta, scope = selected_cells)
assert_distinct(outputs<NFT>.token_id, scope = transaction)
assert_singleton(Config.config_id, scope = group)

Aggregate fields must resolve to fixed-width integer or fixed-byte schema
fields. Dynamic tables, generic collections, and bool fields are rejected.
Non-literal assert_delta arguments must be bound through reads to
witness.* or lock_args.*, so the runtime delta has an auditable source.

Boundary: Aggregate primitives are currently metadata-only for automatic
aggregate verifier-loop lowering. They emit codegen_coverage_status: "gap:metadata-only" and status: "runtime-required" until a later lowering
pass proves them on chain. 0.15 now also cross-references declared aggregate
invariants against checked action obligations; matched obligations are reported
as bounded action coverage, while unmatched declarations remain visible and
gateable.

Covenant ProofPlan Metadata

0.15 adds a ProofPlan stage and cellc explain-proof audit surface.
Runtime, action, function, and lock metadata expose ProofPlan records with:

  • invariant name and source span
  • trigger, scope, reads, coverage
  • input/output relation checks
  • group cardinality
  • identity/lifecycle policy
  • builder assumptions
  • diagnostics and codegen coverage status
  • matched/unmatched invariant action coverage

cellc explain-proof prints trigger/scope/reads/coverage/on-chain status in
human-readable and JSON output.

ScriptArgs and lock_args provenance is reported under reads.lock_args,
not reads.witness; witness remains reserved for transaction witness data.

cellc check --deny-runtime-obligations rejects runtime-required ProofPlan
gaps, including declared invariants whose coverage is still metadata-only or
whose action coverage is unmatched.

Production and strict gates also reject records that claim checked runtime
coverage without executable evidence. Static or metadata-only details such as
checked-static do not populate executable runtime/codegen evidence.

Lock-group transaction risk diagnostics warn when a lock_group verifier
scans transaction-wide views, because only inputs sharing that lock trigger
the verifier.

Expression-local Unsigned Widening

0.15 defines a deliberately bounded coercion rule for primitive unsigned
integers. CellScript may widen u8 -> u16 -> u32 -> u64 -> u128 only inside
arithmetic and numeric comparison expressions.

This is not a general implicit numeric coercion feature. Assignment, return,
ABI, witness, create layout, struct field initialization, Molecule layout,
and serialization boundaries remain exact-type boundaries. Integer literals may
be context-typed by an expected primitive integer type, but non-literal values
must use an explicit cast at boundaries:

let total: u64 = amount_u64 + fee_u16 // accepted expression-local widening
let stored: u64 = fee_u16             // rejected boundary widening
let stored: u64 = fee_u16 as u64      // accepted explicit boundary cast

Compound assignment is a write boundary: target += rhs is valid only when
rhs is the same width as, or narrower than, target. Generic u128
arithmetic and ordering remain unsupported except for explicitly implemented
u128 delta and equality paths.

Cell Identity and TYPE_ID Lifecycle

0.15 promotes cell identity from a metadata annotation into a first-class
primitive policy:

resource Token has store {
    identity(ckb_type_id)
    amount: u64
}

Supported identity policies:

Policy Meaning 0.15 executable boundary
identity none No identity tracking (default, backward compatible) No identity verifier is emitted
identity ckb_type_id CKB TYPE_ID: derived from first input + output index create_unique requires a TYPE_ID output plan and reports global creation uniqueness as runtime-required; replace_unique preserves TypeHash
identity field(path) Fixed-width field identity within the data payload create_unique anchors the output field bytes and reports global uniqueness as runtime-required; replace_unique compares input/output field bytes
identity script_args Identity derived from the executing script args create_unique anchors the output LockHash and reports global uniqueness as runtime-required; replace_unique preserves LockHash
identity singleton_type Singleton type identity create_unique anchors the output TypeHash and reports singleton creation exclusivity as runtime-required; replace_unique preserves TypeHash

Identity-aware lifecycle forms:

// Identity-aware creation
let minted = create_unique<Token>(identity = ckb_type_id) {
    amount: 100
} with_lock(recipient)

// Identity-aware replacement (consumes input, preserves identity)
let updated = replace_unique<Token>(identity = ckb_type_id) old {
    amount: old.amount - 50
}

IrInstruction::CreateUnique and IrInstruction::ReplaceUnique carry
identity metadata through the full compile pipeline. TypeMetadata.identity_policy
exposes the policy in compiled JSON metadata (hidden when none).

replace_unique has the syntax
replace_unique<T>(identity = policy) input_cell { ... }; the input operand is
required because the verifier compares the consumed Cell with the replacement
output. It does not take a with_lock(...) clause.

For create_unique policies, 0.15 emits local runtime anchors for the created
output and records the full global uniqueness proof as runtime-required.
For ckb_type_id, the remaining boundary is the TYPE_ID builder plan. For
field-, script-args-, and singleton-type creation, global uniqueness remains a
builder/indexer responsibility outside the CKB-VM execution scope.

Explicit Destruction Policies

0.15 adds policy-specific destruction forms so the compiler and verifier know
what is being proved:

Form What it proves
destroy_singleton_type(cell) No output with the same TypeHash exists
destroy_unique(cell, identity = type_id) TYPE_ID continuation absence, lowered through the same output TypeHash scan
destroy_instance(cell, identity_field = id) A field-identified instance destruction intent; full same-field output scan is runtime-required
burn_amount(cell, field = amount) Quantity-delta burn intent; executable delta proof is runtime-required

Bare destroy cell still compiles as DestructionPolicy::Default. In strict
mode it must be authorized by the 0.15 kernel effects consume + burn instead
of the legacy has destroy capability. Use a policy-specific form when the
audit needs to distinguish singleton absence, TYPE_ID consumption,
field-identified instance consumption, or amount burn.

IrInstruction::Destroy now carries policy: IrDestructionPolicy through
IR and codegen. Codegen only emits the legacy same-TypeHash absence scan for
singleton/type-id destruction policies; instance and amount policies are
reported as runtime-required instead of being over-constrained as singleton
absence.

Kernel/Protocol Primitive Split

0.15 splits resource capabilities into kernel effects and protocol verbs.

New kernel-effect capabilities in has ... clauses:

resource Token has store, create, consume, replace, burn, relock, retarget_type, read_ref

These are context-sensitive identifiers: they are only treated as capability
keywords inside has ... clauses and remain ordinary identifiers elsewhere
(e.g., action burn(token: Token) compiles normally).

Capability::is_protocol_verb() and Capability::kernel_effects() classify
capabilities for migration tooling. transfer and destroy are protocol
verbs in 0.15; their effects decompose as:

transfer  -> consume + create + relock (+ replace if lock changes)
destroy   -> consume + burn (or consume + assert_absence)

Verifier Soundness Hardening

0.15 closes the known high-risk boundary leaks where verifier semantics could
be lowered too early into ordinary low-level values, raw byte spans, raw paths,
or syntax occurrences. The hardening work includes:

  • fail-closed paths no longer lowering as ordinary Return(U64(error)) values;
  • runtime/helper and syscall status paths checked before exposing DSL values;
  • lock predicate success requiring canonical bool == 1;
  • Molecule semantic field access gated by containing-layout canonicality;
  • branch-local and duplicate lifecycle effects conservatively rejected until
    CFG-aware resource summaries are complete;
  • package/dependency paths contained inside their declared capability roots;
  • const initializers restricted to compile-time-safe expressions;
  • initial SyscallSpec, IR status-boundary, validated schema planning,
    ResourceEffectSummary, and ProofPlan executable-evidence scaffolding.

Internal Metadata Renaming

Public metadata fields that previously used type_hash ambiguously are now
explicit about which CKB hash domain they refer to:

Old name New name
type_hash-absence ckb_type_script_hash-absence
type_hash-preservation ckb_type_script_hash-preservation
lock_hash-preservation ckb_lock_script_hash-preservation

Protocol Macro Provenance

ProofPlan coverage records include macro provenance for selected
compiler-recognized flows such as transfer, create, claim, settle,
consume, destroy, and pool protocol metadata. This is audit metadata;
it is not a replacement for builder-backed CKB transaction evidence.

Runtime-Obligation Policy Gate

cellc check --deny-runtime-obligations rejects runtime-required ProofPlan
gaps, including declared invariants whose coverage is still metadata-only or
whose action coverage is unmatched.

New Syntax Reference

Type Declaration Identity

resource Token has store {
    identity(ckb_type_id)      // CKB TYPE_ID
    amount: u64
}

shared OracleData {
    identity(script_args)       // Script.args identity
    value: u64
}

resource NFT has store {
    identity(field(token_id))   // Field-based identity
    token_id: [u8; 32]
    owner: Address
}

Default is identity none (no tracking); backward compatible.

Identity-Aware Lifecycle Forms

// create_unique — identity-aware cell creation
let token = create_unique<Token>(identity = ckb_type_id) {
    amount: 100
} with_lock(recipient)

// create_unique with a field identity
let nft = create_unique<NFT>(identity = field(token_id)) {
    token_id,
    owner
} with_lock(owner)

// replace_unique - identity-aware replacement (consumes input)
let updated = replace_unique<Token>(identity = ckb_type_id) token {
    amount: token.amount - 10
}

let moved = replace_unique<NFT>(identity = field(token_id)) nft {
    token_id: nft.token_id,
    owner: new_owner
}

Destruction Policy Forms

// Prove no same-TypeHash output exists
destroy_singleton_type(token)

// Prove TYPE_ID identity is consumed (not replaced)
destroy_unique(token, identity = type_id)

// Prove a specific instance is consumed (allow other same-type outputs)
destroy_instance(token, identity_field = id)

// Prove quantity delta (burn)
burn_amount(token, field = amount)

Aggregate Invariant Syntax

invariant conservation {
    trigger: type_group
    scope: group
    reads: group_inputs<Token>.amount, group_outputs<Token>.amount

    assert_sum(group_outputs<Token>.amount) == assert_sum(group_inputs<Token>.amount)
}

invariant no_duplicate_nft {
    trigger: type_group
    scope: transaction
    reads: outputs<NFT>.token_id

    assert_distinct(outputs<NFT>.token_id, scope = transaction)
}

Future Direction: 0.16 Enforced Boundary Architecture

In 0.15, invariants are treated as declared ProofPlan obligations rather than
implicitly executed verifier functions. This is intentional: an invariant is
only sound when its trigger, scope, reads, and CKB script boundary are explicit.

The next step is invariant satisfaction checking. A declared invariant should be
considered production-satisfied only if one of the following holds:

  1. it has been lowered into executable verifier code;
  2. it is matched by a checked action obligation with compatible trigger, scope,
    type, field, and relation coverage;
  3. it is rejected by strict or production gates as runtime-required.

Aggregate primitives such as assert_sum, assert_conserved, assert_delta,
assert_distinct, and assert_singleton are the first candidates for
executable lowering, because their fixed-width field restrictions already
provide a bounded ABI and scanner shape.

The 0.16 theme is moving from boundary scaffolding to enforced architecture:
all runtime and stdlib helpers should derive from a shared SyscallSpec,
status-like values should be impossible to treat as domain values, semantic
schema access should require validated field objects, lifecycle effects should
merge through CFG-aware summaries, and ProofPlan claims should cite concrete
IR/codegen/runtime evidence IDs.

Verification

Targeted 0.15 gate:

./scripts/cellscript_gate.sh ci
./scripts/cellscript_gate.sh backend
cargo test --locked -p cellscript proof_plan --lib -- --test-threads=1
cargo test --locked -p cellscript aggregate_invariant --lib -- --test-threads=1
cargo test --locked -p cellscript identity --lib -- --test-threads=1
cargo test --locked -p cellscript --test cli cellc_explain_proof -- --test-threads=1

Full release gate:

./scripts/cellscript_gate.sh release
2 Likes

CellScript 0.16 Release Notes

CellScript 0.16 is a rather large upgrade focused on assurance and tooling. For users, the main change is that the compiler is no longer just producing an artefact and a metadata sidecar.

It now gives you a clearer view of what a contract expects from a CKB transaction builder:

  1. what is checked by generated verifier code,
  2. what still needs external evidence,
  3. and which proof or deployment facts changed between
    builds.

This release is deliberately conservative. It improves the day-to-day workflow for building, reviewing, and packaging CKB-facing CellScript contracts, but it does not claim full transaction solving, formal verification, or executable CKB equivalence for every standard compatibility fixture.

Also, NovaSeal now ships with the 0.16 branch as bundled proposal packages and local
acceptance tooling. Its detailed project progress will be tracked separately in
the Nervos Talk thread.

What Developers Will Notice

Clearer Pre-Production Feedback

--primitive-strict=0.16 is now the strict pre-production mode. It catches
ProofPlan gaps that earlier workflows could leave as audit notes.

In practical terms, this means:

  • contracts with metadata-only invariant claims fail earlier;
  • runtime-required obligations are surfaced as blockers instead of being easy
    to miss in metadata;
  • strict builds make it clearer whether a source file is ready for production
    evidence or still needs protocol-specific review;
  • fail-closed examples now fail for explicit PP0150 reasons rather than hiding
    behind older 0.15-era tooling paths.

Some bundled examples intentionally remain strict-fail-closed in 0.16.
token.cell, amm_pool.cell, and launch.cell still contain selected
aggregate or Pool ProofPlan gaps. They remain useful examples, but the release
notes and wiki no longer describe them as strict-clean production artefacts.

Better Builder Handoff

The compiler now gives transaction builders a much more concrete handoff.

Users can inspect:

  • required inputs and outputs;
  • required cell deps and witness fields;
  • capacity, fee, change, and signature policy expectations;
  • which assumptions need evidence before signing;
  • which assumptions are only structural and which require external material.

The user-facing command is:

cellc explain-assumptions src/main.cell --json

This is meant for wallet, relayer, SDK, and builder integration work. It does
not replace CKB dry-run or final transaction validation, but it makes the
builder contract visible and reviewable.

Transaction Shape Checks Before Signing

0.16 adds:

cellc validate-tx --against metadata.json tx.json --json

This checks whether a transaction JSON shape lines up with the compiler’s
builder assumptions. It is useful before signing or handing a transaction to a
separate builder pipeline.

It can catch missing or malformed evidence for assumptions such as TYPE_ID
plans, global uniqueness, lock-group transaction scope, capacity evidence, and
manifest-bound spawn targets.

This is still not a full semantic CKB verifier. Production claims still require
dry-run, capacity checks, cycle evidence, commit evidence, and any external
attestations required by the protocol.

More Useful Transaction Templates

cellc solve-tx now emits a deterministic transaction template rather than
leaving builders to reconstruct all requirements from scattered metadata.

Users should expect a template that names:

  • input and output slots;
  • dep requirements;
  • fee and change expectations;
  • signing manifest structure;
  • per-lock signature request requirements.

It is not a final solver. It does not pick live cells, resolve headers, compute
final fees, place every witness, or submit the transaction. It gives builders a
stable starting point.

More Practical Audit And Deployment Reports

The metadata tooling surface has expanded around common release-review tasks:

cellc deploy-plan
cellc verify-deploy
cellc diff-deploy
cellc lock-deps
cellc proof-diff
cellc profile
cellc trace-tx
cellc audit-bundle

The user-visible benefit is that release reviewers can now answer questions
without manually comparing raw metadata files:

  • what changed between two ProofPlan records;
  • which deployment dependency changed;
  • which lock deps are expected;
  • which source entries contribute to an audit bundle;
  • whether a deployment plan still matches its metadata;
  • what a profile exposes to downstream tooling.

These reports are JSON-first so they can be used in CI, wallets, release
scripts, and external audit tooling.

Better Editor Experience

The VS Code extension is aligned with CellScript 0.16.0.

For users, this means the local editor integration now follows the current
cellc --lsp and 0.16 authoring surface. The extension exposes active-file
commands for the report flows that do not require separate input files:

  • builder assumptions;
  • transaction template;
  • deployment plan;
  • profile report;
  • audit bundle.

Commands that compare or validate separate artefacts remain CLI-first:
validate-tx, trace-tx, proof-diff, verify-deploy, diff-deploy, and
lock-deps.

CKB And Compatibility

Descriptive CKB Compatibility Fixtures

The CKB compatibility suite now documents expected shapes for common Nervos
contracts and patterns:

  • sUDT;
  • xUDT;
  • ACP;
  • Cheque;
  • Omnilock-compatible locks;
  • NervosDAO since/epoch behaviour;
  • Type ID.

The manifest is:

tests/compat/ckb_standard/manifest.json

These fixtures are useful for review, planning, and compatibility discussion.
They are not yet executable accepted/rejected CKB VM tests. CKB dry-run remains
the acceptance mechanism for production claims.

CKB Standard Library Protocol Stubs

0.16 adds schema-level stdlib protocol descriptors for:

  • std::sudt;
  • std::xudt;
  • std::type_id;
  • std::htlc;
  • std::cheque;
  • std::acp.

For users, this is a roadmap signal and a tooling anchor. The descriptors make
the intended protocol surface visible, but they are not production modules yet:
there is no CellScript source implementation, assembly generation, or
production CKB evidence for these stdlib protocols in 0.16.

NovaSeal Packaging

NovaSeal is included with the 0.16 branch as bundled proposal packages plus
local devnet/profile acceptance tooling. This means CellScript users can inspect
NovaSeal examples, profiles, schemas, fixtures, and local evidence generation
from the same checkout.

The local acceptance boundary remains explicit. A local run can report:

status=local_devnet_passed_external_endpoint_required
live_devnet_rpc_executed=true
local_blockers=0
external_endpoint_status=external_required

Full external-completeness is stricter and must reach:

status=passed
live_devnet_rpc_executed=true
local_blockers=0
acceptance_blockers=0
blockers=0
external_endpoint_status=passed

NovaSeal is therefore shipping with CellScript as a bundled proposal package,
not as a blanket mainnet-production claim for every CellScript or NovaSeal
profile. External BIP340 TCB review, public BTC SPV evidence, shared CellDep
attestation, and profile-specific external review remain part of the production
acceptance boundary.

Ongoing NovaSeal progress, discussion, and project-facing updates will be
tracked in the Nervos Talk thread:
NovaSeal: a Bitcoin-authorised Cell framework for CKB.

Compatibility

Existing v0.15-style sources can still use default compatibility mode while
migration is in progress.

Use --primitive-strict=0.16 when you want the stricter pre-production
assurance gate. Expect it to reject metadata-only or runtime-required ProofPlan
gaps that were previously visible but not fatal.

The practical migration advice is:

  1. Compile without strict mode to inspect current metadata.
  2. Run cellc explain-assumptions --json and review builder obligations.
  3. Try --primitive-strict=0.16.
  4. Treat PP0150 as a real readiness signal, not as a compiler nuisance.
  5. Use CKB dry-run and acceptance evidence before making production claims.

Verification

Focused v0.16 gate:

cargo test --locked -p cellscript --test v0_16 -- --test-threads=1
cargo test --locked -p cellscript proof_plan --lib -- --test-threads=1
cargo check --locked -p cellscript --all-targets
git diff --check

Full scoped 0.16 gate:

cargo fmt --all
cargo check --locked -p cellscript --all-targets
cargo test --locked -p cellscript
cargo clippy --locked -p cellscript --all-targets -- -D warnings
git diff --check

NovaSeal local acceptance entry point:

./scripts/novaseal_devnet_stateful_acceptance.sh --pretty
target/debug/cellc certify --plugin novaseal-profile-v0 --repo-root . --json

Deferred To 0.17

The following items remain outside the scoped 0.16 release:

  • executable CKB VM accepted/rejected fixture runner;
  • full CKB transaction semantic validation;
  • final transaction solver with live cell selection, dep/header resolution,
    fee/change calculation, witness placement, signing, and dry-run;
  • on-chain deployment verification;
  • full CellScript-to-RISC-V/assembly source maps;
  • production-ready CKB stdlib protocol implementations;
  • executable aggregate invariant lowering;
  • iCKB differential tests;
  • production formal-verification guarantees;
  • deeper compiler cleanups from the comparative audit.

Intentional Boundaries

0.16 improves user-facing assurance, but the boundaries remain important:

  • ProofPlan soundness is a metadata consistency checker, not a formal proof of
    invariant soundness;
  • validate-tx is structural and evidence-schema validation, not full CKB
    semantic validation;
  • solve-tx emits templates, not final transactions;
  • standard CKB compatibility fixtures are descriptive, not executable
    equivalence tests;
  • CKB stdlib protocol modules are schema stubs, not production-ready modules;
  • NovaSeal ships with the branch as bundled proposal packages and local
    evidence tooling, not as a blanket production claim;
  • CKB dry-run, transaction commitment evidence, and required external
    attestations remain the production acceptance layer.
2 Likes

CellScript 0.16 → 0.20 Release Notes

CellScript 0.16 to 0.20 is the move from a compiler that emits an artifact and
metadata into a fuller build path: source packages, lockfiles, deployment
identity, generated builders, browser compilation, and stricter CKB evidence.

CellScript now gives downstream tools a clearer answer to “what source was built, what artifact came out, what deployment does it match, and what transaction-builder assumptions still need real CKB evidence?”

Why This Is One Note

The 0.17, 0.18, and 0.19 work was important, but not cleanly user-facing on its
own. Those releases moved the iCKB research surface, CKB protocol helpers,
first-class Script handling, package identity, registry verification, and
adapter boundaries forward in overlapping steps.

For readers, the useful public story is the larger 0.16 to 0.20 arc. The
per-version patch notes for 0.16.1 and 0.16.2 remain separate because they
describe concrete fixes on the 0.16 line. The 0.20 release note carries the
detailed final evidence boundary.

The Main Changes

1. Better Handoff To Transaction Builders

0.16.1 cleaned up the bundled examples for external builders: token minting,
launch bootstrap, AMM pool creation, and NFT collection creation now expose the
first-cell paths directly instead of relying on implicit harness knowledge.

0.16.2 then made the CLI handoff more concrete:

  • cellc explain-assumptions and cellc solve-tx can be scoped with
    --entry-action or --entry-lock.
  • cellc entry-witness exposes witness shape and script-group witness
    placement.
  • cellc resource-identity emits passive resource identity plans for resource
    output type scripts.
  • cellc validate-tx checks transaction shape, resource identities, and
    production fixture-identity mistakes before signing.
  • cellc builder manifest and cellc builder check are the canonical
    builder-facing workflow over ABI, constraints, witness, assumptions,
    resource identity, and validation.

0.20 builds on that handoff with cellc gen-builder --target typescript, which
generates a typed TypeScript builder scaffold from compiler metadata.

The generated builder is intentionally bounded. It can plan actions, validate
lockfile and deployment identity, and delegate build / dry-run / submit work to
a runtime adapter. It is not a wallet, signer, indexer, or full CKB transaction
solver.

2. Package And Deployment Identity

By 0.20, CellScript projects are no longer treated as loose source files.
Cell.toml, Cell.lock, Deployed.toml, registry records, source hashes,
artifact hashes, metadata hashes, schema hashes, ABI hashes, constraint hashes,
and cell-data codec hashes now form one identity chain.

That means a tool can fail closed when the source, build, registry record,
deployment record, or live chain data no longer match. The registry resolver
uses the Git-backed source-package model, verifies source_hash, skips yanked
versions, and records the result in Cell.lock.

3. Multi-File Packages

Now CellScript 0.20 treats package compilation as a source graph.
Imports are exact-path, local package dependencies are loaded before frontend checks, diagnostics point back to the right file, and cache keys include dependency sources plus package
metadata.

Cross-file type, schema, and helper reuse is supported inside one entry
artifact. This is compile-time reuse, not an ELF linker and not cross-script
runtime linking.

4. More Honest CKB Evidence

The CKB/devnet acceptance path now checks the ELF entry ABI before accepting local-node evidence. The gate rejects compiled CKB ELFs that do not preserve the CKB-VM entry assumptions.

Acceptance reports also include exact build rows that bind the compiled ELF, host hash, CKB deployable hash, verify-artifact result, ABI gate result, and live code-cell data hash when devnet deployment evidence exists.

Compile-only evidence is still useful, but it is not the same as live devnet or
chain evidence.

5. Clearer CLI, LSP, And Playground Experience

The CLI is easier to approach:

  • top-level help shows package commands and direct compile mode;
  • cellc --list enumerates commands;
  • unknown bare commands get suggestions;
  • parse, lex, and compile errors include source snippets;
  • package checks report multiple frontend errors with file context.

The browser playground now supports a local multi-file workspace over the WASM compiler path. It stays client-side: no server compile API, no uploaded source archive, and no server-owned project state.

6. Protocol Work Stayed Explicit

The line adds or matures CKB-facing helpers for SourceView, Script and ScriptArgs handling, WitnessArgs extraction, DAO / xUDT / Type ID related checks, OutPoint and MetaPoint scans, capacity helpers, and raw cell-data codec metadata.

Those helpers are compiler and verifier surface, not hidden protocol magic.

When a feature needs external data, builder work, or chain acceptance, the metadata says so.

7. CellScript-Native Protocols And Research Surfaces

NovaSeal and Evolving DOB are bundled CellScript-native protocols.

NovaSeal has local and devnet evidence across several profiles, including the 0.20 multi-file fungible-xUDT refactor. Public production status still depends on current external BIP340 TCB review, public BTC SPV evidence, public/shared CellDep evidence, and profile-specific attestations.

Evolving DOB profile v1 is included as proposal evidence with manifests, fixtures, ProofPlan and invariant records, devnet workflow material, and audit notes. It is not a claim that every guard, registry pressure path, or deployment policy question is closed.

iCKB equivalence remains benchmark and differential-evidence work. The committed matrix has original-vs-CellScript and CellScript-only CKB VM rows, but production equivalence is not claimed. Keep it labelled as research / benchmark evidence until the missing production-equivalence closure work is actually done.

What Was Removed Or Tightened

The legacy transfer capability is gone from the active surface and its test matrix. Old investigation notes were removed or archived so stale status does not look current.

Raw cell-data access is now named through the cell-data codec manifest. Public raw-layout production claims still need the external codec, builder, indexer, and parity evidence that such claims require.

Validation

For routine local development:

./scripts/cellscript_gate.sh dev

For merge-readiness:

./scripts/cellscript_gate.sh ci

For CKB production or external live/devnet claims:

./scripts/cellscript_gate.sh release

For compile-only release preflight:

./scripts/cellscript_gate.sh release-quick

Use release for any claim that depends on live/devnet CKB evidence.
release-quick is not external chain evidence.

For website or playground changes, also run:

website/scripts/build-wasm.sh
(cd website && npm run build)
3 Likes

CellScript 0.21.0 Release Note

CellScript 0.21 is out. You can install it with one line, but the bigger story is underneath: you can now sign your work, the CLI is finally organized, and the compiler will catch a class of bugs it used to let through.

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.)

1. You can sign your build

If you’ve ever had to defend a deployed CellScript contract with “trust me, the artifact matches the source”, this is your release.

cellc receipt produces a cellscript-compile-receipt-v1 envelope that binds together:

  • the source hash,
  • the metadata schema version (now 44),
  • the ProofPlan, ProtocolGraph, and TemplateLayout hashes,
  • the artifact hash, the metadata hash, and the report hash,
  • and optionally Ed25519 signatures for compiler and publisher roles.

Then cellc sign-receipt adds a signature, and cellc verify-receipt checks it. There’s also a --receipt flag on cellc verify-artifact, so a verification step in CI can refuse to deploy an artifact whose receipt doesn’t check out.

For teams running registries: receipt verification becomes the audit boundary. For solo devs: it’s peace of mind, plus a clean artifact you can hand to a reviewer.

2. The CLI got a real shape

The old cellc flat command list was fine when there were eight commands. By 0.21 there were forty, and half of them had cryptic names.

The 0.21 tree is grouped:

Before After
cellc solve-tx cellc tx solve
cellc deploy-plan cellc deploy plan
cellc verify-deploy cellc deploy verify
cellc registry-verify cellc registry verify
cellc explain-assumptions cellc explain assumptions

The old flat names are still aliases — they work, they’re just hidden from cellc --list and cellc --help. So your existing scripts don’t break, and you can migrate at your own pace.

Two more DX wins in this bucket:

  • --message-format=json on every command. CI logs become parseable. Agent loops can branch on diagnostic codes instead of grepping strings. The --json flag for successful payloads still works the way it did.
  • --color=auto|always|never plus NO_COLOR=1 respect. Piped output is no longer drowned in ANSI escape codes.

3. The compiler got sharper

Two changes that catch real bugs:

Flow-edge validation. If you declare flow Wallet { Open -> Closed; }, an action claiming Open -> Spent now fails to compile, with a diagnostic that names the type, the state field, and the missing edge. Cyclic flows (Open <-> Closed) still work; the cycle has to be declared. No codegen change — this is purely a static contract.

Executable aggregate invariant lowering. The most common xUDT shape:

assert_sum(group_outputs<Token>.amount) == assert_sum(group_inputs<Token>.amount)

is no longer a metadata-only record.

When the action has matching consumed-to-created amount evidence, codegen now auto-emits a __xudt_require_group_amount_conserved call into the action prelude.

Three ProofPlan coverage states now exist — metadata-only, runtime-helper-required, checked-runtime — and strict 0.17 validation rejects the stale-helper gap ( PP0170) that 0.20 used to silently accept. If you’ve been doing this by hand, you can stop.

The TypeScript builders and the CKB adapter also got a builder-resolution pass, args_parts for variable-length script args, manifest-backed CellDep completion, action-aware scan selector evidence. The adapter still fails closed on missing or mismatched evidence, so don’t expect it to paper over bad metadata.

4. A bonus for the agentic loop

If you use Claude Code, Cursor, Aider, Codex, or any other tool that speaks MCP: CellScript 0.21 ships a cellscript-mcp server binary and six programming skills (cellscript-{diagnostics, language-basics, metadata-audit, package-cli, ckb-model, builder-deployment}). Point your agent at the MCP server, and it gets the same compiler, examples, and gate policy that you do. The dev and CI gates enforce skill-pack freshness, so the docs can’t silently drift.

A derived ProtocolGraph view is now embedded in audit bundles — types, states, transitions, action patterns, with cycles marked. It’s a metadata-derived view, not a new IR, but it’s the thing your auditor is going to ask for first.

Try it

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

1 Like