A smart contract demo built with ckb-js-vm in TypeScript that demonstrates how to use a Hashed Timelock Contract (HTLC) to enable trustless, conditional payments on CKB.
A Hashed Timelock Contract (HTLC) is a type of smart contract that enables trustless, conditional payments between two parties. It uses a cryptographic hash to ensure that funds can only be claimed if the recipient provides the correct preimage of the hash within a set time limit. If the preimage isn’t revealed before the deadline, the sender can safely refund their funds.
- Atomic swaps: Enable cross-chain token exchanges without needing a centralized exchange or trusted third party.
- Payment channels: Power off-chain micropayment systems (like Lightning Network) by ensuring safe settlement on-chain.
- Escrow protection: Guarantee that either the recipient gets paid if conditions are met, or the sender gets refunded if not.
- Trustless automation: Remove the need for intermediaries by enforcing conditions (hash + timelock) directly on-chain.
On CKB, this HTLC is enforced through a Lock Script that protects a Cell with two mutually-exclusive branches:
- Fund:
- Anyone who reveals the correct preimage of the stored hash can unlock the Cell.
- The output must go to the recipient address specified when the Cell was created.
- Refund:
- If the timelock has expired (using CKB’s
sincefield in block height or epoch), the original depositor can reclaim the funds. - The refund transaction must include another input Cell locked by the same refund Lock Script to validate the refund address.
- If the timelock has expired (using CKB’s
⚠️ This is an educational prototype—do not use it in production or with real assets.
This project uses the CKB JavaScript VM (ckb-js-vm) to write smart contracts in typescript. The contracts are compiled to bytecode and can be deployed to the CKB blockchain.
simple-htlc
├── contracts # Smart contract source code
│ └── htlc # HTLC contract implementation
│ └── src
│ ├── index.ts # Main entry point
│ ├── type.ts # Type definitions
│ └── util.ts # Utility functions
├── deployment # Deployment configuration and scripts
│ ├── README.md # Deployment instructions
│ ├── scripts.json # Deployment script config
│ └── system-scripts.json # System-level script definitions
├── scripts # Helper scripts for building and deploying
│ ├── add-contract.js # Register a new contract
│ ├── build-all.js # Build all contracts
│ ├── build-contract.js # Build a single contract
│ └── deploy.js # Deploy contracts
├── tests # Test suite
│ ├── core # Core testing utilities
│ │ ├── helper.ts # Helper functions
│ │ ├── type.ts # Type definitions
│ │ └── util.ts # Utility functions
│ ├── htlc.devnet.test.ts # Integration tests on devnet
│ ├── htlc.mock.test.ts # Mock-based tests
│ └── htlc.unit.test.ts # Unit tests
├── jest.config.cjs # Jest test runner configuration
├── package.json # Project metadata, dependencies, and scripts
├── tsconfig.base.json # Shared TypeScript config
├── tsconfig.json # Project-specific TypeScript config
└── README.md # Project overview and documentation- Node.js (v18 or later)
- pnpm package manager
- Install dependencies:
pnpm install
Build all contracts:
pnpm run buildBuild a specific contract:
pnpm run build:contract hello-worldRun all tests:
pnpm testRun tests for a specific contract:
pnpm test -- hello-worldCreate a new contract:
pnpm run add-contract my-new-contractThis will:
- Create a new contract directory under
contracts/ - Generate a basic contract template
- Create a corresponding test file
- Edit your contract in
contracts/<contract-name>/src/index.typescript - Build the contract:
pnpm run build:contract <contract-name> - Run tests:
pnpm test -- <contract-name>
All contracts are built to the global dist/ directory:
dist/{contract-name}.js- Bundled JavaScript codedist/{contract-name}.bc- Compiled bytecode for CKB execution
Tests use the ckb-testtool framework to simulate CKB blockchain execution. Each test:
- Sets up a mock CKB environment
- Deploys the contract bytecode
- Executes transactions
- Verifies results
build- Build all contractsbuild:contract <name>- Build a specific contracttest- Run all testsadd-contract <name>- Add a new contractdeploy- Deploy contracts to CKB networkclean- Remove all build outputsformat- Format code with Prettier
Deploy your contracts to CKB networks using the built-in deploy script:
# Deploy to devnet (default)
pnpm run deploy
# Deploy to testnet
pnpm run deploy -- --network testnet
# Deploy to mainnet
pnpm run deploy -- --network mainnet# Deploy with upgradable type ID
pnpm run deploy -- --network testnet --type-id
# Deploy with custom private key
pnpm run deploy -- --network testnet --privkey 0x...
# Combine multiple options
pnpm run deploy -- --network testnet --type-id --privkey 0x...--network <network>- Target network:devnet,testnet, ormainnet(default:devnet)--privkey <privkey>- Private key for deployment (default: uses offckb's deployer account)--type-id- Enable upgradable type ID for contract updates
After successful deployment, artifacts are saved to the deployment/ directory:
deployment/scripts.json- Contract script informationdeployment/<network>/<contract>/deployment.toml- Deployment configurationdeployment/<network>/<contract>/migrations/- Migration history
@ckb-js-std/bindings- CKB JavaScript VM bindings@ckb-js-std/core- Core CKB JavaScript utilities
ckb-testtool- Testing framework for CKB contractsesbuild- Fast JavaScript bundlerjest- JavaScript testing frameworktypescript- TypeScript compilerts-jest- TypeScript support for Jestprettier- Code formatter
MIT