This is the single source of truth for all development workflows in OffCKB.
- Development Guide
- Node.js >= 20.0.0
- pnpm (install via
npm install -g pnpm)
git clone --recurse-submodules https://github.com/ckb-devrel/offckb.git
cd offckb
pnpm installIf you already cloned without
--recurse-submodules, rungit submodule update --init --recursive.
pnpm start
# This runs ts-node-dev with hot-reload on src/cli.tspnpm build
# tsc → ncc bundle → build/index.js| Command | Description |
|---|---|
pnpm start |
Run CLI in dev mode (ts-node-dev, hot-reload) |
pnpm build |
Build production bundle to build/ |
pnpm lint |
Run ESLint on src/**/*.ts |
pnpm lint:fix |
Run ESLint with auto-fix |
pnpm fmt |
Format code with Prettier |
pnpm typecheck |
TypeScript type check (tsc --noEmit) |
pnpm test |
Run unit tests (Jest) |
pnpm test:watch |
Run tests in watch mode |
pnpm test:coverage |
Run tests with coverage report |
pnpm test:ci |
Run tests with coverage (CI mode) |
pnpm changeset |
Create a changeset file for your PR |
pnpm clean |
Remove dist/, build/, target/ |
Key directories:
| Directory | Role |
|---|---|
src/cli.ts |
Entry point — registers all CLI commands via commander |
src/cmd/ |
Command implementations, one file per CLI command (e.g. node.ts, create.ts, deploy.ts) |
src/cfg/ |
Configuration: accounts, environment paths, settings |
src/sdk/ |
CKB SDK wrappers (RPC calls, network utilities) |
src/deploy/ |
Contract deployment logic (migration, script handling, TOML generation) |
src/tools/ |
WASM debugger, RPC proxy, transaction dumper |
src/tui/ |
Terminal UI (blessed-based devnet config editor) |
src/templates/ |
Project scaffolding template processing |
src/util/ |
Shared utilities (fs, encoding, logger, validator, etc.) |
src/type/ |
Shared TypeScript type definitions |
ckb/ |
Git submodules for CKB smart contract source code, built via Makefile |
templates/v4/ |
Project scaffolding templates shipped with the CLI |
build/ |
ncc-bundled production output — never edit directly |
Conventions:
- Adding a new CLI command: create
src/cmd/<name>.ts, register it insrc/cli.ts src/cmd/*modules should usesrc/sdk/for CKB interaction andsrc/util/for helpers- Keep
src/type/base.tsfor shared types; command-specific types stay in command files - Native CKB script binaries live in
ckb/submodules — modify viaMakefile, not directly
| Branch | Purpose | CI |
|---|---|---|
develop |
Development mainline. All feature/fix branches merge here first. Version is kept up-to-date with bumps. | lint + test (matrix) |
master |
Stable release branch. Receives merges from develop for formal releases. |
lint + test + publish on tag |
feature/* |
New feature work. Branch from develop, merge back to develop. |
lint + test |
fix/* |
Bug fixes. Branch from develop, merge back to develop. |
lint + test |
v0.*.x |
Canary / maintenance branches. Pushes auto-publish canary releases to npm. | canary publish |
Normal flow — all changes go through develop first:
feature/* ──→ develop ──→ v0.*.x ──→ canary publish (npm canary tag)
└──→ master ──→ tag v*.*.* ──→ npm publish (latest)
Exception — direct commits to v0.*.x (skip develop):
Only for:
- Hotfix that needs immediate canary release
- Legacy/incompatible branches (e.g. v2, v3) that diverge from current
develop
fix/* ──→ v0.*.x (direct) ──→ canary publish
- Create a branch from
develop(e.g.feature/add-fooorfix/bar-crash) - Make your changes
- Run
pnpm changesetto generate a changeset file describing the change (required by CI)- For changes that don't need a changelog entry (docs, CI config, pure refactoring), use
pnpm changeset --empty
- For changes that don't need a changelog entry (docs, CI config, pure refactoring), use
- Commit and push. Pre-commit hooks will automatically run:
- ESLint (with auto-fix) + Prettier on staged
.tsfiles - Prettier on staged template/account files
- TypeScript type check (
tsc --noEmit)
- ESLint (with auto-fix) + Prettier on staged
- Open a PR to
develop(ormasterfor hotfix) - CI must pass:
- Nodejs CI: lint + build + format consistency check
- Test: unit tests + integration tests (Ubuntu/Windows/macOS)
- Changeset Check: verifies a changeset file is present
- Get code review, then merge
We use changesets to manage changelog entries and version bumps.
Every PR must include a changeset file. CI will block the PR otherwise.
pnpm changesetThis interactive CLI will ask you:
- Version bump type:
patch(bug fix),minor(new feature),major(breaking change) - Summary: A human-readable description of the change (this goes into CHANGELOG.md)
The command creates a markdown file in .changeset/ — commit it with your PR.
For PRs that don't affect the published package (documentation, CI, internal refactoring):
pnpm changeset --emptyThis creates a changeset that satisfies CI but won't add a CHANGELOG entry or bump the version.
During the release process, pnpm changeset version reads all accumulated changeset files, determines the version bump, updates package.json version and CHANGELOG.md, then deletes the consumed changeset files.
- Ensure
developis stable and all target PRs are merged - On
develop, run changeset version to consume accumulated changesets:This consumes allpnpm changeset version
.changeset/*.mdfiles, updatesCHANGELOG.mdand bumpspackage.jsonversion - Commit the version bump:
git add . git commit -m "chore: release v$(node -p 'require(\"./package.json\").version')"
- Merge
developintomaster - On
master, create and push the tag:git tag v$(node -p 'require("./package.json").version') git push origin master --tags - The
Publish on Tagworkflow automatically publishes to npm
Merge develop into the target v0.*.x branch (or push directly for hotfix/legacy cases). The Canary release on new commit workflow will:
- Auto-bump version with a canary prerelease suffix (e.g.
0.4.5-canary-abc1234) - Publish to npm under the
canarytag
Note: Direct commits to
v0.*.x(bypassing develop) should only be used for hotfixes or legacy branches that are incompatible with currentdevelop.
- Test files:
*.test.ts - Location:
tests/directory (orsrc/**/__tests__/for co-located tests) - Framework: Jest with ts-jest
- Run:
pnpm test
- New features/fixes must include tests
- Priority for coverage improvement:
- Pure logic modules:
src/util/,src/cfg/,src/scripts/,src/deploy/ - Command logic:
src/cmd/*(mock CKB node / file system as needed) - Template processing:
src/templates/
- Pure logic modules:
- Integration tests:
scripts/create-test.sh(runs full create → build → test cycle)
- CI enforces a minimum coverage threshold (currently 10% statements)
- The threshold will be raised as tests are added
- View coverage locally:
pnpm test:coveragethen opencoverage/index.html - Coverage is uploaded to Codecov on CI (Ubuntu only)
Prerequisites:
- rust/cargo
- capsule
- docker
Update the relevant submodule inside ckb/, then run:
make allSee Makefile for individual targets: omnilock, anyone-can-pay, xudt, spore, ckb-js-vm, nostr-lock, pw-lock, secp256k1_multisig_v2.
Edit files in ckb/devnet/.
All script configs are generated by ckb list-hashes — you don't need to manually maintain script hashes.
Edit files in templates/v4/. Template files use .template extension and are processed by src/templates/processor.ts.
Prerequisites:
- rust/cargo
- wasm32-wasip1 target:
rustup target add wasm32-wasip1 - On Linux:
sudo apt install gcc-multilib
Update the ckb-standalone-debugger submodule, then run:
make ckb-debugger