Make CKB contracts interact like building blocks
A powerful Inter-Process Communication protocol that enables seamless communication between CKB scripts. Build modular, reusable, and composable smart contracts.
Build smarter contracts with modular, reusable components
Split complex logic into separate scripts that communicate seamlessly. Build once, reuse everywhere.
Use proc-macros to automatically generate IPC boilerplate code. Focus on your business logic, not plumbing.
Built-in support for serde serialization. Any type that implements Serialize/Deserialize works out of the box.
Strong type checking at compile time. Catch errors before deployment, not in production.
Uses VLQ (Variable-Length Quantity) encoding for efficient wire format. Minimal overhead, maximum performance.
Implementations in both Rust and C. Choose the language that fits your project best.
Simple concepts, powerful results
See how easy it is to use CKB Script IPC
use alloc::string::String;
// Define your IPC interface with a simple trait
#[ckb_script_ipc::service]
pub trait World {
fn hello(name: String) -> Result<String, u64>;
}
// That's it! The proc-macro generates:
// - WorldClient for calling the service
// - WorldServer trait for implementing the service
// - All serialization/deserialization code
use crate::def::World;
use ckb_script_ipc_common::spawn::run_server;
struct WorldServer;
impl World for WorldServer {
// Implement your method logic
fn hello(&mut self, name: String) -> Result<String, u64> {
if name == "error" {
Err(1)
} else {
Ok(format!("hello, {}", name))
}
}
}
pub fn server_entry() -> Result<(), Error> {
let world = WorldServer;
// Start the server - it runs in an infinite loop
run_server(world.server())
}
use crate::def::WorldClient;
use ckb_script_ipc_common::spawn::spawn_server;
use ckb_std::ckb_constants::Source;
pub fn client_entry() -> Result<(), Error> {
// 1. Spawn the server process
let (read_pipe, write_pipe) = spawn_server(
0,
Source::CellDep,
&[CString::new("demo").unwrap().as_ref()],
)?;
// 2. Create the client
let mut client = WorldClient::new(read_pipe, write_pipe);
// 3. Make IPC calls - just like calling a local function!
let result = client.hello("world".into())?;
// result = "hello, world"
Ok(())
}
See what you can build with CKB Script IPC
Build reusable cryptographic services that multiple scripts can share:
Offload heavy computation to specialized scripts:
Build contracts that work together like LEGO blocks:
Start building with CKB Script IPC in minutes
# Cargo.toml
[dependencies]
ckb-script-ipc = "x.x.x"
ckb-script-ipc-common = "x.x.x"
serde = { version = "1.0", default-features = false, features = ["derive"] }
#[ckb_script_ipc::service]
pub trait MyService {
fn my_method(arg: String) -> Result<String, u64>;
}
Implement the trait for your server, spawn it from your client, and start making calls!
View Full Demo →Any type that implements Serialize and Deserialize from serde can be used. This includes all primitive types, standard library types, and custom structs annotated with #[derive(Serialize, Deserialize)].
CKB Script IPC uses serde_json for message serialization. This provides a good balance between human readability and compatibility.
The code operates within a script process that is part of a transaction, and can only run on the same machine. This is more akin to Inter-Process Communication (IPC) rather than Remote Procedure Call (RPC). RPC typically includes features like encryption, authentication, retries, and scaling that aren't relevant in this context.
Use cargo-expand to view the code generated by the #[ckb_script_ipc::service] macro.
Yes! Enable the std feature in ckb-script-ipc-common and use native::spawn_server to interact with on-chain script services from native code.
Start building modular, composable CKB contracts today.