CellScript

Write Cell contracts as typed transitions, not raw wire format.

target
ckb-vm RISC-V
model
schema-backed Cells
output
metadata + ProofPlan
token.cell
1module cellscript::fungible_token2// ... invariant and MintAuthority omitted3resource Token has store, create, consume, replace, burn, relock {4    amount: u64,5    symbol: [u8; 8],6}78action transfer_token(token: Token, to: Address) -> next_token: Token9where10    consume token11    create next_token = Token { amount: token.amount, symbol: token.symbol } with_lock(to)1213action burn(token: Token)14where15    assert(token.amount > 0, "cannot burn zero")16    destroy token
1module cellscript::nft2// ... constants and Metadata struct omitted3resource NFT has store, create, consume, replace, burn, relock, read_ref {4    token_id: u64,5    owner: Address,6    metadata_hash: Hash,7    royalty_recipient: Address,8    royalty_bps: u16,9}1011// ... listing and offer receipts omitted12action transfer(nft_before: NFT, to: Address) -> nft_after: NFT13where14    assert(nft_before.owner != to, "Cannot transfer to self")15    preserve nft_after from nft_before {16        token_id17        metadata_hash18        royalty_recipient19        royalty_bps20    }21    require nft_after.owner == to
1module cellscript::amm_pool2use cellscript::fungible_token::Token3// ... LPReceipt omitted4shared Pool has store, create, replace {5    token_a_symbol: [u8; 8],6    token_b_symbol: [u8; 8],7    reserve_a: u64,8    reserve_b: u64,9    total_lp: u64,10    fee_rate_bps: u16,11}1213action swap_a_for_b(pool_before: Pool, input: Token, min_output: u64, to: Address) -> (pool_after: Pool, token_out: Token)14where15    assert(input.symbol == pool_before.token_a_symbol, "wrong input token")16    let fee = input.amount * pool_before.fee_rate_bps as u64 / 1000017    let net_input = input.amount - fee18    let amount_out = pool_before.reserve_b * net_input / (pool_before.reserve_a + net_input)19    // ... preserve, require, consume, create omitted20    assert(amount_out >= min_output, "slippage exceeded")
1module cellscript::vesting2use cellscript::fungible_token::Token3// ... VestingConfig and VestingGrant fields omitted4flow VestingGrant.state {5    Granted -> Claimable;6    Granted -> FullyClaimed;7    Claimable -> FullyClaimed;8}910action claim_vested(grant: VestingGrant) -> (tokens: Token, updated_grant: VestingGrant)11    transition grant.state: Claimable -> updated_grant.state: FullyClaimed12where13    let now = env::current_timepoint()14    assert(now >= grant.cliff_timepoint, "cliff not reached")15    // ... vesting arithmetic omitted16    consume grant17    create tokens = Token { amount: claimable, symbol: grant.token_symbol } with_lock(grant.beneficiary)
$cellc examples/token.cell --target-profile ckb

Getting Started

1

Install

cargo install --path .

2

Compile

cellc examples/token.cell --target riscv64-elf --target-profile ckb

3

Check

cellc check --target-profile ckb

Compiler Workflow

CellScript source

Parse & check

Syntax, types, effects

IR + Metadata

Typed model & assurance info

Lower to RISC-V

ckb-vm codegen & optimisations

ELF / Assembly

RISC-V artefacts for ckb-vm

Core Model

CellScript keeps the contract model visible: Cell shapes, effects, locks, flows, and review metadata stay in one typed surface.

Narrow by design: not a general-purpose runtime, not a new VM, not account storage in disguise.

Owned Cell state with explicit lifecycle effects.

Example excerpt examples/token.cell
// examples/token.cell
resource Token has store, create, consume, replace, burn, relock {
    amount: u64,
    symbol: [u8; 8],
}

Shared state whose reads and replacements stay scheduler-visible.

Example excerpt examples/amm_pool.cell
// examples/amm_pool.cell
shared Pool has store, create, replace {
    token_a_symbol: [u8; 8],
    token_b_symbol: [u8; 8],
    // ... reserves, LP supply, fee rate omitted
}

Deferred proof Cells for listings, grants, proposals, and claims.

Example excerpt examples/nft.cell
// examples/nft.cell
receipt Listing has create, consume, burn {
    token_id: u64,
    seller: Address,
    price: u64,
    // ... created_at and state omitted
}

Transaction entry points with explicit consume/create/destroy effects.

Example excerpt examples/vesting.cell
// examples/vesting.cell
action claim_vested(grant: VestingGrant) -> (tokens: Token, updated_grant: VestingGrant)
    transition grant.state: Claimable -> updated_grant.state: FullyClaimed
where
    // ... timepoint checks and vesting arithmetic omitted
    consume grant

Boundary checks for protected Cells, script args, witnesses, and env reads.

Example excerpt examples/language/canonical_style.cell
// examples/language/canonical_style.cell
lock vault_owner(protected vault: Vault, lock_args owner: Address, witness claimed_owner: Address) -> bool {
    let input = source::group_input(0)
    let witness_lock = witness::lock(input)
    // ... digest and ownership checks omitted
}

Allowed state edges for Cells with lifecycle-dependent validity.

Example excerpt examples/vesting.cell
// examples/vesting.cell
flow VestingGrant.state {
    Granted -> Claimable;
    Granted -> FullyClaimed;
    Claimable -> FullyClaimed;
}

Scoped aggregate claims with trigger, scope, reads, and known gaps.

Example excerpt examples/language/v0_15_scoped_invariant.cell
// examples/language/v0_15_scoped_invariant.cell
invariant token_amount_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)
}

Typed support data for metadata records and finite protocol states.

Example excerpt examples/nft.cell
// examples/nft.cell
struct Metadata {
    name: String,
    description: String,
    image_uri: String,
    attributes: Vec<(String, String)>,
}

Type-id and field-keyed identity patterns for unique Cells.

Example excerpt examples/language/v0_15_identity_lifecycle.cell
// examples/language/v0_15_identity_lifecycle.cell
action mint_unique_nft(recipient: Address, tid: u64) -> UniqueNFT
where
    create_unique<UniqueNFT>(identity = field(token_id)) { token_id: tid, owner: recipient } with_lock(recipient)

Assurance Output

A local build emits review metadata. Treat the sidecar as useful evidence, not an authenticated proof outside the build boundary.

Schema v42

current compiler metadata schema

Source vesting.cell

shared + receipt + flow

Boundary local sidecar

validated; provenance required when shared

Tooling Surface

When

Use before review or integration.

Output

Schema, effects, source hashes, target profile.

cellc metadata examples/vesting.cell --target-profile ckb --json
When

Use when checking builder obligations.

Output

Inputs, outputs, proof branches, runtime requirements.

cellc constraints examples/vesting.cell --target-profile ckb
When

Use before handing work to an auditor.

Output

Source, assumptions, metadata, ProofPlan.

cellc audit-bundle examples/token.cell --target-profile ckb --out audit/
When

Use while authoring .cell files.

Output

Syntax, diagnostics, and local language feedback.

cellc lsp

Examples

End-to-end contract examples with Cells, actions, and constraints.

More Examples

Focused examples for locks, witness input, ownership, and capacity/time behaviour.

More Examples

Language feature samples used to pin parser, lowering, and metadata behaviour.

More Examples