Skip to main content

CKB JS VM: Mechanism and Capabilities

The Quick Start guide mentions that JS contracts run inside the ckb-js-vm. This document provides a detailed explanation of ckb-js-vm.

Introduction

CKB contracts are based on the RISC-V architecture, which means most programming languages can be used to develop contracts for CKB. Currently, Rust and C are the most widely used languages for contract development. However, both have relatively high barriers to entry, which discourages many developers from getting started.

We hope to expand support to more languages in order to lower the entry threshold for developers.

Most beginner-friendly languages, such as Python and JavaScript, are interpreted languages. For CKB-VM, this means they must be interpreted and executed inside the virtual machine, which adds an additional layer of complexity.

JavaScript has long been one of the most popular programming languages. In the past, we attempted to run JavaScript on CKB using Duktape, a lightweight JavaScript engine. However, due to poor performance, this effort was not continued.

Later, we developed ckb-lua-vm. From a technical perspective, Lua is well-suited for contract development and offers acceptable performance. Unfortunately, Lua's limited popularity prevented it from gaining widespread adoption.

Building on these past experiences, we created ckb-js-vm, which is based on QuickJS, and built a full set of supporting tools, including:

  • JavaScript bindings with TypeScript libraries
  • A JavaScript testing framework: ckb-testtool

(QuickJS is a small and embeddable JavaScript engine. It was created by Fabrice Bellard, the author of QEMU and the original developer of FFmpeg.)

ckb-js-vm args Explanation

The ckb-js-vm script structure in molecule is below:

code_hash: <code hash of ckb-js-vm, 32 bytes>
hash_type: <hash type of ckb-js-vm, 1 byte>
args: <ckb-js-vm flags, 2 bytes> <code hash of resource cell, 32 bytes> <hash type of resource cell, 1 byte>

The first 2 bytes are parsed into an int16_t in C using little-endian format (referred to as ckb-js-vm flags). If the lowest bit of these flags is set (v & 0x01 == 1), the file system is enabled. File system functionality will be described in another section.

The subsequent code_hash and hash_type point to a resource cell which may contain:

  1. A file system
  2. JavaScript source code
  3. QuickJS bytecode

When the file system flag is enabled, the resource cell contains a file system that can also include JavaScript code. For most scenarios, QuickJS bytecode is stored in the resource cell. When an on-chain script requires extra args, they can be stored beginning at offset 35 (2 + 32 + 1). Compared to normal on-chain scripts in other languages, ckb-js-vm requires these extra 35 bytes.

Comparison Between JS Contracts and Traditional Contracts

Since JavaScript contracts are executed inside the JS-VM, creating a transaction for them introduces a few differences compared to traditional contracts (such as those written in Rust or C).

The key differences are as follows:

  • Script

    • Traditional contracts use their own code_hash + hash_type.
    • JavaScript contracts instead reference the code_hash + hash_type of the ckb-js-vm.
  • Args

    • Traditional contracts directly fill in the args field with their parameters.
    • JavaScript contracts prepend JS-specific parameters at the beginning of args (2 bytes flags + 32 bytes code hash + 1 byte hash type). This also means when reading arguments inside the contract, you need to offset by 35 bytes.
  • Cell Dependencies

    • Traditional contracts only require their own binary as a cell dependency.
    • JavaScript contracts require both the ckb-js-vm binary and the JavaScript bytecode cell as dependencies.
  • Performance

    • Traditional contracts (Rust/C) are compiled directly to RISC-V machine code, offering the best performance.
    • JavaScript contracts are interpreted by ckb-js-vm, which consumes more cycles. Bytecode execution is faster than raw JavaScript but still slower than native contracts.

When to Use JavaScript Contracts

JavaScript contracts are suitable for:

  • Prototyping: Quickly test ideas without learning Rust or C
  • Educational purposes: Learn CKB script concepts with a familiar language
  • Simple logic: Scripts with straightforward validation rules
  • Development tooling: Build internal tools and utilities

For production use cases involving complex logic or high-value assets, we recommend using Rust for better performance and security.

note

While CKB-JS-VM is not yet deployed on mainnet, you can experiment with it on devnet and testnet. Check the current deployment status in the ecosystem-scripts documentation.