CellScript - A DSL for Cell-Based Contracts

Hi community,

I would like to share something I’ve been working on in the past half year, and be honest about where it stands.

The Cell model is one of the most thoughtful designs I’ve encountered in blockchain architecture. CKB proved that UTXO-style state, combined with a general-purpose VM, gives you something account models simply can’t: explicit ownership, no implicit mutation, and real composability at the transaction level.

But writing Cell scripts by hand is hard. Really hard. You’re parsing witness bytes, tracking indices, encoding state into raw arrays, calling syscalls by number. It’s powerful, but it doesn’t scale to an ecosystem.

So I built CellScript.

CellScript is a domain-specific language that compiles .cell source to RISC-V ELF binaries — the same ones ckb-vm already runs. It doesn’t introduce a new VM. It doesn’t replace the CellTx envelope. It layers a type-safe, linearity-enforced programming model on top of the existing execution stack.

The Design of CellScript

The design follows the same layered philosophy that makes CKB interesting in the first place: CKB should remain the secure value and settlement layer, while richer protocol logic can be expressed, checked, and executed through Cell transitions. CellScript does not try to turn L1 into a monolithic execution computer. Instead, it makes off-layer or verifier-level state transitions more explicit, typed, and auditable.

What it gives you:

  • Typed resources: resource, shared, receipt — linear values the compiler tracks. No silent drops. No accidental copies.
  • Explicit lifecycle: consume, create, transfer, destroy, claim, settle — operations that map 1:1 to Cell transactions.
  • Effect inference: the compiler knows if your code reads, writes, creates, or destroys.
  • Scheduler metadata: for DAG-parallel execution, the compiler emits access summaries so block builders can reason about independent work.
  • Policy gates: production builds can reject incomplete or risky paths before deployment.

This is an preview release. The compiler works. The type checker catches real mistakes. The codegen produces valid ELF. But the stateful protocol semantics are still maturing, and I’m looking for people willing to break things.

If you’ve ever written a Cell script in C and thought ‘there has to be a better way’ — this is my attempt at that better way.

Try it. File issues. Tell me where the abstractions leak. Tell me where the compiler should reject something it currently accepts.

Examples cover tokens, NFTs, vesting, timelocks, multisig, and AMM pools. They’re not production contracts, they’re proofs that the language maps to real Cell patterns.


How to test the examples:

For example testing, the main Rust test entry is cargo test --locked -p cellscript --test examples. That test file covers the checked-in .cell examples under examples/ and examples/language/, including assembly/ELF compilation, metadata, schema, and backend-shape checks. The production bundled examples are amm_pool.cell, launch.cell, multisig.cell, nft.cell, timelock.cell, token.cell, and vesting.cell.

For CKB-facing acceptance, the scripts are under scripts/. The main release gate is:

./scripts/cellscript_ckb_release_gate.sh full

The lower-level CKB acceptance entry is:

./scripts/ckb_cellscript_acceptance.sh --production

There is also a wrapper for stateful local CKB scenarios:

./scripts/cellscript_ckb_stateful_scenarios.sh

Repo: Github Repo: tsukifune-kosei/CellScript
Tutorials: Home · a19q3/CellScript Wiki · GitHub
VS-Code Extension:CellScript/editors/vscode-cellscript at main · a19q3/CellScript · GitHub
Latest Release Note:
CellScript - A DSL for Cell-Based Contracts - #12 by ArthurZhang

16 Likes

awesome project! Link sharing should be ok now.

6 Likes

Just Pined this post. Glory to the builders! :partying_face:

5 Likes

Thank you @matt_ckb and @terrytai! :folded_hands:

Really appreciate the link fix and the pin. Means a lot to have the community’s support behind this.

5 Likes

因为这一条被置顶了,所以贴一下这里


中文用户可以看这条,传送门:

2 Likes

Awesome work!

What is Spora? What is the Spora target profile for?

Hi jan, it is great that you have noticed my little pet project, haha.

Spora is an very experimental GhostDAG-based cell chain inspired by a thesis by Yonatan et al in 2021, and the core design goal is to stay strictly isomorphic to the CKB cell model rather than introducing a different programming model.

The reason I started it was to explore whether the cell model could potentially scale to much higher throughput while preserving the same underlying semantics. In principle, a design like this could become an interesting direction for the future of cell-based systems if the hard problems are solved well.

Right now, though, it is still very much an experimental system. The main bottlenecks are storage blowup and, related to that, how far execution parallelization can be pushed in a clean and safe way. If those problems can eventually be solved well, then I think this line of exploration may have real long-term value for the cell model.

At the moment, most of my active work is still on CellScript rather than Spora.

The repository is here: https://github.com/tsukifune-kosei/Spora
It can already run basic transfer transactions and simple CellScript contracts.

If you have any thoughts or suggestions on the direction, I would genuinely love to hear them.

7 Likes

Interesting! I bet @nirenzang has plenty to say about DAGs :slight_smile: —he created NC-MAX after evaluating every block-DAG concept he could find at the time.

A different but intriguing idea is a transaction-DAG[1] L2: an off-chain DAG of transactions without blocks. The transaction-DAG could bring in lower latency and higher throughput, while L1, acting as a censorship-resistant verification engine, helps mitigate the transactional DAG’s limitations.

[1] The original IOTA Tangle is an example of transactional DAG, IOTA is a L1 ledger.

4 Likes

Hi Jan,

Thanks for pointing out this direction. It is quite enlightening from a scaling perspective, and it also brings out a more fundamental question, not simply DAG or not, but what the appropriate DAG ‘vertex’ should be, be it block, transaction, message, or intent.

I have actually read some of Ren Zhang’s work, and I partially agree that blockDAG approaches likely need further evolution. in particular, they probably should not be interpreted as eliminating the security-performance tradeoff altogether, especially under adversarial or near-physical-limit conditions. That actually is quite relevant in this context.

Reflecting on that, Spora is probably better treated not as a final architecture, but as a research vehicle.

As for a potential upper-layer direction, a true transaction/intent-DAG above L1 seems rather interesting.

As far as I can tell at the moment, Obyte appears closer as a structural reference for a block-free transaction fabric, while IOTA is also useful as a reference for questions around confirmation semantics, tip/frontier selection.

Will definitely explore that line more seriously once this project reaches a stable stage.


P.S. The line of thought touched on here was later crystallised into the CellFabric proposal:

6 Likes

CellScript 0.12: from syntax prototype to gated CKB tooling release

CellScript 0.12 is ready.

This release moves CellScript from a prototype-oriented DSL into an early compiler and tooling foundation for CKB-style contracts.

The short version:

0.12 proves that the bundled examples can compile, package, emit metadata, and pass strict local CKB acceptance gates under a clear release boundary.

It does not mean arbitrary new contracts are automatically production-ready.

It means the current bundled suite, compiler metadata, transaction builders, documentation, and acceptance evidence now agree on one tested boundary.

What is covered

0.12 closes the current bundled CKB example suite:

Example Actions Locks Status
amm_pool.cell 6 0 covered
launch.cell 2 0 covered
multisig.cell 8 5 covered
nft.cell 9 5 covered
timelock.cell 10 5 covered
token.cell 4 0 covered
vesting.cell 4 0 covered
Total 43 15 strict local CKB acceptance

The production gate strict-compiles the original sources under the CKB profile, deploys the bundled artifacts, and exercises every bundled business action through builder-backed local CKB transactions.

This is the main boundary of the 0.12 claim:

The bundled suite is production-gated. External contracts still need their own review, metadata checks, builder evidence, CKB acceptance reports, and security audit.

What changed

0.12 stabilises the practical toolchain around the language.

It includes:

  • .cell modules with typed declarations;
  • persistent Cell-native declarations: resource, shared, and receipt;
  • executable action and lock entries;
  • Cell effects such as consume, create, read_ref, transfer, destroy, claim, and settle;
  • RISC-V assembly and ELF output for ckb-vm-compatible execution;
  • explicit CKB target profile checks;
  • fail-closed handling for unsupported runtime assumptions;
  • entry-scoped compilation through --entry-action and --entry-lock;
  • artifact metadata sidecars;
  • cellc verify-artifact;
  • refreshed Wiki and tutorials;
  • a packaged VS Code extension with CellScript LSP support.

0.12 is deliberately precise about its supported type boundary.

Supported and documented:

  • Vec<u8>;
  • Vec<Address>;
  • Vec<Hash>;
  • documented fixed-width struct-vector paths.

Not claimed:

  • generic HashMap<K, V> runtime support;
  • complete linear ownership for arbitrary cell-backed collections;
  • automatic production readiness for external contracts.

Release evidence is now part of the workflow

A CellScript artifact should not be treated as just an ELF file.

0.12 pairs artifacts with metadata:

artifact
artifact.meta.json

The metadata exposes the compiler’s view of:

  • constraints;
  • runtime errors;
  • ABI shape;
  • CKB hash policy;
  • DepGroup policy;
  • capacity requirements;
  • transaction-size evidence;
  • cycle limits;
  • occupied-capacity evidence;
  • builder-facing assumptions.

cellc verify-artifact checks that an artifact matches its metadata sidecar and selected policy flags.

That is necessary for a production claim, but it is not sufficient by itself. A production claim also needs CKB acceptance evidence.

Useful commands

Run the production gate from the CellScript repository root:

./scripts/ckb_cellscript_acceptance.sh --production

Then validate the evidence:

python3 scripts/validate_ckb_cellscript_production_evidence.py \
  target/ckb-cellscript-acceptance/<run>/ckb-cellscript-acceptance-report.json

Several compiler-facing reports are now part of the normal review workflow:

Command Purpose
cellc metadata Emits artifact metadata and lowering/runtime details.
cellc constraints Exposes constraints, runtime errors, ABI, CKB policy, and capacity requirements.
cellc abi Explains externally callable entry ABI.
cellc entry-witness Encodes or explains parameterised entry witness payloads.
cellc scheduler-plan Reports admission and conflict policy from scheduler hints.
cellc opt-report Compares optimisation output and backend shape.
cellc ckb-hash Computes CKB Blake2b hashes for release and builder workflows.
cellc verify-artifact Verifies artifact, metadata, source hash, target profile, and production flags.

Package and editor workflow

0.12 also turns the local package workflow into something closer to a real developer toolchain.

Covered:

  • cellc init;
  • cellc build;
  • cellc check;
  • cellc fmt;
  • cellc doc;
  • cellc add --path;
  • cellc remove;
  • cellc info;
  • local path dependency checks;
  • lockfile consistency checks;
  • VS Code extension packaging;
  • CellScript LSP support.

Registry publishing and remote package resolution remain experimental and fail-closed.

What this proves

0.12 proves that the bundled production examples:

  • strict-compile under the CKB target profile;
  • produce ckb-vm-compatible artifacts;
  • emit metadata and constraints reports;
  • can be deployed in the local CKB production acceptance run;
  • have all bundled business actions exercised through builder-backed CKB transactions;
  • expose release evidence instead of leaving compiler and builder assumptions implicit.

In short:

0.12 makes CellScript reviewable as a toolchain, not only as a language idea.

What this does not claim

0.12 is still not an external security audit or a generic production certification system.

It does not claim:

  • arbitrary new contracts are production-ready;
  • external audit closure;
  • exhaustive state-space verification;
  • formal verification;
  • registry trust;
  • remote package trust;
  • public-network acceptance evidence;
  • generic map support;
  • full Cell-backed generic collection ownership.

External contracts still need their own:

  • metadata review;
  • builder evidence;
  • CKB acceptance reports;
  • malformed transaction tests;
  • capacity checks;
  • cycle and size analysis;
  • independent security review.
4 Likes

Hey Arthur, thank you!! I appreciate you introducing CellScript publicly!

From what I understand, it gives CKB and other cell-based systems a higher-level language without abstracting away the cell model. Gotta love the honesty about what already works and what is still maturing!

Looking at the repo, CellScript compiles to ckb-vm-compatible RISC-V assembly or ELF. It also makes resources, shared state, receipts, and explicit Cell effects, plus typed metadata, explicit. That keeps the language tied to the same execution target and cell effects.

As you put it:

So I’d llike to understand where CellScript draws the composability boundary on CKB today.

If protocol A already occupies a cell’s type script slot, what is the intended path for protocol B to build on top of it and add new behavior?

From the repo, I see three paths:

Which of those best matches how you think about composability on CKB today? Over time, do you expect CellScript to lean toward composing around existing cells, first-class script-to-script composition, or something else?

See also: [NIP] Allow some Output Lock Scripts to Validate.

Phroi

4 Likes

Thank you Phroi. This is a very sharp reading of the current design, and I think you are pointing at precisely the right boundary.

Reading the NIP again, I think it captures something quite fundamental about CKB composability. The problem is not merely “how do we add another script?” The deeper question is: where does a constraint live, when is it invoked, what surface of the transaction does it observe, which cells does it actually protect, and what is merely assumed by the surrounding builder or wallet?

That is the distinction I would like CellScript to preserve rather than smooth over.

I would say the three paths you identified are all real, but they live at different levels of maturity.

Today, the default and most honest path is to compose around existing cells.

If protocol A already occupies a cell’s type script slot, CellScript should not pretend that protocol B can simply attach another independent type rule to the same cell. Unless A was explicitly designed for that extension, B should usually build around A through receipts, proofs, companion cells, read-only CellDep / read_ref access, explicit witness requirements, and transaction-level constraints.

That is also how I read the NIP: not merely as a concrete mechanism proposal, but as a useful articulation of the slot and coverage problem in the CKB model.

This also matches some examples I have heard from @matt_ckb. Some developers seem to want lock scripts to assert conditions when they are attached to a cell. Others move authorization-like logic into the type script, as in rental-style designs. In UDT-like protocols, many state-transition assertions naturally live in the type script, while authorization stays in the lock; but there are also cases where people want the lock to act covenant-style and check transaction-wide output conditions.

To me, these are not isolated design oddities. They are all symptoms of the same deeper question: CKB protocols increasingly need a clearer vocabulary for different constraint categories — authorization constraints, attachment-time invariants, transaction-wide covenant constraints, type-local state-transition rules, and inter-protocol composition constraints.

The NIP’s output-lock-validation direction is highly relevant, but I would treat it with some care.

It may give us another place to express covenant-like constraints, especially where the type slot is already occupied. But I would be cautious about presenting this as if lock and type scripts were interchangeable. They are not. Their invocation conditions, coverage, and social meaning in the model are different.

For CellScript, the important thing is to make that difference inspectable:

  • what is checked by the type script;
  • what is checked by the lock script;
  • whether the check applies to inputs, outputs, or the whole transaction shape;
  • which cells are actually protected;
  • what remains only a builder, wallet, or protocol assumption.

This is exactly the kind of boundary I would like CellScript metadata and ProofPlan to surface. CellScript should first model those constraint categories explicitly, and only then lower them into lock scripts, type scripts, wrappers, receipts, companion cells, or future L1 mechanisms such as output-lock validation, depending on the target profile and the available semantics.

On the roadmap side, I should probably phrase the v0.14 “script composition” item more carefully.

The wording may have made it sound broader than I intended. Spawn/IPC is real and useful, but I do not see it as the default answer to protocol composability. It is better understood as bounded verifier composition: delegated verification, reusable verifier modules, and modular validation pipelines. It does not make a cell’s type script slot multi-tenant, and it should not be presented as doing so.

The more important semantic layer is really v0.15, where the roadmap moves into scoped invariants and Covenant ProofPlan. That is where CellScript should make trigger, scope, reads, coverage, on-chain enforcement, and builder assumptions explicit.

So the intended layering is:

  • v0.14: low-level mechanism for bounded verifier composition;
  • v0.15: semantic composability through scoped invariants and ProofPlan;
  • later: combine this with receipts, companion cells, read-only deps, and lock-side or output-lock validation patterns where the coverage is clear.

That distinction may not have been clear enough in the roadmap wording, so this is useful feedback.

In that sense, I see the NIP less as a separate concern and more as one of the clearest examples of why CellScript needs to exist. A good language layer should not make the underlying model disappear. It should give developers better instruments for seeing where the model’s real boundaries are.

3 Likes

Gotcha!! CellScript is focused on making the current Cell model easier to work with.

As it stands, CellScript is already valuable for authors who want typed shared state, receipts, locks, and transaction-shaped effects without working directly with the raw format.

Because CellScript already explores composition between cells, I had unwittingly assumed you were exploring a bit further in that direction, toward inter-protocol composing standards. If you ever move in that direction, feel free to tag me! :hugs:

Keep up the Great Work, Phroi

3 Likes

Thanks Phroi, really appreciate that.

I do think inter-protocol composition standards are a very interesting longer-term direction. My current instinct is that this probably belongs more naturally at a CellFabric layer than inside the core CellScript language alone.

The way I currently separate the responsibilities is roughly:

  • CellScript: authoring and compiling individual cell protocols, including ProofPlan metadata that makes guarantees, coverage, and assumptions explicit;
  • CellFabric: a higher inter-protocol intent and composition layer, describing how different protocols coordinate with each other before final CKB settlement.

So CellScript should first make lock/type boundaries, receipts, read_ref, witnesses, builder assumptions, and ProofPlan coverage visible for individual protocols.

A higher CellFabric layer could then describe how protocols expose intents, receipts, conflict keys, settlement plans, read/write boundaries, verifier interfaces, and ProofPlan coverage to one another.

I would not frame CellFabric as a replacement for lower-level validation changes such as output-lock validation. That still seems like a separate L1 semantics question. But I do think the broader motivation is related: protocols need safer and more explicit composition surfaces.

I had sketched a related direction here, and I suspect it may eventually be a better home for this kind of inter-protocol composition work, though it would need refinement if framed specifically around the NIP.

I will definitely tag you if I start exploring this layer more seriously.

3 Likes

@phroi

By the way, if you don’t mind, I have been looking into using real CKB protocols as maturity benchmarks for CellScript, and your iCKB seems like a natural stress test candidate.

The idea is not to make any production-equivalence claim, but to use normalised differential fixtures to expose weak spots in the language model under realistic DAO/xUDT/accounting-heavy scenarios.

In practice, this would mean comparing behaviour under the same semantic inputs, capacities, output data, deps, headers and witnesses where relevant, allowing only the script-under-test code cell/hash to differ, and then executing both sides in CKB VM/testtool with pass/pass and fail/fail alignment, plus explicit evidence: hashes, exit codes, cycles, named failure modes, etc.

I will likely start putting this together properly around the end of next month. If you are interested, I would really value your perspective when it reaches a more concrete stage.

2 Likes

Hey Arthur, you are very welcome to try iCKB in CellScript :hugs:

To make that easier, I tightened the iCKB artifacts around the relevant DAO, header, witness, and deployment details:

So if you still want to use iCKB as a maturity benchmark, the code and docs should be a much cleaner starting point now.

Happy to help once you get to concrete fixtures,
Phroi

2 Likes

Hi Phroi,

That is very helpful. :100:
I will read through these updates before turning this into concrete fixtures.
Once I have the first small fixture set ready, I will tag you again for feedback.
Really appreciate the help.

3 Likes

CellScript v0.13.2: closing the 0.13 stable line

CellScript v0.13.2 is ready.

This release closes the 0.13 line. The focus is not new syntax, but hardening: making the action model stable, keeping Cell effects visible, and proving that the bundled examples can run through real local CKB transaction flows.

The short version:

0.13 made CellScript actions explicit.
0.13.2 makes the bundled examples prove their Cell lifecycle on local CKB.

What changed in 0.13

The 0.13 line stabilised the new action model:

action fill(input: Offer, payment: Token, buyer: Address)
    -> (output: Offer, seller_payment: Token)
    move input.state: Live -> output.state: Filled
where
    require payment.amount == input.price
    require payment.symbol == input.payment_symbol

    consume payment

    create seller_payment = Token {
        amount: payment.amount,
        symbol: payment.symbol
    } with_lock(input.seller)

The important design rule is simple:

Sugar may shorten source code, but it must not hide Cell movement, ownership, or authorisation.

So CellScript does not try to make CKB look like an account-model chain.
An action still describes a Cell transformation: what is consumed, what is created, what is preserved, and what must be proven.

0.13 also added or stabilised:

  • named action outputs;
  • where proof blocks;
  • explicit move state edges;
  • protected, witness, and fixed-width lock_args;
  • bounded stack-backed Vec<T: FixedWidth> helpers;
  • lifecycle and Cell metadata helpers that lower to explicit verifier obligations.

The main 0.13.2 improvement: stateful CKB acceptance

The biggest improvement in 0.13.2 is the new strict stateful acceptance suite.

Earlier tests could show that individual actions compile and pass local CKB checks.
0.13.2 goes further: it runs multi-step business flows where outputs from one committed transaction become real inputs to later transactions.

For example:

token:
    mint -> transfer -> mint -> merge -> burn

nft:
    mint -> list -> transfer by listing

amm:
    seed -> add liquidity -> swap -> remove liquidity

multisig:
    create -> propose -> sign -> sign -> execute

That means the test gate now checks actual Cell lifecycle behaviour:

old live output
    -> consumed by next transaction
    -> no longer live

new output
    -> created by committed transaction
    -> remains live for later steps

This is the part I am happiest about.
The examples are no longer only compile-time demonstrations. They now have committed local CKB protocol-flow evidence behind them.

Current release evidence

The full release gate now covers:

7 bundled production examples
43/43 production acceptance actions
16 builder-backed lock valid/invalid spend cases
27 stateful local CKB scenarios
46 committed stateful steps
7 end-to-end business-flow scenarios
20 stateful action-branch scenarios

Each committed stateful step records:

  • dry-run evidence;
  • committed transaction evidence;
  • measured cycles;
  • transaction size;
  • occupied capacity;
  • capacity checks;
  • consumed-input liveness checks;
  • output liveness checks;
  • malformed transaction rejection where applicable.

The full release gate is:

./scripts/cellscript_ckb_release_gate.sh full

It includes compiler checks, syntax-combination audits, VS Code validation, documentation-boundary checks, builder-backed local CKB acceptance, and stateful action coverage.

What this proves

This proves that the bundled production examples:

  • compile under the CKB target profile;
  • produce CKB-compatible artifacts;
  • can participate in committed local CKB devnet transactions;
  • preserve expected Cell lifecycle behaviour across multi-step flows;
  • reject malformed variants where covered;
  • report cycles, size, and occupied capacity.

In short:

The 0.13 examples now behave like small CKB protocols, not just compiler demos.

What this does not claim

This is still not a security audit or mainnet-value certification.

0.13.2 does not claim:

  • external audit closure;
  • exhaustive state-space verification;
  • formal verification;
  • hidden signer authority;
  • hidden sighash defaults;
  • full generic maps;
  • Cell-backed generic collection ownership;
  • declarative capacity or since/header policy.

Also, witness Address is still just decoded witness data.
It is not a cryptographic authorisation proof by itself. First-class signer values and explicit sighash verification need stronger CKB binding semantics before becoming stable source syntax.

Next

The 0.13 implementation scope is now closed.

CellScript 0.14 will focus on bringing the language much closer to real CKB transaction behaviour. It will improve how CellScript describes and checks transaction sources, witnesses, lock args, ScriptGroup metadata, outputs and outputs_data, TYPE_ID creation, capacity floors, maturity/time/epoch rules, and dynamic hashing behaviour.

The main goal is simple: CellScript examples should not only compile; they should describe CKB transaction shapes in a way that is easier to test, review, and audit.

At the same time, 0.14 will stay realistic about scope. I will focus first on the CKB semantics that matter for verifier correctness: ScriptGroup and outputs_data fixtures, TYPE_ID create/continue/reject cases, and clearer time/epoch testing.

5 Likes

我记得以前也有个 CellScript 项目,最开始是一个在黑客松上产出的项目,也是类似于达到简化 CKB 合约编写难度的效果,但很可惜没有在维护了,看起来可能是一个同名的项目,可见英雄所见略同啊。

对于这个项目我有些疑问:

  1. 请问 .cell 文件如何确定合约 Entry 的?
  2. 请问 CellScript 的编程自由度对比纯 Rust 合约有哪些限制?
  3. 看起来 CellScript 是面向函数编程的?
1 Like