Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Theme
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Create API Key
Add Docs
CKB SDK
https://github.com/nervosnetwork/ckb-sdk-rust
Admin
CKB SDK is a Rust library providing RPC access, data structures, transaction assembly, and signature
...
Tokens:
13,411
Snippets:
70
Trust Score:
7.9
Update:
1 week ago
Context
Skills
Chat
Benchmark
74.1
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# CKB SDK Rust CKB SDK Rust is a comprehensive Rust library for building applications on the Nervos CKB blockchain. It provides essential features including RPC access to CKB nodes, data structure definitions for various CKB concepts, support for assembling and balancing CKB transactions, and signature unlocking support for commonly used lock scripts such as sighash, multisig, and omni-lock. The SDK enables developers to interact seamlessly with CKB by offering both synchronous and asynchronous APIs for node communication, cell collection, transaction building, and signing. It supports multiple network types (mainnet, testnet, dev) and provides high-level abstractions for common operations like capacity transfers, DAO deposits/withdrawals, and SUDT token transactions, while also allowing low-level customization when needed. ## CkbRpcClient - Node RPC Client The `CkbRpcClient` provides a convenient interface for interacting with CKB nodes via JSON-RPC. It supports all standard CKB RPC methods including chain queries, indexer operations, transaction pool management, and network operations. ```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); ``` ## CkbRpcAsyncClient - Async Node RPC Client The `CkbRpcAsyncClient` provides the same functionality as `CkbRpcClient` but with async/await support for non-blocking operations in async runtimes. ```rust use ckb_sdk::rpc::CkbRpcAsyncClient; use ckb_types::H256; async fn fetch_blockchain_data() -> Result<(), Box<dyn std::error::Error>> { let client = CkbRpcAsyncClient::new("https://testnet.ckb.dev:8114"); // Async block queries let tip_number = client.get_tip_block_number().await?; println!("Tip block: {}", tip_number); let tip_header = client.get_tip_header().await?; println!("Tip header hash: {:?}", tip_header.hash); // Get block with cycles information let block_hash: H256 = tip_header.hash; if let Some((block_view, cycles)) = client.get_block_with_cycles(block_hash).await? { println!("Block transactions: {}", block_view.transactions.len()); println!("Cycles: {:?}", cycles); } // Estimate transaction cycles use ckb_jsonrpc_types::Transaction; let tx: Transaction = /* your transaction */; let estimate = client.estimate_cycles(tx).await?; println!("Estimated cycles: {:?}", estimate.cycles); Ok(()) } // Run in tokio runtime #[tokio::main] async fn main() { fetch_blockchain_data().await.unwrap(); } ``` ## IndexerRpcClient - Cell Indexer Client The `IndexerRpcClient` provides methods to query cells and transactions using the built-in CKB indexer. It supports powerful filtering by script, data, capacity range, and block range. ```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<JsonBytes> = 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()); } ``` ## Address - CKB Address Management The `Address` type handles CKB address parsing, generation, and conversion between different formats. It supports both legacy short addresses and the CKB2021 full address format. ```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); ``` ## CapacityTransferBuilder - Simple CKB Transfers The `CapacityTransferBuilder` provides a high-level interface for building CKB capacity transfer transactions. It handles cell collection, capacity balancing, and witness placeholder generation automatically. ```rust use std::collections::HashMap; 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::str::FromStr; // Setup sender and receiver 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(); // Build unlocker 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<dyn ScriptUnlocker>); // Build capacity balancer with placeholder witness 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 // fee rate in shannons per byte ); // Initialize helpers let ckb_rpc = "https://testnet.ckb.dev:8114"; 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 transfer output let capacity = HumanCapacity::from_str("100.0").unwrap(); // 100 CKB let output = CellOutput::new_builder() .lock(Script::from(&receiver)) .capacity(capacity.0.pack()) .build(); // Build and sign transaction let builder = CapacityTransferBuilder::new(vec![(output, Bytes::default())]); let (tx, still_locked_groups) = builder.build_unlocked( &mut cell_collector, &cell_dep_resolver, &header_dep_resolver, &tx_dep_provider, &balancer, &unlockers, ).unwrap(); assert!(still_locked_groups.is_empty()); println!("Transaction hash: {:?}", tx.hash()); ``` ## MultisigConfig - Multisig Transaction Support The SDK supports multisig transactions with configurable threshold and required-first-n parameters. This enables m-of-n signature schemes for enhanced security. ```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<secp256k1::SecretKey>, config: MultisigConfig, ) -> HashMap<ScriptId, Box<dyn ScriptUnlocker>> { 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<dyn ScriptUnlocker>); 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()); ``` ## DaoDepositBuilder - Nervos DAO Deposit The `DaoDepositBuilder` creates transactions for depositing CKB into the Nervos DAO to earn interest. ```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()); ``` ## DaoWithdrawBuilder - Nervos DAO Withdrawal The `DaoWithdrawBuilder` creates Phase 2 withdrawal transactions to claim deposited CKB plus interest from the Nervos DAO. This follows the prepare transaction (Phase 1). ```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()); ``` ## DefaultCellCollector - Cell Collection The `DefaultCellCollector` queries and collects live cells from the CKB indexer based on specified criteria such as lock script, type script, data length, and capacity range. ```rust use ckb_sdk::{ rpc::CkbRpcClient, traits::{ CellCollector, CellQueryOptions, DefaultCellCollector, MaturityOption, PrimaryScriptType, QueryOrder, ValueRangeOption, }, }; use ckb_types::packed::Script; let ckb_rpc = "https://testnet.ckb.dev:8114"; let mut cell_collector = DefaultCellCollector::new(ckb_rpc); // Build lock script to search for let lock_script = Script::new_builder() .code_hash([/* code hash bytes */].pack()) .hash_type(ckb_types::core::ScriptHashType::Type) .args([/* args bytes */].pack()) .build(); // Create query options let mut query = CellQueryOptions::new_lock(lock_script.clone()); // Filter by secondary script (type script when primary is lock) query.secondary_script_len_range = Some(ValueRangeOption::new_exact(0)); // No type script // Filter by data length query.data_len_range = Some(ValueRangeOption::new_exact(0)); // Empty data only // Filter by capacity query.capacity_range = Some(ValueRangeOption::new_min(100_00000000)); // Min 100 CKB // Filter by block range query.block_range = Some(ValueRangeOption::new(0, 1000000)); // Set maturity filter (for cellbase cells) query.maturity = MaturityOption::Mature; // Set collection limit query.min_total_capacity = 200_00000000; // Stop when 200 CKB collected // Query order query.order = QueryOrder::Asc; // Collect cells (apply_changes=true marks cells as used) let (cells, total_capacity) = cell_collector.collect_live_cells(&query, true).unwrap(); println!("Collected {} cells with total {} shannons", cells.len(), total_capacity); for cell in &cells { println!(" OutPoint: {:?}", cell.out_point); println!(" Capacity: {} CKB", cell.output.capacity().unpack() as f64 / 100_000_000.0); println!(" Block: {}", cell.block_number); } // Lock a specific cell manually (prevent reuse) let out_point = cells[0].out_point.clone(); let tip_block = 1000000u64; cell_collector.lock_cell(out_point, tip_block).unwrap(); // Reset collector state (clear locked cells) cell_collector.reset(); ``` ## SecpSighashUnlocker - Sighash Transaction Signing The `SecpSighashUnlocker` provides transaction signing for standard sighash (secp256k1-blake160) lock scripts, the most common lock type in CKB. ```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<ScriptId, Box<dyn ScriptUnlocker>> = 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()); } ``` ## OmniLockConfig - Omni Lock Support The SDK supports Omni Lock, a flexible lock script that enables multiple authentication modes including pubkey hash, Ethereum signatures, multisig, time locks, and administrator mode. ```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()); ``` ## 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. ```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::<HeaderView>("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 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::<serde_json::Value, _, _>(topics.into_iter()).await?; // Get subscribed topic info println!("Subscribed topics: {:?}", handle.topics().collect::<Vec<_>>()); println!("Subscription IDs: {:?}", handle.ids().collect::<Vec<_>>()); // 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(()) } #[tokio::main] async fn main() { subscribe_new_blocks().await.unwrap(); } ``` ## LightClientRpcClient - Light Client Support The SDK provides support for CKB light client operations, enabling SPV-style verification without downloading the full blockchain. ```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); } // 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); // 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); // Send transaction through light client 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); ``` ## Summary CKB SDK Rust provides a complete toolkit for building applications on the Nervos CKB blockchain. The main use cases include: building and signing standard CKB transfers with sighash or multisig scripts, interacting with the Nervos DAO for deposits and withdrawals to earn interest, managing User Defined Tokens (UDT/SUDT), querying blockchain state through the RPC client and indexer, and subscribing to real-time events via pub/sub. The SDK also supports advanced features like Omni Lock for flexible authentication, light client operations for SPV-style verification, and Anyone-Can-Pay cells for receiving payments. Integration patterns typically involve creating an RPC client for node communication, setting up cell collectors and dependency resolvers from the genesis block, building transactions with appropriate builders (CapacityTransferBuilder, DaoDepositBuilder, etc.), configuring capacity balancers with placeholder witnesses for fee estimation, registering unlockers for the lock scripts being used, and finally building unlocked transactions that are ready to broadcast. The SDK's trait-based design allows swapping implementations for testing (dummy implementations) or custom scenarios (light client implementations), making it flexible for various deployment environments from full nodes to light clients.