### Build SDK examples Source: https://github.com/nervosnetwork/ckb-sdk-rust/blob/master/README.md Command to compile the provided SDK examples. ```sh cargo build --examples ``` -------------------------------- ### Install CKB SDK dependency Source: https://github.com/nervosnetwork/ckb-sdk-rust/blob/master/README.md Add the ckb-sdk crate to your Cargo.toml file. ```toml # Cargo.toml [dependencies] ckb-sdk = "5" ``` -------------------------------- ### Main function to run Pub/Sub example Source: https://context7.com/nervosnetwork/ckb-sdk-rust/llms.txt The main function that executes the subscribe_new_blocks asynchronous function. ```rust #[tokio::main] async fn main() { subscribe_new_blocks().await.unwrap(); } ``` -------------------------------- ### Generate All Test Data via Docker Source: https://github.com/nervosnetwork/ckb-sdk-rust/blob/master/src/test-data/cycle.md Run this command in the test-data directory to generate all necessary cycle test data using Docker. Ensure Docker is installed and running. ```bash make all-via-docker ``` -------------------------------- ### Get Currently Tracked Scripts from Light Client Source: https://context7.com/nervosnetwork/ckb-sdk-rust/llms.txt Retrieves a list of all scripts currently being tracked by the light client. It iterates through the results and prints the block number from which tracking started for each script. ```rust use ckb_sdk::rpc::ckb_light_client::LightClientRpcClient; // Get currently tracked scripts let scripts = light_client.get_scripts().unwrap(); for s in scripts { println!("Tracking script from block {}", s.block_number); } ``` -------------------------------- ### Get Tip Header from Light Client Source: https://context7.com/nervosnetwork/ckb-sdk-rust/llms.txt Retrieves the latest block header known to the light client. It then prints the block number of the tip header. ```rust use ckb_sdk::rpc::ckb_light_client::LightClientRpcClient; // Get tip header let tip = light_client.get_tip_header().unwrap(); println!("Light client tip: {}", tip.inner.number); ``` -------------------------------- ### Get Total Tracked Capacity with Light Client Source: https://context7.com/nervosnetwork/ckb-sdk-rust/llms.txt Calculates the total capacity of cells being tracked by the light client for a given script. It retrieves the capacity and converts it to CKB for display. ```rust use ckb_sdk::rpc::ckb_light_client::LightClientRpcClient; use ckb_sdk::rpc::ckb_indexer::SearchKey; let capacity = light_client.get_cells_capacity(search_key).unwrap(); println!("Total tracked capacity: {} CKB", capacity.capacity.value() as f64 / 100_000_000.0); ``` -------------------------------- ### Build and test the project Source: https://github.com/nervosnetwork/ckb-sdk-rust/blob/master/README.md Commands to compile the project and execute unit tests. ```bash cargo build ``` ```bash make test ``` -------------------------------- ### Querying CKB with IndexerRpcClient Source: https://context7.com/nervosnetwork/ckb-sdk-rust/llms.txt Demonstrates how to initialize the indexer client, search for cells by lock script with filters, paginate results, and query transaction history. ```rust use ckb_sdk::rpc::IndexerRpcClient; use ckb_sdk::rpc::ckb_indexer::{SearchKey, SearchKeyFilter, ScriptType, Order}; use ckb_jsonrpc_types::{Script, JsonBytes, Uint32}; use ckb_types::H256; let indexer = IndexerRpcClient::new("https://testnet.ckb.dev:8114"); // Get indexer tip let tip = indexer.get_indexer_tip().unwrap(); if let Some(tip) = tip { println!("Indexer tip block: {} hash: {:?}", tip.block_number, tip.block_hash); } // Search for cells by lock script let lock_script = Script { code_hash: H256::from_slice(&[/* sighash code hash */]).unwrap(), hash_type: ckb_jsonrpc_types::ScriptHashType::Type, args: JsonBytes::from_vec(vec![/* blake160 of pubkey */]), }; let search_key = SearchKey { script: lock_script, script_type: ScriptType::Lock, script_search_mode: None, filter: Some(SearchKeyFilter { script: None, script_len_range: None, output_data: None, output_data_filter_mode: None, output_data_len_range: Some([0.into(), 1.into()]), // Empty data only output_capacity_range: Some([100_00000000u64.into(), u64::MAX.into()]), // Min 100 CKB block_range: None, }), with_data: Some(true), group_by_transaction: None, }; // Paginate through cells let mut cursor: Option = None; loop { let cells = indexer.get_cells( search_key.clone(), Order::Asc, Uint32::from(100u32), cursor.clone() ).unwrap(); for cell in &cells.objects { println!("Cell: {:?}, capacity: {:?}", cell.out_point, cell.output.capacity); } if cells.objects.is_empty() { break; } cursor = Some(cells.last_cursor); } // Get total capacity of cells matching search key let capacity = indexer.get_cells_capacity(search_key.clone()).unwrap(); if let Some(cap) = capacity { println!("Total capacity: {} CKB", cap.capacity.value() as f64 / 100_000_000.0); } // Search for transactions let txs = indexer.get_transactions(search_key, Order::Desc, Uint32::from(10u32), None).unwrap(); for tx in txs.objects { println!("Transaction: {:?}", tx.tx_hash()); } ``` -------------------------------- ### Initialize LightClientRpcClient Source: https://context7.com/nervosnetwork/ckb-sdk-rust/llms.txt Initializes the LightClientRpcClient to connect to a CKB light client RPC endpoint. This client enables SPV-style verification. ```rust use ckb_sdk::rpc::ckb_light_client::LightClientRpcClient; let light_client = LightClientRpcClient::new("http://127.0.0.1:9000"); ``` -------------------------------- ### Implement Multisig Transaction Workflow Source: https://context7.com/nervosnetwork/ckb-sdk-rust/llms.txt Demonstrates the full lifecycle of a multisig transaction, including configuration, building, and incremental signing. Requires valid secret keys and RPC connectivity to a CKB node. ```rust use std::collections::HashMap; use ckb_sdk::{ constants::MultisigScript, rpc::CkbRpcClient, traits::{ DefaultCellCollector, DefaultCellDepResolver, DefaultHeaderDepResolver, DefaultTransactionDependencyProvider, SecpCkbRawKeySigner, }, tx_builder::{transfer::CapacityTransferBuilder, unlock_tx, CapacityBalancer, TxBuilder}, unlock::{MultisigConfig, ScriptUnlocker, SecpMultisigScriptSigner, SecpMultisigUnlocker}, Address, HumanCapacity, ScriptId, SECP256K1, }; use ckb_types::{ bytes::Bytes, core::BlockView, packed::{CellOutput, Script, WitnessArgs}, prelude::*, H160, }; use ckb_hash::blake2b_256; // Create multisig config (2-of-3) let sighash_addresses = vec![ H160::from_slice(&[/* address 1 blake160 */]).unwrap(), H160::from_slice(&[/* address 2 blake160 */]).unwrap(), H160::from_slice(&[/* address 3 blake160 */]).unwrap(), ]; let multisig_config = MultisigConfig::new_with( MultisigScript::V2, sighash_addresses, 0, // require_first_n 2, // threshold ).unwrap(); // Build sender script from multisig config let sender = Script::new_builder() .code_hash(MultisigScript::V2.script_id().code_hash.pack()) .hash_type(MultisigScript::V2.script_id().hash_type) .args(Bytes::from(multisig_config.hash160().as_bytes().to_vec()).pack()) .build(); // Create balancer with multisig placeholder witness let placeholder_witness = multisig_config.placeholder_witness(); let balancer = CapacityBalancer::new_simple(sender.clone(), placeholder_witness, 1000); // Build multisig unlocker (initially with no keys for transaction generation) fn build_multisig_unlockers( keys: Vec, config: MultisigConfig, ) -> HashMap> { let signer = SecpCkbRawKeySigner::new_with_secret_keys(keys); let multisig_signer = SecpMultisigScriptSigner::new(Box::new(signer), config); let multisig_unlocker = SecpMultisigUnlocker::new(multisig_signer); let multisig_script_id = MultisigScript::V2.script_id(); let mut unlockers = HashMap::default(); unlockers.insert(multisig_script_id, Box::new(multisig_unlocker) as Box); unlockers } // Initialize RPC helpers let ckb_rpc = "http://127.0.0.1:8114"; let ckb_client = CkbRpcClient::new(ckb_rpc); let cell_dep_resolver = { let genesis_block = ckb_client.get_block_by_number(0.into()).unwrap().unwrap(); DefaultCellDepResolver::from_genesis(&BlockView::from(genesis_block)).unwrap() }; let header_dep_resolver = DefaultHeaderDepResolver::new(ckb_rpc); let mut cell_collector = DefaultCellCollector::new(ckb_rpc); let tx_dep_provider = DefaultTransactionDependencyProvider::new(ckb_rpc, 10); // Build balanced transaction (no signing yet) let unlockers = build_multisig_unlockers(Vec::new(), multisig_config.clone()); let receiver = Address::from_str("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqvglkprurm00l7hrs3rfqmmzyy3ll7djdsujdm6z").unwrap(); let output = CellOutput::new_builder() .lock(Script::from(&receiver)) .capacity(HumanCapacity::from_str("120.0").unwrap().0.pack()) .build(); let builder = CapacityTransferBuilder::new(vec![(output, Bytes::default())]); let tx = builder.build_balanced( &mut cell_collector, &cell_dep_resolver, &header_dep_resolver, &tx_dep_provider, &balancer, &unlockers, ).unwrap(); // Sign with first key let key1 = secp256k1::SecretKey::from_slice(&[/* key 1 bytes */]).unwrap(); let unlockers = build_multisig_unlockers(vec![key1], multisig_config.clone()); let (tx, _) = unlock_tx(tx, &tx_dep_provider, &unlockers).unwrap(); // Sign with second key (reaches threshold) let key2 = secp256k1::SecretKey::from_slice(&[/* key 2 bytes */]).unwrap(); let unlockers = build_multisig_unlockers(vec![key2], multisig_config.clone()); let (tx, still_locked) = unlock_tx(tx, &tx_dep_provider, &unlockers).unwrap(); // Transaction is now fully signed assert!(still_locked.is_empty()); println!("Multisig transaction ready: {:?}", tx.hash()); ``` -------------------------------- ### Subscribe to Multiple Topics with Pub/Sub Client Source: https://context7.com/nervosnetwork/ckb-sdk-rust/llms.txt Connects to a CKB node and subscribes to a list of topics including 'new_tip_header', 'new_tip_block', and 'new_transaction'. It also demonstrates dynamically adding a subscription for 'proposed_transaction'. ```rust use ckb_sdk::pubsub::Client; use ckb_jsonrpc_types::HeaderView; use ckb_types::core::HeaderView as CoreHeaderView; use tokio::net::TcpStream; use futures::StreamExt; // Subscribe to multiple topics async fn subscribe_multiple_topics() -> std::io::Result<()> { let tcp = TcpStream::connect("127.0.0.1:18114").await?; let client = Client::new(tcp); let topics = vec!["new_tip_header", "new_tip_block", "new_transaction"]; let mut handle = client.subscribe_list::(topics.into_iter()).await?; // Get subscribed topic info println!("Subscribed topics: {:?}", handle.topics().collect::>()); println!("Subscription IDs: {:?}", handle.ids().collect::>()); // Add another subscription dynamically let handle = handle.subscribe("proposed_transaction").await?; // Unsubscribe from a topic // handle.unsubscribe("new_transaction").await?; // Process events while let Some(result) = handle.next().await { if let Ok((topic, value)) = result { println!("Event from {}: ?::", topic, value); } } Ok(()) } ``` -------------------------------- ### Query Cells using Light Client Source: https://context7.com/nervosnetwork/ckb-sdk-rust/llms.txt Performs a cell query similar to a full node indexer using the light client. It constructs a SearchKey to specify the script and other criteria, then retrieves a paginated list of cells. ```rust use ckb_sdk::rpc::ckb_light_client::LightClientRpcClient; use ckb_sdk::rpc::ckb_indexer::{SearchKey, ScriptType, Order}; use ckb_jsonrpc_types::{Script, JsonBytes, Uint32}; use ckb_types::H256; let search_key = SearchKey { script: script.clone(), script_type: ScriptType::Lock, script_search_mode: None, filter: None, with_data: Some(true), group_by_transaction: None, }; let cells = light_client.get_cells(search_key.clone(), Order::Asc, Uint32::from(100u32), None).unwrap(); for cell in cells.objects { println!("Cell: ?::", cell.out_point); } ``` -------------------------------- ### Fetch Header with Proof using Light Client Source: https://context7.com/nervosnetwork/ckb-sdk-rust/llms.txt Fetches a specific block header using its hash. The result indicates whether the header was fetched, is currently being fetched, was recently added, or is not found. ```rust use ckb_sdk::rpc::ckb_light_client::{LightClientRpcClient, FetchStatus}; use ckb_types::H256; let block_hash: H256 = "0x...".parse().unwrap(); let fetch_result = light_client.fetch_header(block_hash).unwrap(); match fetch_result { FetchStatus::Fetched { data } => println!("Header: ?::", data), FetchStatus::Fetching { first_sent } => println!("Fetching since: {}", first_sent), FetchStatus::Added { timestamp } => println!("Added at: {}", timestamp), FetchStatus::NotFound => println!("Header not found"), } ``` -------------------------------- ### Interact with CKB RPC Source: https://github.com/nervosnetwork/ckb-sdk-rust/blob/master/README.md Initialize a client to fetch block data from a CKB node. ```rust use ckb_sdk::rpc::CkbRpcClient; let mut ckb_client = CkbRpcClient::new("https://testnet.ckb.dev"); let block = ckb_client.get_block_by_number(0.into()).unwrap(); println!("block: {}", serde_json::to_string_pretty(&block).unwrap()); ``` -------------------------------- ### Subscribe to New Blocks with Pub/Sub Client Source: https://context7.com/nervosnetwork/ckb-sdk-rust/llms.txt Connects to a CKB node's TCP port and subscribes to 'new_tip_header' events. Processes incoming block headers, converting them to CoreHeaderView for detailed information. ```rust use ckb_sdk::pubsub::Client; use ckb_jsonrpc_types::HeaderView; use ckb_types::core::HeaderView as CoreHeaderView; use tokio::net::TcpStream; use futures::StreamExt; // Connect to CKB node TCP port (not HTTP) async fn subscribe_new_blocks() -> std::io::Result<()> { let tcp = TcpStream::connect("127.0.0.1:18114").await?; let client = Client::new(tcp); // Subscribe to new tip headers let mut handle = client.subscribe::("new_tip_header").await?; while let Some(result) = handle.next().await { match result { Ok((topic, header)) => { let core_header: CoreHeaderView = header.into(); println!( "New block - Topic: {}, Number: {}, Hash: ?::", topic, core_header.number(), core_header.hash() ); } Err(e) => eprintln!("Error: {}", e), } } Ok(()) } ``` -------------------------------- ### Send Transaction via Light Client Source: https://context7.com/nervosnetwork/ckb-sdk-rust/llms.txt Submits a transaction to the CKB network through the light client. It returns the transaction hash upon successful submission. ```rust use ckb_sdk::rpc::ckb_light_client::LightClientRpcClient; use ckb_jsonrpc_types::Transaction; let tx: Transaction = /* your transaction */; let tx_hash = light_client.send_transaction(tx).unwrap(); println!("Sent via light client: ?::", tx_hash); ``` -------------------------------- ### Register Scripts for Tracking with Light Client Source: https://context7.com/nervosnetwork/ckb-sdk-rust/llms.txt Defines a script (e.g., sighash script) and registers it with the light client to track relevant cells. It specifies the script's code hash, hash type, and arguments. ```rust use ckb_sdk::rpc::ckb_light_client::{ScriptStatus, SetScriptsCommand}; use ckb_sdk::rpc::ckb_indexer::ScriptType; use ckb_jsonrpc_types::Script; use ckb_types::H256; use ckb_jsonrpc_types::JsonBytes; let script = Script { code_hash: H256::from_slice(&[/* sighash code hash */]).unwrap(), hash_type: ckb_jsonrpc_types::ScriptHashType::Type, args: JsonBytes::from_vec(vec![/* blake160 */]), }; let script_status = ScriptStatus { script: script.clone(), script_type: ScriptType::Lock, block_number: 0.into(), // Start tracking from genesis }; // Set scripts to track (replaces all existing) light_client.set_scripts(vec![script_status], Some(SetScriptsCommand::All)).unwrap(); ``` -------------------------------- ### LightClientRpcClient - Light Client Support Source: https://context7.com/nervosnetwork/ckb-sdk-rust/llms.txt This section details the LightClientRpcClient for CKB light client operations, enabling SPV-style verification. ```APIDOC ## LightClientRpcClient - Light Client Support The SDK provides support for CKB light client operations, enabling SPV-style verification without downloading the full blockchain. ### Initialize and Configure Light Client Initialize the client and register scripts to track. ```rust use ckb_sdk::rpc::ckb_light_client::{ LightClientRpcClient, ScriptStatus, SetScriptsCommand, FetchStatus, }; use ckb_sdk::rpc::ckb_indexer::{SearchKey, ScriptType, Order}; use ckb_jsonrpc_types::{Script, JsonBytes, Uint32}; use ckb_types::H256; let light_client = LightClientRpcClient::new("http://127.0.0.1:9000"); // Register scripts to track let script = Script { code_hash: H256::from_slice(&[/* sighash code hash */]).unwrap(), hash_type: ckb_jsonrpc_types::ScriptHashType::Type, args: JsonBytes::from_vec(vec![/* blake160 */]), }; let script_status = ScriptStatus { script: script.clone(), script_type: ScriptType::Lock, block_number: 0.into(), // Start tracking from genesis }; // Set scripts to track (replaces all existing) light_client.set_scripts(vec![script_status], Some(SetScriptsCommand::All)).unwrap(); // Get currently tracked scripts let scripts = light_client.get_scripts().unwrap(); for s in scripts { println!("Tracking script from block {}", s.block_number); } ``` ### Querying Cells and Capacity Query cells and retrieve the total tracked capacity. ```rust use ckb_sdk::rpc::ckb_light_client::LightClientRpcClient; use ckb_sdk::rpc::ckb_indexer::{SearchKey, ScriptType, Order}; use ckb_jsonrpc_types::{Script, JsonBytes, Uint32}; use ckb_types::H256; let light_client = LightClientRpcClient::new("http://127.0.0.1:9000"); // Define a script for searching let script = Script { code_hash: H256::from_slice(&[/* sighash code hash */]).unwrap(), hash_type: ckb_jsonrpc_types::ScriptHashType::Type, args: JsonBytes::from_vec(vec![/* blake160 */]), }; // Query cells (similar to full node indexer) let search_key = SearchKey { script, script_type: ScriptType::Lock, script_search_mode: None, filter: None, with_data: Some(true), group_by_transaction: None, }; let cells = light_client.get_cells(search_key.clone(), Order::Asc, Uint32::from(100u32), None).unwrap(); for cell in cells.objects { println!("Cell: ?::", cell.out_point); } // Get cells capacity let capacity = light_client.get_cells_capacity(search_key).unwrap(); println!("Total tracked capacity: {} CKB", capacity.capacity.value() as f64 / 100_000_000.0); ``` ### Fetching Headers and Tip Information Retrieve block headers and the current tip header. ```rust use ckb_sdk::rpc::ckb_light_client::{LightClientRpcClient, FetchStatus}; use ckb_types::H256; let light_client = LightClientRpcClient::new("http://127.0.0.1:9000"); // Fetch header with proof let block_hash: H256 = "0x...".parse().unwrap(); let fetch_result = light_client.fetch_header(block_hash).unwrap(); match fetch_result { FetchStatus::Fetched { data } => println!("Header: ?::", data), FetchStatus::Fetching { first_sent } => println!("Fetching since: {}", first_sent), FetchStatus::Added { timestamp } => println!("Added at: {}", timestamp), FetchStatus::NotFound => println!("Header not found"), } // Get tip header let tip = light_client.get_tip_header().unwrap(); println!("Light client tip: {}", tip.inner.number); ``` ### Sending Transactions Submit a transaction through the light client. ```rust use ckb_sdk::rpc::ckb_light_client::LightClientRpcClient; use ckb_jsonrpc_types::Transaction; let light_client = LightClientRpcClient::new("http://127.0.0.1:9000"); // Send transaction through light client let tx: Transaction = /* your transaction */; let tx_hash = light_client.send_transaction(tx).unwrap(); println!("Sent via light client: ?::", tx_hash); ``` ``` -------------------------------- ### Compile Molecule Schema to Rust Source: https://github.com/nervosnetwork/ckb-sdk-rust/blob/master/src/types/schemas/README.md Use the moleculec compiler to generate Rust code from a schema file, piping the output through rustfmt. ```bash moleculec --language rust --schema-file omni_lock.mol | rustfmt > omni_lock.rs ``` -------------------------------- ### Build a Nervos DAO deposit transaction Source: https://context7.com/nervosnetwork/ckb-sdk-rust/llms.txt Initializes the builder with a receiver, sets up necessary resolvers and collectors, and uses a capacity balancer to fund the transaction. ```rust use ckb_sdk::{ rpc::CkbRpcClient, traits::{ DefaultCellCollector, DefaultCellDepResolver, DefaultHeaderDepResolver, DefaultTransactionDependencyProvider, }, tx_builder::{ dao::{DaoDepositBuilder, DaoDepositReceiver}, CapacityBalancer, TxBuilder, }, Address, }; use ckb_types::{ bytes::Bytes, core::BlockView, packed::{Script, WitnessArgs}, prelude::*, }; use std::str::FromStr; let ckb_rpc = "https://testnet.ckb.dev:8114"; // Depositor's lock script let depositor = Address::from_str( "ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqf7v2xsyj0p8szesqrwqapvvygpc8hzg9sku954v" ).unwrap(); let lock_script: Script = Script::from(&depositor); // Create deposit receiver (102 CKB minimum for DAO cell) let deposit_capacity = 102_00000000u64; // 102 CKB in shannons let receiver = DaoDepositReceiver::new(lock_script.clone(), deposit_capacity); // Build DAO deposit transaction let builder = DaoDepositBuilder::new(vec![receiver]); // Initialize helpers let ckb_client = CkbRpcClient::new(ckb_rpc); let cell_dep_resolver = { let genesis_block = ckb_client.get_block_by_number(0.into()).unwrap().unwrap(); DefaultCellDepResolver::from_genesis(&BlockView::from(genesis_block)).unwrap() }; let header_dep_resolver = DefaultHeaderDepResolver::new(ckb_rpc); let mut cell_collector = DefaultCellCollector::new(ckb_rpc); let tx_dep_provider = DefaultTransactionDependencyProvider::new(ckb_rpc, 10); // Build base transaction (inputs will be added by balancer) let base_tx = builder.build_base( &mut cell_collector, &cell_dep_resolver, &header_dep_resolver, &tx_dep_provider, ).unwrap(); // Add capacity balancer to fund the deposit let placeholder_witness = WitnessArgs::new_builder() .lock(Some(Bytes::from(vec![0u8; 65])).pack()) .build(); let balancer = CapacityBalancer::new_simple(lock_script, placeholder_witness, 1000); // The transaction will have: // - Inputs: collected from depositor's cells // - Outputs: DAO deposit cell with type script, change cell // - Cell deps: DAO cell dep, secp256k1 cell dep println!("DAO deposit transaction created with {} outputs", base_tx.outputs().len()); ``` -------------------------------- ### Pub/Sub Client - Real-time Subscriptions Source: https://context7.com/nervosnetwork/ckb-sdk-rust/llms.txt This section covers the Pub/Sub client for subscribing to real-time CKB node events such as new blocks and transactions. ```APIDOC ## PubSub Client - Real-time Subscriptions The SDK provides a pub/sub client for subscribing to real-time CKB node events like new blocks, new transactions, and proposed transactions. ### Subscribe to New Blocks This example demonstrates how to connect to the CKB node's TCP port and subscribe to new tip headers. ```rust use ckb_sdk::pubsub::Client; use ckb_jsonrpc_types::HeaderView; use ckb_types::core::HeaderView as CoreHeaderView; use tokio::net::TcpStream; use futures::StreamExt; // Connect to CKB node TCP port (not HTTP) async fn subscribe_new_blocks() -> std::io::Result<()> { let tcp = TcpStream::connect("127.0.0.1:18114").await?; let client = Client::new(tcp); // Subscribe to new tip headers let mut handle = client.subscribe::("new_tip_header").await?; while let Some(result) = handle.next().await { match result { Ok((topic, header)) => { let core_header: CoreHeaderView = header.into(); println!( "New block - Topic: {}, Number: {}, Hash: ?::", topic, core_header.number(), core_header.hash() ); } Err(e) => eprintln!("Error: {}", e), } } Ok(()) } ``` ### Subscribe to Multiple Topics This example shows how to subscribe to multiple topics simultaneously and process events. ```rust use ckb_sdk::pubsub::Client; use tokio::net::TcpStream; use futures::StreamExt; // Subscribe to multiple topics async fn subscribe_multiple_topics() -> std::io::Result<()> { let tcp = TcpStream::connect("127.0.0.1:18114").await?; let client = Client::new(tcp); let topics = vec!["new_tip_header", "new_tip_block", "new_transaction"]; let mut handle = client.subscribe_list::(topics.into_iter()).await?; // Get subscribed topic info println!("Subscribed topics: ?::", handle.topics().collect::>()); println!("Subscription IDs: ?::", handle.ids().collect::>()); // Add another subscription dynamically let handle = handle.subscribe("proposed_transaction").await?; // Unsubscribe from a topic // handle.unsubscribe("new_transaction").await?; // Process events while let Some(result) = handle.next().await { if let Ok((topic, value)) = result { println!("Event from {}: ?::", topic, value); } } Ok(()) } ``` ``` -------------------------------- ### Configure Omni Lock Scripts Source: https://context7.com/nervosnetwork/ckb-sdk-rust/llms.txt Configure Omni Lock for various authentication modes including pubkey hash, Ethereum, multisig, and time locks. Use the resulting configuration to build lock scripts and placeholder witnesses. ```rust use ckb_sdk::{ unlock::{OmniLockConfig, IdentityFlag, OmniLockAcpConfig}, }; use ckb_types::{H160, H256}; // Create Omni Lock config with pubkey hash (sighash mode) let pubkey_hash = H160::from_slice(&[/* 20 bytes */]).unwrap(); let omni_config = OmniLockConfig::new_pubkey_hash(pubkey_hash); // Create Omni Lock config with Ethereum address let eth_address = H160::from_slice(&[/* 20 bytes keccak256(pubkey)[12..32] */]).unwrap(); let omni_config = OmniLockConfig::new_ethereum(eth_address); // Create Omni Lock config with multisig use ckb_sdk::unlock::MultisigConfig; let multisig_config = MultisigConfig::new_with( ckb_sdk::constants::MultisigScript::V2, vec![ H160::from_slice(&[/* address 1 */]).unwrap(), H160::from_slice(&[/* address 2 */]).unwrap(), ], 0, // require_first_n 2, // threshold ).unwrap(); let omni_config = OmniLockConfig::new_multisig(multisig_config); // Enable Anyone-Can-Pay mode let mut omni_config = OmniLockConfig::new_pubkey_hash(pubkey_hash); let acp_config = OmniLockAcpConfig { ckb_minimum: Some(100_00000000), // 100 CKB minimum udt_minimum: Some(1000), }; omni_config.set_acp_config(Some(acp_config)); // Enable time lock use ckb_sdk::types::{Since, SinceType}; let since = Since::new(SinceType::EpochNumberWithFraction, 100, false); omni_config.set_time_lock_config(Some(since)); // Build lock script from config let omni_lock_code_hash = H256::from_slice(&[/* omni lock code hash */]).unwrap(); let lock_script = omni_config.build_lock_script(omni_lock_code_hash.clone()); println!("Omni lock script: {:?}", lock_script); // Get placeholder witness for transaction building let placeholder = omni_config.placeholder_witness(); println!("Placeholder witness size: {}", placeholder.as_bytes().len()); ``` -------------------------------- ### Import Generated Molecule Types Source: https://github.com/nervosnetwork/ckb-sdk-rust/blob/master/src/types/schemas/README.md Replace existing imports with these lines to correctly reference the CKB types and molecule prelude. ```rust #![allow(unused_imports)] use ckb_types::molecule; use ckb_types::packed::*; use ckb_types::prelude::*; ``` ```rust use super::blockchain::*; use molecule::prelude::*; ``` -------------------------------- ### Sign Transactions with SecpSighashUnlocker Source: https://context7.com/nervosnetwork/ckb-sdk-rust/llms.txt Use SecpSighashUnlocker to sign standard secp256k1-blake160 lock scripts. Requires a registered unlocker in the transaction builder. ```rust use std::collections::HashMap; use ckb_sdk::{ constants::SIGHASH_TYPE_HASH, traits::SecpCkbRawKeySigner, tx_builder::unlock_tx, unlock::{ScriptUnlocker, SecpSighashUnlocker}, ScriptId, }; use ckb_types::core::TransactionView; // Create signer with secret keys let secret_key = secp256k1::SecretKey::from_slice(&[/* 32 bytes */]).unwrap(); let signer = SecpCkbRawKeySigner::new_with_secret_keys(vec![secret_key]); // Create unlocker from signer let sighash_unlocker = SecpSighashUnlocker::from(Box::new(signer) as Box<_>); // Register unlocker by script ID let sighash_script_id = ScriptId::new_type(SIGHASH_TYPE_HASH.clone()); let mut unlockers: HashMap> = HashMap::new(); unlockers.insert(sighash_script_id, Box::new(sighash_unlocker)); // Unlock (sign) a balanced transaction let tx_dep_provider = /* DefaultTransactionDependencyProvider */; let balanced_tx: TransactionView = /* transaction with placeholder witnesses */; let (signed_tx, still_locked_groups) = unlock_tx( balanced_tx, &tx_dep_provider, &unlockers, ).unwrap(); if still_locked_groups.is_empty() { println!("Transaction fully signed!"); println!("Transaction hash: {:?}", signed_tx.hash()); } else { println!("Still need signatures for {} script groups", still_locked_groups.len()); } ``` -------------------------------- ### Managing CKB Addresses Source: https://context7.com/nervosnetwork/ckb-sdk-rust/llms.txt Covers parsing, generating, and converting CKB addresses, including support for legacy and CKB2021 formats, multisig, and custom script addresses. ```rust use ckb_sdk::types::{Address, AddressPayload, NetworkType, CodeHashIndex}; use ckb_types::packed::Script; use ckb_types::{H160, h160}; use std::str::FromStr; // Parse address from string let addr_str = "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqgvf0k9sc40s3azmpfvhyuudhahpsj72tsr8cx3d"; let address = Address::from_str(addr_str).unwrap(); // Get address components let network = address.network(); // NetworkType::Mainnet or NetworkType::Testnet let payload = address.payload(); let is_new_format = address.is_new(); // CKB2021 format // Convert to lock script let lock_script: Script = Script::from(&address); println!("Lock script code_hash: {:?}", lock_script.code_hash()); println!("Lock script args: {:?}", lock_script.args()); // Create address from public key hash (sighash address) let pubkey_hash = h160!("0xb39bbc0b3673c7d36450bc14cfcdad2d559c6c64"); let payload = AddressPayload::from_pubkey_hash(pubkey_hash); let new_address = Address::new(NetworkType::Mainnet, payload, true); // true = CKB2021 format println!("New format address: {}", new_address); // Create address from public key use secp256k1::PublicKey; use ckb_crypto::secp::SECP256K1; let secret_key = secp256k1::SecretKey::from_slice(&[/* 32 bytes */]).unwrap(); let pubkey = PublicKey::from_secret_key(&SECP256K1, &secret_key); let payload = AddressPayload::from_pubkey(&pubkey); let address = Address::new(NetworkType::Testnet, payload, true); println!("Address from pubkey: {}", address); // Create multisig short address let multisig_hash = h160!("0x4fb2be2e5d0c1a3b8694f832350a33c1685d477a"); let payload = AddressPayload::new_short(CodeHashIndex::Multisig, multisig_hash); let multisig_addr = Address::new(NetworkType::Mainnet, payload, false); println!("Multisig address: {}", multisig_addr); // Create full address with custom code hash use ckb_types::core::ScriptHashType; use ckb_types::packed::Byte32; use ckb_types::bytes::Bytes; let code_hash = Byte32::from_slice(&[/* 32 bytes */]).unwrap(); let args = Bytes::from(vec![/* args bytes */]); let payload = AddressPayload::new_full(ScriptHashType::Type, code_hash, args); let full_address = Address::new(NetworkType::Mainnet, payload, true); println!("Full address: {}", full_address); ``` -------------------------------- ### Construct a transfer transaction Source: https://github.com/nervosnetwork/ckb-sdk-rust/blob/master/README.md Build and sign a transaction to transfer CKB capacity. Ensure keys and addresses are handled securely for production use. ```rust use ckb_sdk::{ constants::SIGHASH_TYPE_HASH, rpc::CkbRpcClient, traits::{ DefaultCellCollector, DefaultCellDepResolver, DefaultHeaderDepResolver, DefaultTransactionDependencyProvider, SecpCkbRawKeySigner, }, tx_builder::{transfer::CapacityTransferBuilder, CapacityBalancer, TxBuilder}, unlock::{ScriptUnlocker, SecpSighashUnlocker}, Address, HumanCapacity, ScriptId, }; use ckb_types::{ bytes::Bytes, core::BlockView, h256, packed::{CellOutput, Script, WitnessArgs}, prelude::*, }; use std::{collections::HashMap, str::FromStr}; // Prepare the necessary data for a CKB transaction: // * set the RPC endpoint for the testnet // * define the sender's address and secret key // * define the recipient's address // * specify the capacity to transfer let ckb_rpc = "https://testnet.ckb.dev:8114"; let sender = Address::from_str("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqf7v2xsyj0p8szesqrwqapvvygpc8hzg9sku954v").unwrap(); let sender_key = secp256k1::SecretKey::from_slice( h256!("0xef4dfe655b3df20838bdd16e20afc70dfc1b9c3e87c54c276820315a570e6555").as_bytes(), ) .unwrap(); let receiver = Address::from_str("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqvglkprurm00l7hrs3rfqmmzyy3ll7djdsujdm6z").unwrap(); let capacity = HumanCapacity::from_str("100.0").unwrap(); // Build ScriptUnlocker let signer = SecpCkbRawKeySigner::new_with_secret_keys(vec![sender_key]); let sighash_unlocker = SecpSighashUnlocker::from(Box::new(signer) as Box<_>); let sighash_script_id = ScriptId::new_type(SIGHASH_TYPE_HASH.clone()); let mut unlockers = HashMap::default(); unlockers.insert( sighash_script_id, Box::new(sighash_unlocker) as Box, ); // Build CapacityBalancer let placeholder_witness = WitnessArgs::new_builder() .lock(Some(Bytes::from(vec![0u8; 65])).pack()) .build(); let balancer = CapacityBalancer::new_simple(sender.payload().into(), placeholder_witness, 1000); // Build: // * CellDepResolver // * HeaderDepResolver // * CellCollector // * TransactionDependencyProvider let mut ckb_client = CkbRpcClient::new(ckb_rpc); let cell_dep_resolver = { let genesis_block = ckb_client.get_block_by_number(0.into()).unwrap().unwrap(); DefaultCellDepResolver::from_genesis(&BlockView::from(genesis_block)).unwrap() }; let header_dep_resolver = DefaultHeaderDepResolver::new(ckb_rpc); let mut cell_collector = DefaultCellCollector::new(ckb_rpc); let tx_dep_provider = DefaultTransactionDependencyProvider::new(ckb_rpc, 10); // Build the transaction let output = CellOutput::new_builder() .lock(Script::from(&receiver)) .capacity(capacity.0) .build(); let builder = CapacityTransferBuilder::new(vec![(output, Bytes::default())]); let (_tx, _) = builder .build_unlocked( &mut cell_collector, &cell_dep_resolver, &header_dep_resolver, &tx_dep_provider, &balancer, &unlockers, ) .unwrap(); ``` -------------------------------- ### Generate a new CKB address Source: https://github.com/nervosnetwork/ckb-sdk-rust/blob/master/README.md Derives a public key from a random private key and creates a Mainnet address payload. ```rust use ckb_sdk::types::{Address, AddressPayload, NetworkType}; use rand::Rng; let mut rng = rand::thread_rng(); let privkey_bytes: [u8; 32] = rng.gen(); let secp_secret_key = secp256k1::SecretKey::from_slice(&privkey_bytes).unwrap(); let pubkey = secp256k1::PublicKey::from_secret_key(&ckb_crypto::secp::SECP256K1, &secp_secret_key); let payload = AddressPayload::from_pubkey(&pubkey); let address = Address::new(NetworkType::Mainnet, payload, true); println!("address: {}", address.to_string()); ``` -------------------------------- ### Register Module in mod.rs Source: https://github.com/nervosnetwork/ckb-sdk-rust/blob/master/src/types/schemas/README.md Add the generated module to the project's module hierarchy. ```rust #[allow(clippy::all)] pub mod omni_lock; ``` -------------------------------- ### Build Nervos DAO Withdrawal Transaction Source: https://context7.com/nervosnetwork/ckb-sdk-rust/llms.txt Creates a Phase 2 withdrawal transaction to claim deposited CKB and interest. Requires a prepared cell out_point from the Phase 1 transaction. ```rust use ckb_sdk::{ rpc::CkbRpcClient, traits::{ DefaultCellCollector, DefaultCellDepResolver, DefaultHeaderDepResolver, DefaultTransactionDependencyProvider, }, tx_builder::{ dao::{DaoWithdrawBuilder, DaoWithdrawItem, DaoWithdrawReceiver}, TxBuilder, }, }; use ckb_types::{ bytes::Bytes, core::{BlockView, FeeRate}, packed::{OutPoint, Script, WitnessArgs}, prelude::*, H256, }; let ckb_rpc = "https://testnet.ckb.dev:8114"; // Prepared cell out_point (from Phase 1 prepare transaction) let prepare_tx_hash: H256 = "0x...".parse().unwrap(); let prepare_out_point = OutPoint::new_builder() .tx_hash(prepare_tx_hash.pack()) .index(0u32.pack()) .build(); // Placeholder witness for sighash unlock let init_witness = WitnessArgs::new_builder() .lock(Some(Bytes::from(vec![0u8; 65])).pack()) .build(); let withdraw_item = DaoWithdrawItem::new(prepare_out_point, Some(init_witness)); // Receiver lock script for withdrawn CKB let receiver_script = Script::new_builder() .code_hash([/* sighash code hash */].pack()) .hash_type(ckb_types::core::ScriptHashType::Type) .args(Bytes::from(vec![/* blake160 */]).pack()) .build(); // Receiver with automatic fee deduction from withdrawn capacity let receiver = DaoWithdrawReceiver::LockScript { script: receiver_script, fee_rate: Some(FeeRate::from_u64(1000)), }; let builder = DaoWithdrawBuilder::new(vec![withdraw_item], receiver); // Initialize helpers let ckb_client = CkbRpcClient::new(ckb_rpc); let cell_dep_resolver = { let genesis_block = ckb_client.get_block_by_number(0.into()).unwrap().unwrap(); DefaultCellDepResolver::from_genesis(&BlockView::from(genesis_block)).unwrap() }; let header_dep_resolver = DefaultHeaderDepResolver::new(ckb_rpc); let mut cell_collector = DefaultCellCollector::new(ckb_rpc); let tx_dep_provider = DefaultTransactionDependencyProvider::new(ckb_rpc, 10); // Build withdrawal transaction // The since field is automatically calculated for lock period let tx = builder.build_base( &mut cell_collector, &cell_dep_resolver, &header_dep_resolver, &tx_dep_provider, ).unwrap(); // The transaction includes: // - Input: prepared cell with since constraint (lock period) // - Output: receiver cell with interest included // - Header deps: deposit header, prepare header // - Witness: header index for DAO calculation println!("DAO withdraw transaction: {:?}", tx.hash()); ``` -------------------------------- ### Synchronous CKB Node RPC Client Source: https://context7.com/nervosnetwork/ckb-sdk-rust/llms.txt Use CkbRpcClient for synchronous interactions with CKB nodes via JSON-RPC. It supports various RPC methods for chain, indexer, transaction pool, and network operations. Ensure you have a valid RPC endpoint. ```rust use ckb_sdk::rpc::CkbRpcClient; use ckb_types::H256; // Create a new RPC client let mut client = CkbRpcClient::new("https://testnet.ckb.dev:8114"); // Get tip block number let tip_number = client.get_tip_block_number().unwrap(); println!("Current tip block number: {}", tip_number); // Get genesis block let genesis_block = client.get_block_by_number(0.into()).unwrap().unwrap(); println!("Genesis block hash: {:?}", genesis_block.header.hash); // Get block by hash let block_hash: H256 = genesis_block.header.hash; let block = client.get_block(block_hash.clone()).unwrap(); // Get transaction by hash let tx_hash: H256 = "0x...".parse().unwrap(); let tx_response = client.get_transaction(tx_hash).unwrap(); if let Some(response) = tx_response { println!("Transaction status: {:?}", response.tx_status.status); } // Get live cell information use ckb_jsonrpc_types::OutPoint; let out_point = OutPoint { tx_hash: tx_hash.clone(), index: 0.into(), }; let cell = client.get_live_cell(out_point, true).unwrap(); println!("Cell status: {:?}", cell.status); // Send transaction use ckb_jsonrpc_types::{Transaction, OutputsValidator}; let tx: Transaction = /* your transaction */; let tx_hash = client.send_transaction(tx, Some(OutputsValidator::Passthrough)).unwrap(); println!("Sent transaction: {:?}", tx_hash); // Get fee rate statistics let fee_stats = client.get_fee_rate_statistics(None).unwrap(); if let Some(stats) = fee_stats { println!("Median fee rate: {:?}", stats.median); } // Query blockchain info let chain_info = client.get_blockchain_info().unwrap(); println!("Chain: {}, Epoch: {:?}", chain_info.chain, chain_info.epoch); ```