Working with ckb-js-vm
The ckb-js-vm
is the binary name of an on-chain script that integrates QuickJS with additional glue code. It
functions similarly to the node
binary as a JavaScript engine and runtime. However, compared to node
, it has more
limited capabilities and is designed to run specifically in the CKB-VM environment. During development, you can run it
on your local machine using ckb-debugger
.
How to Build
To build ckb-js-vm, run:
git submodule update --init
make all
clang-18 is required for compilation. After building, the binary will be available at build/ckb-js-vm
.
If you need a reproducible build (ensuring the same binary is generated regardless of build environment), you can use:
bash reproducible_build.sh
ckb-js-vm Command Line Options
When an on-chain script is invoked by exec
or spawn
syscalls, it can accept command line arguments. The
ckb-js-vm supports the following options to control its execution behavior:
-c <filename>
: Compile JavaScript source code to bytecode, making it more efficient for on-chain execution-e <code>
: Execute JavaScript code directly from the command line string-r <filename>
: Read and execute JavaScript code from the specified file-t <target>
: Specify the target resource cell's code_hash and hash_type in hexadecimal format-f
: Enable file system mode, which provides support for JavaScript modules and imports
Note, the -c
and -r
options can only work with ckb-debugger
. The -c
option is particularly useful for preparing
optimized bytecode as described in the previous chapter. When no options are specified, ckb-js-vm runs in its default
mode. These command line options provide valuable debugging capabilities during development.
Compiling JavaScript into Bytecode
The ckb-js-vm includes built-in functionality for compiling JavaScript code into bytecode, which improves execution efficiency on-chain. You can use this feature as follows:
ckb-debugger --read-file hello.js --bin build/ckb-js-vm -- -c hello.bc
This command:
- Uses
--read-file hello.js
to provide the JavaScript source file to ckb-debugger - Specifies the ckb-js-vm binary with
--bin build/ckb-js-vm
- Passes the
-c hello.bc
option to ckb-js-vm (everything after--
)
The process compiles hello.js
and outputs the bytecode to hello.bc
. The --read-file
option is specific to
ckb-debugger and allows it to read a file as a data source. Command line arguments after the --
separator are passed
directly to the on-chain script, enabling the use of the -c
compilation flag.
Note that this compilation functionality requires the ckb-debugger environment and cannot work independently.
QuickJS bytecode is version-specific and not portable between different QuickJS versions. This compilation approach ensures that generated bytecode is always compatible with the exact QuickJS version used in ckb-js-vm.
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 chapter.
The subsequent code_hash
and hash_type
point to a resource cell which may contain:
- A file system
- JavaScript source code
- 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.
QuickJS Integration
ckb-js-vm is built on QuickJS, a small and embeddable JavaScript engine developed by Fabrice Bellard. QuickJS features:
- Fast and lightweight JavaScript interpreter
- Support for ES2022 features
- Small footprint suitable for embedded systems
- Efficient memory management
ckb-js-vm leverages QuickJS to provide a JavaScript runtime environment within the CKB. This integration enables:
- Running JavaScript code directly on CKB-VM
- Compiling JavaScript to bytecode for more efficient execution
- Calling syscalls
Bindings
ckb-js-vm provides bindings that allow JavaScript code to interact with the CKB blockchain through the
@ckb-js-std/bindings
module. These bindings expose CKB syscalls and other functionality to JavaScript:
- Syscalls defined in the RFC
- Hashing functions: SHA2-256, Keccak256, Blake2b, RIPEMD-160
- Cryptographic algorithms: secp256k1, Schnorr
- Miscellaneous functions: hex, base64, and SMT (Sparse Merkle Tree)
JavaScript Module System
ckb-js-vm exclusively supports ECMAScript Modules (ESM) and does not support CommonJS. This means you must use the modern ES import syntax for all module operations.
Supported Import Syntax
Use the ES import syntax to import modules:
// Importing the entire module
import * as bindings from "@ckb-js-std/bindings";
// Named imports
import { hex } from "@ckb-js-std/bindings";
// Default import (if the module has a default export)
import defaultExport from "module-name";
Unsupported CommonJS Syntax
The following CommonJS patterns are not supported and will result in errors:
// ❌ This will not work in ckb-js-vm
const bindings = require("@ckb-js-std/bindings");
// ❌ This will also not work
module.exports = { /* ... */ };
Module Resolution Rules
When importing modules in ckb-js-vm:
- Built-in modules like
@ckb-js-std/bindings
are resolved automatically - Relative imports (starting with
./
or../
) are resolved relative to the current file - Bare imports (like
import x from "module-name"
) require the file system mode to be enabled
When using file system mode, make sure your module structure follows ESM conventions with .js
or .bc
file extensions
explicitly included in import statements.