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.

3 Likes

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

CellScript 0.23.0 Release Notes

Release: v0.23.0,

In a Nutshell:

The language surface is mostly unchanged.

The significant changes are at the boundaries between source code, generated artifacts, transaction builders.

Also equally important is the rapidly evolving Unified Registry.

Major changes:

  • every package now declares the source-semantics edition 2026;
  • compiler outputs carry one resolved compatibility-profile identity;
  • parameterized CKB entries accept arguments only from WitnessArgs.input_type;
  • older lock, deployment, receipt, builder, and raw-witness identities are
    rejected rather than interpreted as 0.23 data; and
  • the public Registry now has a working publish, compiler-verification, discovery, and source-package installation path.

Note:
Existing projects need a deliberate upgrade. In particular, changing the package version alone is not sufficient: the manifest, witness builder, and persisted build records must all move to the 0.23 identities together.

Install 0.23.0

Install a published binary:

CELLSCRIPT_VERSION=0.23.0 curl -fsSL https://raw.githubusercontent.com/CellScript-Labs/CellScript/main/scripts/install.sh | sh
cellc --version

The GitHub release includes SHA256SUMS for the four platform archives.

To build the exact released source, use the repository-pinned Rust 1.97.1
toolchain:

git clone --branch v0.23.0 --depth 1 https://github.com/CellScript-Labs/CellScript.git
cd CellScript
cargo install --locked --path .

A new package created by 0.23 already contains the required edition:

cellc init hello-cell
cd hello-cell
cellc check --target-profile ckb
cellc build --target riscv64-elf --target-profile ckb

Upgrading An Existing Package

The minimum source-manifest change is:

[package]
edition = "2026"

Then regenerate the records that are bound to compiler output:

  1. rebuild every RISC-V artifact and its metadata;
  2. refresh Cell.lock and Deployed.toml instead of retaining their older
    schemas;
  3. regenerate compile receipts and generated action builders;
  4. update transaction builders and fixtures to put the CSARGv1 payload in
    WitnessArgs.input_type before signing; and
  5. repeat package checks, CKB-VM tests, capacity checks, and any deployment
    verification used by the project.

There is no compatibility reader that upgrades an old persisted record in
place. A missing edition, a non-2026 edition, or an old record identity is now deemed as an
error. This is intentional: two tools should not be able to read the same
package or deployment record and silently assign different semantics to it.

Edition 2026 And The Resolved Compatibility Profile

Edition 2026 is CellScript’s first source-semantics edition. The year is a long-lived epoch label, not a promise of annual editions and not a shorthand for every compiler ABI.

The edition owns source rules that could change the meaning of an unchanged .cell file, including parsing ambiguities, name resolution, typing and coercion, desugaring, and resource-flow semantics. Other compatibility axes continue to version independently:

  • compiler SemVer;
  • target profile;
  • primitive-assurance mode;
  • entry-payload encoding;
  • witness placement and script-group source; and
  • metadata, source, artifact, and constraints schemas.

The compiler combines these values into cellscript-resolved-compatibility-profile-v1 and emits the resolved profile and its hash in compile metadata. Tools that consume an artifact compare the same hash instead of inferring compatibility from the compiler version.

The persisted 0.23 identity set is:

Surface 0.23 identity
Compile metadata metadata schema 57, source schema 2, artifact schema 1, constraints schema 2
Resolved profile cellscript-resolved-compatibility-profile-v1 with independent source, target, assurance, ABI, and schema axes
Cell.lock version 2
Deployed.toml version 2 with schema cellscript-deployed-v0.23-edition-2026
Compile receipt receipt v2 with edition and resolved profile
Generated action builder cellscript-generated-action-builder-v0.23-edition-2026
Registry build record explicit edition and compatibility-profile hash
Registry publication one complete entry containing edition, profile hash, status, dependencies, and yank state

The same profile identity is carried through the CLI, LSP, native library,
WASM metadata API, Registry, lock file, deployment record, receipt, and builder
output. A mismatch fails at the boundary where it is observed; consumers do
not substitute a default profile.

Canonical Entry Arguments In WitnessArgs.input_type

At the CKB transaction layer, a witness is a byte array. CellScript 0.23 now requires the selected witness bytes to encode the standard Molecule
WitnessArgs table:

WitnessArgs {
    lock:        BytesOpt,  // Lock Script or signature data
    input_type:  BytesOpt,  // CellScript CSARGv1 entry payload
    output_type: BytesOpt,  // output-side Type Script data
}

The placement ABI is cellscript-witnessargs-input-type-v2. The generated
entry wrapper performs the following steps:

  1. select GroupInput#0 for the active script group;
  2. if the group has no input, select GroupOutput#0;
  3. validate the WitnessArgs table and its BytesOpt offsets;
  4. extract input_type;
  5. check the CSARGv1\0 payload magic; and
  6. decode the positional entry arguments.

The wrapper no longer accepts a raw CSARGv1 byte array as an alias for a
WitnessArgs value. It also rejects a missing input_type, malformed Molecule
offsets, or a payload placed in lock or output_type. These cases return
runtime error 25 entry-witness-abi-invalid.

Generated builders parse or create WitnessArgs, preserve existing lock and
output_type values, and refuse to overwrite an occupied input_type. The
payload is placed before the transaction is signed so that the final witness
layout is covered by the signing flow.

Two naming points are worth making explicit:

  • input_type is a field of WitnessArgs; it does not mean the Type Script of
    an input Cell.
  • CSARGv1 remains CellScript’s entry-payload encoding inside that field; it
    does not replace Molecule or the CKB WitnessArgs convention.

This placement leaves lock available for Lock Script signatures and keeps
CellScript arguments separate from output-side Type Script data.

Publishing Through The Public Registry

The public Registry is available at cellscript.dev/registry.

In 0.23, the source-package path is connected from the CLI through the write API and compiler worker to public discovery and installation.

Before publishing, verify the package and inspect the request without writing:

cellc package verify --json
cellc publish --dry-run

For the first publication under a package coordinate, run:

cellc publish --authorise

The CLI creates a delegated P-256 publishing key, stores it as pending in the
local operating-system keychain, and creates a 15-minute browser session for
the exact namespace, package, and artifact kind. After wallet approval, the
Registry returns the matching key ID, the CLI marks the local key active, and
the original publish continues. --no-open prints the session URL for remote
or terminal-only use.

The private publishing key does not move into the browser. The browser approves
the delegated capability; later releases can use the active local key until its
capability expires or is revoked. The explicit auth capability submit and
auth namespace claim sequence remains available for CI, manual signing, and
external-wallet workflows.

What Registry Verification Means

A successful publish first admits a signed source record and immutable source
snapshot. Admission also creates a compiler-verification job. A separate
least-privilege worker then:

  1. authenticates the snapshot descriptor and source contents;
  2. compiles the package with the current CellScript compiler;
  3. checks the canonical manifest, Edition 2026, and resolved-profile identity;
  4. records the build evidence; and
  5. promotes the release to verified_build only after those checks succeed.

Pending and rejected entries are not shown by default search. They remain available through direct audit URLs or explicit status filters.

The Registry status names are deliberately narrower than a general security claim:

Status Meaning
source_published The signed source record and snapshot were admitted. Compiler verification has not completed.
indexed_pending Registry indexing or verification publication is still pending.
verified_build The recorded compiler/build checks passed for the bound source and profile. This is not a contract audit.
deployed Separate deployment evidence was accepted and checked against its immutable identity.
on_chain_committed The configured chain-evidence path observed the required sufficiently confirmed live commitment.

Generic administrative status changes cannot manufacture
verified_build, deployed, or on_chain_committed. Those transitions use the ordered evidence-promotion path and bind each new state to its preceding evidence.

Installing A Registry Source Package

Install an accepted CellScript source package with:

cellc install namespace/package@version

cellc install and cellc update use the public API’s accepted-status view by
default. Before placing the dependency in the local package graph, the CLI
checks:

  • the immutable snapshot descriptor SHA-256;
  • archive and path safety;
  • each file’s BLAKE2b hash;
  • the whole source-tree hash;
  • the Cell.toml package identity;
  • Edition 2026; and
  • the resolved compatibility-profile identity.

An explicit install of an unverified or quarantined release requires the corresponding acknowledgement. That choice is stored with the dependency so a later lock refresh or build does not silently forget the risk decision.

The older CELLSCRIPT_REGISTRY_URL Git/registry.json path remains available as an explicit offline override. It is no longer the default public authority.

Registry Artifacts And Chain Evidence

The Registry is not limited to CellScript dependency packages. A publication declares its artifact kind, source language, profile, and consumption mode. CellScript source packages, CKB executables, runtime verifiers, reproducible binaries, and copy-only templates can all be discovered, but they are not consumed in the same way:

  • only a CellScript source package with dependency consumption enters
    cellc package resolution;
  • a deployable executable is verified, pinned, deployed, and referenced as a
    CellDep through explicit artifact commands;
  • a runtime verifier is recorded as part of the trusted computing base; and
  • a template is copied without becoming an implicit package dependency.

For a reproducible-binary profile, a manifest flag is not enough to claim reproducibility. The Registry requires signed reports from between two and sixteen distinct builders, subject to the configured builder and trust-domain policy. Reports bind the environment, source, build recipe, executable, build log, builder identity, and preceding evidence.

Mainnet deployment and Registry commitment support is implemented in the 0.23 tree, including RPC liveness checks and wallet transaction intents. At the release boundary, the production commitment path remains disabled because the canonical Registry Type Script, custody Lock, and required code CellDeps have not all been deployed and configured. The public service being live is not evidence of an on-chain mainnet commitment.

Syntax And Example Cleanup

0.23 does not redesign actions, verification blocks, invariants, destruction policies, parameter sources, or Registry namespaces. The syntax audit instead closed several consistency gaps in checked-in examples and fixtures:

  • canonical type declarations use comma-terminated fields;
  • the parser still accepts comma-free fields as compatibility input, and the syntax-combination matrix tests both forms, this may well change in 0.24
  • atomic-swap, NFT, timelock, and multi-phase-DAO examples use a named
    U64_MAX value for overflow guards instead of repeating raw boundary
    literals; and
  • crypto-primitive CKB-VM fixtures now place CSARGv1 in WitnessArgs.input_type rather than using the removed raw-witness alias.

The formatter, bundled examples, syntax-combination audit, and CKB-VM fixtures now agree on the canonical forms.

A Better Playground Experience

With CellScript 0.23 the browser Playground is also upgraded into a more reliable, Cell-oriented workbench:

  • browser-local workspaces preserve source files, the selected entry, active panels, and saved or unsaved state across refreshes;
  • compile errors keep the last successful output visible and clearly mark it as stale;
  • a failed compiler Worker can be restarted without reloading the page;
  • the newly added feature ‘Cell Flow’ provides a visual view of actions and Cell transitions;
  • Inspector connects actions, types, diagnostics, and metadata while keeping the raw compiler output available.

Registry Endpoints:

curl --fail --silent --show-error https://api.registry.cellscript.dev/ready
curl --fail --silent --show-error 'https://api.registry.cellscript.dev/v1/artifacts?limit=5'
curl --fail --silent --show-error https://registry.cellscript.dev/health
curl --fail --silent --show-error https://api.testnet.registry.cellscript.dev/ready

These calls show service configuration and liveness.

5 Likes

CellScript 0.24: Verifiable Builds and Reproducible Projects

When the CellScript 0.23 Registry went live, it made several gaps difficult to ignore. Internally, the compiler and package pipeline needed tighter security boundaries. Externally, projects consuming CellScript needed stronger evidence for what had been built, which source and dependencies it came from, and why the result could be trusted.

I decided to shorten the development cycle instead of leaving those problems for a distant milestone. Several other projects I maintain are beginning to use CellScript not only as a compiler, but also as a shared semantic foundation. For those projects to iterate steadily, that foundation needs reproducible builds, explicit execution evidence, and verification boundaries that do not depend on trusting the compiler alone.

That is why CellScript 0.24 arrives only ten days after 0.23.

This release focuses on four areas:

  1. Compiler artifacts can be checked independently.
  2. cellc test executes explicit backends.
  3. Cell.lock becomes authoritative for dependency resolution.
  4. CKB Lock Scripts can publish LS-IDL through the Registry.

Independent artifact verification

flowchart LR
    S["CellScript source"] --> C["CellScript compiler"]

    C --> E["CKB ELF"]
    C --> L["Lowering record"]
    C --> M["Source map"]

    E --> V["Independent artifact checker"]
    L --> V
    M --> V

    V --> R["Verification report"]
    R --> A["Auditors"]
    R --> G["Registry"]
    R --> D["Downstream projects"]

A CKB ELF build now produces two additional files:

ARTIFACT.lowering.json
ARTIFACT.sourcemap.json

The lowering record describes the path from typed IR to machine blocks. The source map connects ranges in the final ELF back to the original CellScript source.

The new cellscript-artifact-checker does not load the compiler frontend or depend on the code generator. It independently checks a bounded set of properties, including:

  • canonical JSON encoding;
  • ABI and stack-frame consistency;
  • control-flow and reachability rules;
  • the allowed RV64 instruction surface;
  • call targets, syscalls, and stack restoration;
  • bindings between the lowering record, source map, and final ELF;
  • ProofPlan references and machine-block digests.

This is not a proof that the compiler is universally correct, nor is it a complete source-to-machine equivalence proof.

It provides something narrower and more practical: Registry workers, auditors, and downstream tools can verify specific structural facts without trusting the same compiler implementation that produced the artifact.

cellc test now runs an explicit backend

Previously, a successful test could sometimes mean only that the test source compiled. CellScript 0.24 makes the execution boundary explicit.

Tests must select a backend:

cellc test --backend simulator
cellc test --backend ckb-vm
cellc test --backend all

A test scenario can describe:

  • live Cell consumption and replacement;
  • Lock Script and Type Script identities;
  • Cell deps, headers, and since;
  • witness fields;
  • expected successful results;
  • exact failure codes;
  • multi-step state transitions.

The simulator remains a development tool and does not provide consensus evidence. The CKB-VM backend provides local runtime evidence, but it is still not evidence of deployment or confirmed execution on-chain.

For compile-only checks, --no-run must be selected explicitly. The tool no longer presents a test that was never executed as a successful runtime result.

The checked-in examples/scenario_basics package contains a small positive and exact-negative example that runs on both backends.

Cell.lock is now the build authority

flowchart LR
    M["Cell.toml requirements"] --> U["Explicit repin<br/>cellc lock / cellc update"]
    X["Git and Registry sources"] --> U
    U --> L["Cell.lock v3<br/>exact dependency graph"]

    L --> B["build / check / test"]
    B --> O["Artifacts and evidence"]

    X -. "not consulted during an ordinary locked build" .-> B

CellScript 0.24 introduces Cell.lock version 3.

The lockfile is no longer a flat list of selected versions. It records a complete dependency graph, including:

  • the digest of the root Cell.toml;
  • exact dependency versions and sources;
  • immutable Git commits or Registry snapshots;
  • source-tree and dependency-manifest digests;
  • dependency edges;
  • features and test-only dependencies;
  • CKB environments bound to a chain ID and genesis hash.

Ordinary build, check, and test commands consume the existing graph. They do not silently select newer versions or follow a moved Git branch.

Dependency selection happens only during an explicit operation such as:

cellc lock
cellc update

Using --frozen additionally disables network access and lockfile writes.

This gives local development, CI, and audit environments the same dependency graph. It also prevents Registry changes, moving Git references, or external resolvers from unexpectedly affecting a normal build.

Projects upgrading from 0.23 must regenerate Cell.lock.

LS-IDL for CKB Lock Scripts

CellScript 0.24 adds an end-to-end workflow for validating, binding, publishing, and retrieving LS-IDL documents.

The cellc artifact ls-idl command can:

  • validate a bounded LS-IDL 0.1 document;
  • calculate the SHA-256 digest of the original idl.json bytes;
  • bind that digest to a CKB executable;
  • create a Registry publication bundle;
  • retrieve the original IDL using a deployed Script identity.

The Registry stores the original bytes rather than parsing and reserializing the document. Both the compiler-backed verifier and the independent artifact verifier check the IDL, its digest, and the digest committed to the executable.

This establishes which interface document was published for a Lock Script. It does not prove that the Script correctly implements that interface, and it does not replace a security audit.

Compatibility coverage includes the current upstream ckb-idl-client vectors and the checked-in IDLs used by the supported upstream tooling. A smaller walkthrough is available in examples/registry_ls_idl.

Other changes worth noting

CellScript 0.24 also includes several integration and maintenance changes:

  • CKB ELF generation now always uses the audited internal assembler. The external RISC-V toolchain fallback has been removed.
  • Native cellc run includes the VM runner by default, but accepts only standalone ELF programs that do not require transaction context.
  • The Registry has a least-privilege verifier for artifact-only CKB bundles. It depends on the standalone checker rather than the full compiler.
  • Mainnet and Pudge Testnet Registry sites share the same interface and frontend assets while keeping APIs, object storage, address prefixes, and chain state separate.
  • The Registry Type Script has a reproducible 0.24.0 ELF identity.
  • The Playground can restore local workspaces, restart a failed compiler worker, retain the last valid output after a failed compile, and display a metadata-derived Cell Flow view.
  • Fiber configuration has moved from the unmaintained serde_yaml crate to serde_yaml_ng.

CKB Adapter migration

The CKB Adapter no longer exposes the permanently fail-closed methods:

deploy_artifact
build_deploy

The supported deployment flow is now:

  1. Build and verify an unsigned transaction with build_deploy_transaction.
  2. Hand the transaction to an external wallet for authorization.
  3. Submit the signed transaction explicitly.

CellScript should not hold wallet keys or blur the boundary between constructing a transaction and authorizing it.

Upgrading from 0.23

A typical upgrade should follow this order:

  1. Regenerate Cell.lock.
  2. Rebuild the ELF and metadata.
  3. Preserve the new lowering record and source map.
  4. Verify the artifact with the standalone checker.
  5. Update CKB Adapter deployment calls.
  6. Select the intended test backend explicitly.
  7. For Lock Scripts, decide whether to publish and bind an LS-IDL document.

CellScript 0.24 does not change the Edition 2026 source-language contract. It does change persisted metadata, lockfiles, and artifact evidence, so 0.23 and 0.24 build outputs should not be mixed.

Getting the source

Until the final tag is published, the release candidate is available from:

git clone --branch nightly-0.24 \
  https://github.com/CellScript-Labs/CellScript.git

Full release notes:

CellScript 0.24 Release Notes

Repository:

CellScript-Labs/CellScript

4 Likes

Four Months of CellScript: What Changed from 0.12 to 0.24

A Q2 and Q3-to-date 2026 project report for the Nervos community

When I posted CellScript 0.12 in April, I thought of it mainly as a compiler that had finally earned a clear release boundary. The bundled contracts compiled, the local CKB acceptance suite covered real transaction paths, and the compiler produced enough metadata for review. Handing the work to another team still required a dependable package, builder, and audit path.

By 0.24, the compiler is only one piece. Cell.lock records the exact dependencies. Generated builders carry transaction assumptions. The Registry stores source and artifact records. cellc test names the simulator or CKB-VM backend it actually ran. A separate checker reads the final ELF and its sidecars without loading the compiler that produced them.

The version numbers cover two uneven stretches of work. From April through June, the language and tooling began reporting CKB-specific facts directly. From July onward, package, builder, Registry, and audit paths had to preserve those facts as they moved outside the compiler.

April to June: compiler and CKB boundaries

0.12: release scope

0.12 focused on documenting exactly what the release covered and making that result reproducible.

The bundled examples could compile to CKB-VM artifacts, produce metadata, and run through a local CKB acceptance path. Runtime failures had stable names and hints. The release record now carried CKB hash and CellDep requirements, the witness ABI, and transaction size and capacity evidence.

The claim covered the bundled suite. Arbitrary new contracts still required their own review and acceptance evidence. Because local acceptance logs can look like a production promise once copied out of the repository, every result carried its scope.

The original 0.12 community update summarized the compiler and CKB gate work.

0.13–0.14: explicit Cell transitions

0.13 and 0.14 made the underlying CKB operations explicit in the language and its reports.

In 0.13, an action became easier to read as a proposed Cell transformation, with its inputs, outputs, and transition conditions visible in source. Lock-facing data sources became explicit. Standard lifecycle patterns moved out of compiler folklore and into reviewable library forms. A syntax-combination audit checked uncommon combinations across the front end, metadata, and generated artifact as well as the bundled happy paths.

0.14 brought CKB transaction sources and output data into the named contract boundary. Witness fields and Script args received distinct bindings; TYPE_ID, since, time, capacity, and child-verifier requirements became explicit records. Ordinary parameters kept their language-level role, with CKB data sources and authority represented separately.

An address supplied through a witness acquires authority only when a Lock Script verifies the relevant signature and message. A source-level capacity floor records a requirement; funding remains the builder’s responsibility. TYPE_ID metadata records a construction plan, while commitment belongs to transaction and chain evidence. 0.14 put those distinctions in the reported contract.

The boundary took several passes to settle. The bundled multisig.cell example still carried discarded 64-byte signature-looking payloads. In 0.22 I removed them and renamed the remaining records as non-cryptographic approvals. The old example looked more convincing than it deserved.

0.15–0.16: ProofPlan and builder evidence

By May, CellScript could describe much more of a transaction, but each claim’s discharge owner was still ambiguous: compiler, generated verifier, transaction builder, or chain.

A contract could state an invariant together with its trigger, scope, reads, and expected coverage. Cell identity and destruction stopped being implied by action names. ProofPlan gave reviewers one place to see the obligations behind an action or Lock Script.

The first version of that machinery recorded some aggregate claims for audit without generated runtime checks. Those entries were labelled “metadata-only” so reviewers could distinguish recorded intent from verifier coverage.

Hardening had found failure and status paths drifting into ordinary values, while lifecycle operations hidden inside branches received stronger descriptions than the analysis supported. I rejected those paths while the analysis caught up.

0.16 exposed those records to builders. A transaction builder could inspect the required Cells and CellDeps, witness and capacity duties, and signing assumptions before signing. Reviewers could compare proof and deployment facts between builds without diffing large metadata files by hand.

NovaSeal entered the repository in the same period as a serious proposal package with local evidence tooling. Its local devnet runs remained proposal evidence. Public Bitcoin SPV evidence, an independently reviewed BIP340 verifier, a live shared CellDep, and profile-specific attestations kept their own acceptance requirements. The separate NovaSeal thread covers those protocol questions.

The 0.16 patch releases came directly from builder friction. 0.16.1 made the first Cell in token, launch, AMM, and NFT lifecycles explicit instead of asking an external builder to know a test harness convention.

0.16.2 fixed a concrete builder failure. A builder could take an active action artifact such as token_mint_with_authority.elf and use it as the passive Type Script identity of a new Token Cell. CKB then executed that action wrapper during Cell creation, the wrapper looked for action witness bytes, and the transaction failed with entry-witness-abi-invalid. The external swap builder made the confusion impossible to dismiss as a documentation problem. 0.16.2 added compiler-owned passive resource identities and made the builder checks reject both scoped action artifacts and always_success fixtures in production shapes.

0.17–0.20: research and package integration

0.17, 0.18, and 0.19 formed one overlapping research and integration window. This report treats 0.20 as the public checkpoint.

The iCKB work compared selected CellScript behaviour with the original protocol under CKB-VM. Its production-equivalence status remained NOT_PROVEN; complete owner-authorisation fixtures, receipt decoding, DAO accounting, and production manifest closure remained open.

At the same time, the package and deployment model was changing underneath the compiler. By 0.20, a project included a source graph, manifest, lockfile, deployment record, and hashes connecting the build contract to deployed identity.

Multi-file packages and exact imports now worked as one source graph. Generated TypeScript builders could consume compiler metadata. Source packages could be installed and checked through the Registry path, deployment identity could be compared with live CKB RPC facts, and the adapter gave wallets and relayers a documented integration boundary. The browser Playground used the same package direction while compiling locally.

The 0.16–0.20 update covers that package and build work in more detail.

July and August: packages, Registry, and external evidence

Packages and builders moved evidence between people and tools, often long after the original compile. That hand-off needed a stable record in place of a folder of loosely related JSON files.

0.21–0.22: compile receipts and evidence tiers

Compile receipts put the source hash, metadata, ProofPlan, graph view, and artifact hashes in one record that the compiler or publisher could sign. ProtocolGraph showed state transitions beside their linked obligations without pretending to be a new consensus layer. Common xUDT conservation checks moved from descriptive metadata into executable coverage, and actions claiming a state transition had to use an edge declared by the corresponding flow.

An audit pipeline, builder, or Registry could now consume a stable record instead of reconstructing a build from loosely related files. The receipt signature authenticates the record. Proof ownership remains in the evidence tiers, while capacity, dry-run, and commitment require transaction and chain evidence.

0.21.1 was a documentation-only patch because the README had not fully caught up with the 0.21.0 release claim. The compiler and runtime were byte-identical to 0.21.0.

Every ProofPlan obligation received one of six tiers: checked by the compiler, checked by generated runtime code, waiting for a runtime helper, owed by the builder, metadata-only, or owed by chain evidence. Reviewers can now identify the owner of every outstanding obligation behind a green build.

The same rule applied to the language surface. Transaction reads became typed, read-only views; quantification and collection operations received explicit bounds; and capability rules became closed and versioned. Borrowed views had compile-time escape and lifecycle-authority checks. Forms without a finished runtime correspondence stayed fail-closed for production.

The release gate adopted the same ownership model. Separate reports covered compiler and builder results, runtime and transaction execution, artifact measurements, and chain evidence. The full gate pinned CKB source and binary provenance and covered the complete bundled action and Lock Script matrix.

0.23: Edition 2026 and the production Registry

0.23 changed less of the visible language than some earlier releases. Most of the work was operational: turning the Registry design into a running service.

Every package now had to say edition = "2026". The edition covered source meaning, while target, assurance level, witness placement, and metadata schema kept their own versions. Compiler SemVer was limited to compiler release identity.

Parameterized CKB entries also gained one canonical home: WitnessArgs.input_type on the selected Script group. This removed an old raw witness shortcut and kept CellScript arguments away from the Lock Script’s signature field. Existing projects migrated their builders and persisted records together.

The migration caught four CKB-VM crypto fixtures that still put raw CSARGv1 bytes directly in the witness. They were rebuilt through WitnessArgs.input_type, and the raw form stayed as an explicit negative case. A permissive compatibility reader would have allowed the repository itself to keep using the shortcut.

The Registry could now accept a source package, queue isolated compiler verification, publish an immutable snapshot, and serve it to a fresh consumer. CellScript source packages remained dependency-resolving; executables and other artifact classes kept separate evidence and consumption paths.

User-facing paths shipped alongside the service. First publication gained a short browser authorisation flow while the private publishing key stayed in the local keychain. Pudge Testnet used a sandbox isolated from the production Registry. The Playground began preserving local work and the last successful output, with recovery after a compiler-worker failure.

The production work included database migration and immutable object storage. Verification jobs used bounded queues and leases. Backup and restore drills, environment separation, and worker-backed status reporting completed the service contract.

0.24: verified artifact bundles

After the 0.23 Registry went live, artifact explanation still depended mainly on the compiler that produced the artifact.

0.24 adds a smaller, compiler-independent checker. A CKB build is now a four-file bundle: the ELF, compile metadata, a canonical lowering record, and a canonical source map. The checker reads the bundle and recomputes a bounded set of structural facts without loading the compiler front end or code generator.

The checker validates ELF shape and control flow together with stack and ABI contracts. It also checks call and syscall use and binds hashes to source ranges. Business intent remains a separate review boundary.

0.24 also made cellc test name the backend it actually used: simulator, CKB-VM, or an explicit compile-only choice. Reports classify simulator results as development evidence and CKB-VM results as runtime evidence; deployment remains a separate state.

Cell.lock is now authoritative. Selection happens during an explicit lock or update operation, and ordinary builds consume the exact manifest, source, feature, test, and CKB environment graph. The lockfile migration removed mutable version selection from audited builds.

LS-IDL uses a similarly narrow boundary for Lock Script interfaces. The Registry stores the exact interface bytes and binds them to the executable and deployed Script identity. That establishes schema, suffix, and Script identity; implementation correctness and security review remain separate.

Four months of change, side by side

Area 0.12 0.24
Unit of work A compiler input and its bundled release context. A manifest-bound package graph with exact runtime, test, feature, source, and environment identities.
Build output CKB ELF or assembly plus a metadata sidecar. A four-file CKB ELF bundle with metadata, verified lowering record, and source map.
Main trust anchor The compiler, its metadata validator, and the local acceptance harness. The compiler plus a separately packaged checker whose dependency graph excludes compiler front-end and code-generation components.
Testing A strong bundled local CKB acceptance suite and compiler/policy tests. Explicit simulator and CKB-VM package scenarios, kept separate from the stateful CKB release oracle and chain evidence.
Builder hand-off ABI, witness, scheduler, constraint, and transaction-measurement reports. Builder assumptions, transaction validation, generated builders, canonical witness placement, lock/deployment identity, and receipts.
Distribution A narrow crates.io package and early Registry design discussion. A live source/artifact Registry with immutable snapshots, evidence states, testnet isolation, and least-privilege artifact verification.
Interface discovery Compiler ABI inspection and documentation. Byte-exact LS-IDL publication and lookup by deployed Lock Script identity.
Compatibility Compiler version carried most of the visible release identity. Edition 2026, compiler SemVer, target, assurance, ABI, metadata schema, and package graph are separate versioned contracts.
Evidence language Release gates separated bundled evidence from arbitrary-contract claims. Binding, structure, lowering, VM execution, deployment, chain state, reproducibility, and semantic equivalence are explicitly separate states.
Ecosystem posture Prove the bundled compiler surface works. Let other tools consume the result while preserving the right to distrust and re-check it.

Next Up: typed time, a few open issues,and cross-Script composition

The last two releases spent most of their energy on package graphs, Registry workers, receipts, builders, and the independent checker. The next quarter shifts more effort back to the source language and application workflow.

The first source-level priority is typed CKB time and Since values. An epoch number, a block number, a timestamp, and an encoded since value are all u64 today, although timelock, DAO, vesting, and atomic-swap contracts use them differently. The work covers the typed API, source migration, and updated example contracts, together with formatter, editor, metadata, builder, and CKB-VM coverage. The remaining open issues stay outside that commitment unless a concrete fixture pulls them in.

The other main task is cross-application composition: several independent Lock and Type Scripts evaluate the same atomic CKB transaction, each under its own rules. CellScript can describe each artifact separately, but applications still have to merge their transaction requirements and prove the result against the selected deployments.

The reference fixture will use an order Type Script, a token Type Script, and an authorization Lock Script compiled as three separate artifacts. The first ProtocolBundle will combine their builder contracts without recompiling them, reject conflicts before signing, and run every relevant Script Group against the same transaction bytes. Its negative cases cover witness-field conflicts, a wrong output index, a deployment from another network, and duplicate assignment of capacity or change.

The fixture will supply the requirements for typed cross-Script roles and exact interface-bound Script handles. The first design will cover closed roles tied to known artifacts. Open roles depend on binding a Script selected during transaction construction to a checked interface without making an on-chain verifier trust a Registry lookup. protocol syntax can follow once that runtime contract is defined.

The settlement fixture also needs a generated TypeScript builder built around CCC. It will resolve named Cells and deployments, place canonical witnesses, and handle occupied capacity, fees, and change policy explicitly. It will then validate the finished transaction, dry-run every Script Group, and hand the unsigned transaction to a wallet. Key custody and signing policy stay with the wallet. Acceptance will run from a fresh external repository without setup code copied from CellScript’s own fixtures.

The composition work is blocked on the resolver bug that allows a transitive dependency to drift across CKB network identities. A stable resolve graph and build plan will give builders and editors the same build identity. A transactional upgrade plan will show package, interface, builder, and deployment changes before updating Cell.lock.

The language comparison will build the same timelock and settlement contracts in CellScript and Rust with ckb-std, adding a C/ckb-c-stdlib baseline where it helps. For the CKB implementations, the report will record source and test size, time to the first working CKB-VM test, pre-runtime errors, remaining builder code, artifact size, and cycles. Comparisons with Move and Sui Move, Cadence, Solidity and Vyper, Cairo, LIGO/Michelson, and Argent’s app-linking work will cover ownership, transaction roles, interfaces, upgrades, and off-chain construction. VM cycle figures will stay within the comparable CKB implementations. All sources, fixtures, and rough edges will be public, including cases where CellScript loses.

Other candidates include bounded Cell-group consumption, output correspondence, and typed committed substate. They remain outside the quarter’s commitment pending a concrete protocol, an accepted runtime contract, and independent review of the consensus-facing work.

2 Likes

CellScript 0.25: More Expressive Contracts, Safer Upgrades, Stronger Verification

Short Announcement

CellScript 0.25 is out.

For contract authors, this release adds bounded non-Cell generics, Option<T>,
generic fixed arrays, full-range u128 literals, checked division and
remainder, bitwise and shift operators, recursive patterns, field-path borrows,
and labeled loop control.

For package maintainers, explicit public, public(package), and private
visibility now feeds a canonical package interface. cellc interface-diff
checks upgrades across source API, serialized layout, runtime ABI, effects,
builders, and deployment contracts.

For auditors, metadata schema 61 adds canonical typed semantics, and the
independent artifact checker binds that record through lowering to the final
RISC-V ELF with new V2419 and V2420 checks.

0.25 also fixes an unsafe bounded-collection lowering gap. Unsupported
consume_each and create_each paths now fail closed instead of compiling to
a false success. Positive runtime support remains deliberately deferred until
its consensus rules and adversarial CKB-VM evidence are complete.

For Contract Authors: More Reuse, Same Explicit Cell Ownership

Bounded generics for ordinary values

CellScript can now specialize generic value structs, enums, and pure functions
before IR lowering:

struct Pair<T: copy + drop + store + fixed + serializable + non_linear>
    has copy, drop, store, fixed, serializable, non_linear
{
    left: T,
    right: T,
}

fn first<T: copy + drop + store + fixed + serializable + non_linear>(
    pair: Pair<T>,
) -> T {
    pair.left
}

The same checked path supports imported public templates. A package can use an
alias for a dependency and specialize its generic types or functions while the
specialization remains owned by the module that declared the template.

The boundary is intentionally narrow:

  • generic instantiation is deterministic and budgeted;
  • value abilities and phantom parameters are explicit;
  • abilities such as copy, drop, and store do not grant Cell lifecycle
    authority;
  • ordinary generic containers cannot hide a resource, shared, or other
    Cell-backed value.

This gives users reusable value code without introducing an object runtime or
weakening CellScript’s linear ownership model.

Useful built-ins and complete fixed-width operations

0.25 adds or completes:

  • built-in Option<T> using the checked fixed-width generic enum path;
  • generic fixed arrays;
  • decimal u128 literals across the full u128 range;
  • checked u128 division and remainder;
  • integer &, |, ^, <<, and >> lowering;
  • exact scalar and wide division-by-zero guards.

Wide operands now use a shared stack-spilled loading path, so resolving a
dynamic Molecule field cannot overwrite a live u128 limb. Constant folding
also refuses arithmetic that would wrap even though the runtime operation
would trap. Dynamic-schema CKB-VM vectors cover u128 addition, bitwise
operations, and shifts.

Patterns that work beyond the simplest case

match now supports recursive enum, tuple, and struct patterns, plus
binding-free or-patterns. Fixed tuple and array values can be materialized and
projected through the paths needed by nested payload matches.

Exhaustiveness checking uses a bounded constructor-matrix computation. Nested
and or-pattern coverage is merged instead of being approximated from the top
level, making useful matches accepted and incomplete ones rejected before
code generation.

Clearer borrowing and loop control

Read-only Cell views can borrow a field path and reborrow from the canonical
root. The compiler still rejects escaping the view, crossing a consume or
destroy operation, or using the borrow as persistent storage.

Loops now support break, continue, and label name: for/while. Valid
targets lower to explicit control-flow graph edges; an unknown or invalid
target is a compile-time error.

For Package Maintainers: Know What An Upgrade Changes

Top-level declarations can now be marked:

public
public(package)
private

Every successful compile emits a canonical
cellscript-package-interface-v2 record and interface_hash. Edition 2026
remains source compatible. If a module mixes explicit and implicit visibility,
the compiler emits W2500 instead of silently changing the default.

Generate an interface with:

cellc interface path/to/package --output target/package.interface.json

Compare two versions with:

cellc interface-diff \
  --old target/old.interface.json \
  --new target/new.interface.json

The report separates six kinds of compatibility:

  1. source API;
  2. serialized layout;
  3. runtime ABI;
  4. effects and capabilities;
  5. generated builders;
  6. deployment contracts.

A breaking report exits with stable diagnostic E2501. Additive exports still
change the interface hash, but they are reported as compatible.

Concrete monomorphizations are implementation evidence, not public exports.
Changing a private generic use site therefore does not create a public API
break. Registry publication signs and stores the canonical interface, the API
recomputes its hash and checks upgrade compatibility at admission, and the
standalone Registry verifier checks the stored binding again.

For Auditors: Follow Typed Intent To The Machine Artifact

0.24 introduced an independently checked four-file artifact bundle:

contract.elf
contract.elf.meta.json
contract.elf.lowering.json
contract.elf.sourcemap.json

0.25 moves that boundary closer to the programmer’s intent. Metadata schema 61
adds cellscript-typed-semantics-v2, covering:

  • canonical types and layouts;
  • locals and operations;
  • control-flow blocks and calls;
  • effects and ownership;
  • borrow regions;
  • owner-qualified generic instantiations.

Verified lowering record v3 accounts for each typed block and binds its
materialized hash to the entry ABI and final machine blocks. In plain language,
the evidence chain is:

typed program -> lowering blocks -> entry ABI -> final RISC-V machine ranges

The standalone cellscript-artifact-checker verifies that chain without
loading the parser, resolver, type checker, IR, optimizer, assembler, or code
generator. Its new stable rejection classes are:

  • V2419: malformed or inconsistent typed semantics;
  • V2420: a broken typed-record, lowering, ABI, or machine binding.

Deterministic mutation tests cover both classes. This makes compiler drift and
artifact tampering easier to detect independently.

The claim remains precise: this is bounded structural and typed verification.
It is not a general proof of source-to-machine semantic equivalence, CKB-VM
execution, deployment, commitment, or mainnet acceptance.

One Registry For Every Executable Surface

0.25 adds a compiler-owned registry of the IR and runtime surfaces that can
reach an executable artifact. It generates both Markdown and JSON support
matrices and is exhaustively matched by the compiler.

That changes failure behavior in two important ways:

  • a known but incomplete executable shape is rejected in strict production
    compilation with E2105 before ASM or ELF is written;
  • a new IR variant or runtime feature cannot silently bypass the registry—it
    must be classified or compilation fails.

Fail-closed runtime helpers remain defense in depth. They are no longer used to
make an unsupported feature look production-ready.

An Important Safety Fix For Bounded Collections

During the 0.25 audit, we found a serious gap in consume_each and
create_each.

The frontend and ownership checker accepted the bounded operation, but IR
lowering replaced its body with Unit. That meant a body containing
require false could compile into an action that returned success without
scanning, checking, consuming, or creating anything.

0.25 removes that path:

  • typed IR retains the predicate or create template;
  • lowering inserts an explicit registered fail-closed call;
  • permissive artifacts return stable runtime error 24,
    collection-runtime-unsupported, in CKB-VM;
  • --production and --deny-fail-closed stop with E2105 before producing
    ASM or ELF;
  • the diagnostic names the operation, source location, missing ProofPlan tier,
    and a concrete remediation;
  • entry ABI and ProofPlan metadata no longer claim supported pointers or
    runtime-observed cardinality when no runtime scan exists.

This is a safety fix, not positive runtime support for bounded lifecycle
collections. The remaining work needs exact transaction-group selection,
canonical Cell and witness decoding, output order and one-to-one
correspondence, identity rules, capacity rules, and positive and adversarial
CKB-VM vectors. 0.25 does not invent those consensus semantics.

For Playground And VS Code Users

The browser Playground now understands the 0.25 authoring surface:

  • generics, abilities, and visibility;
  • bitwise and shift operators;
  • recursive patterns;
  • field-path borrows;
  • labeled loop control.

The compiler returns a bounded authoring summary for the Cell Flow, action,
type, and raw-metadata panels. Full public-interface, typed-semantics,
ProofPlan, and verified-artifact records remain available through native
cellc and the VS Code workflow.

The browser compiler remains metadata-only and does not emit ELF. Omitting
native-only records and the optional browser semantic language service keeps
the default WASM bundle within its 600 KB gzip budget. The VS Code extension
retains full completion, hover, definition, and native-report workflows.

For CI And Local Development

Compiler caches and gate evidence now have bounded defaults:

  • incremental caches keep the 32 most recently used identities per root;
  • managed syntax, strict-backend, and CKB-acceptance streams keep three runs
    per mode;
  • successful syntax audits discard reproducible per-case intermediates;
  • production acceptance removes its transient Cargo target and stopped-node
    database while retaining identifying reports and verified artifacts;
  • identical large files are hardlinked only after their SHA-256 identities
    match;
  • latest-<mode>.json records the exact report path, hash, size, and status;
  • cellc clean --cache also finds nested workspace cache roots.

Cleanup is confined to managed workspace and evidence roots. Symlinked managed
path components and non-regular cache payloads are rejected, cache entries are
created fresh rather than overwritten, and recency updates use create-new plus
rename. Operators can still override the retention bound for an external
archiver or an explicit debugging session.

Try CellScript 0.25

Install the exact release:

curl -fsSL \
  https://github.com/CellScript-Labs/CellScript/releases/download/v0.25.0/install.sh | sh

cellc --version

For an existing package, start with the new review surfaces:

cellc explain generics path/to/package
cellc interface path/to/package --output target/package.interface.json
cellc check --target-profile ckb --all-targets --production
cellc build --target riscv64-elf --target-profile ckb --production
cellc verify-artifact build/main.elf --verify-sources --production

When preparing an upgrade, save the old and new interfaces and run
cellc interface-diff before publishing.

1 Like

One note on the 0.25 language work:

the design and public interface for value generics are not final yet.

i am currently considering two bounded options—simplifying the public generic surface, or keeping user-defined generics package-local for the 0.25 stable line.

suggestions and concrete use cases are very welcome: [Language design] Choose a stable surface for value generics and public API signatures · Issue #23 · CellScript-Labs/CellScript · GitHub