use std::collections::HashMap; use std::error::Error as StdErr; use ckb_hash::blake2b_256; use ckb_jsonrpc_types as json_types; 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, SECP256K1, }; use ckb_types::{ bytes::Bytes, core::{BlockView, ScriptHashType, TransactionView}, packed::{CellOutput, Script, WitnessArgs}, prelude::*, H256, }; use clap::Parser; /// Transfer some CKB from one sighash address to other address /// # Example: /// ./target/debug/examples/transfer_from_sighash \ /// --sender-key \ /// --receiver
\ /// --capacity 61.0 #[derive(Parser, Debug)] #[clap(author, version, about, long_about = None)] struct Args { /// The sender private key (hex string) #[clap(long, value_name = "KEY")] sender_key: H256, /// The receiver address #[clap(long, value_name = "ADDRESS")] receiver: Address, /// The capacity to transfer (unit: CKB, example: 102.43) #[clap(long, value_name = "CKB")] capacity: HumanCapacity, /// CKB rpc url #[clap(long, value_name = "URL", default_value = "http://127.0.0.1:8114")] ckb_rpc: String, } fn main() -> Result<(), Box> { // Parse arguments let args = Args::parse(); let sender_key = secp256k1::SecretKey::from_slice(args.sender_key.as_bytes()) .map_err(|err| format!("invalid sender secret key: {}", err))?; let sender = { let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &sender_key); let hash160 = blake2b_256(&pubkey.serialize()[..])[0..20].to_vec(); Script::new_builder() .code_hash(SIGHASH_TYPE_HASH.pack()) .hash_type(ScriptHashType::Type.into()) .args(Bytes::from(hash160).pack()) .build() }; let tx = build_transfer_tx(&args, sender, sender_key)?; // Send transaction let json_tx = json_types::TransactionView::from(tx); println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); let outputs_validator = Some(json_types::OutputsValidator::Passthrough); let _tx_hash = CkbRpcClient::new(args.ckb_rpc.as_str()) .send_transaction(json_tx.inner, outputs_validator) .expect("send transaction"); println!(">>> tx sent! <<<"); Ok(()) } fn build_transfer_tx( args: &Args, sender: Script, sender_key: secp256k1::SecretKey, ) -> Result> { // 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, placeholder_witness, 1000); // Build: // * CellDepResolver // * HeaderDepResolver // * CellCollector // * TransactionDependencyProvider let ckb_client = CkbRpcClient::new(args.ckb_rpc.as_str()); let cell_dep_resolver = { let genesis_block = ckb_client.get_block_by_number(0.into())?.unwrap(); DefaultCellDepResolver::from_genesis(&BlockView::from(genesis_block))? }; let header_dep_resolver = DefaultHeaderDepResolver::new(args.ckb_rpc.as_str()); let mut cell_collector = DefaultCellCollector::new(args.ckb_rpc.as_str()); let tx_dep_provider = DefaultTransactionDependencyProvider::new(args.ckb_rpc.as_str(), 10); // Build the transaction let output = CellOutput::new_builder() .lock(Script::from(&args.receiver)) .capacity(args.capacity.0.pack()) .build(); 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, )?; assert!(still_locked_groups.is_empty()); Ok(tx) }