diff --git a/examples/dao_deposit_example.rs b/examples/dao_deposit_example.rs new file mode 100644 index 00000000..4f4c9e8a --- /dev/null +++ b/examples/dao_deposit_example.rs @@ -0,0 +1,52 @@ +use ckb_sdk::{ + transaction::{ + builder::{CkbTransactionBuilder, SimpleTransactionBuilder}, + handler::{dao, HandlerContexts}, + input::InputIterator, + signer::{SignContexts, TransactionSigner}, + TransactionBuilderConfiguration, + }, + Address, CkbRpcClient, NetworkInfo, +}; +use ckb_types::h256; +use std::{error::Error as StdErr, str::FromStr}; + +fn main() -> Result<(), Box> { + let network_info = NetworkInfo::testnet(); + let sender = Address::from_str("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq2qf8keemy2p5uu0g0gn8cd4ju23s5269qk8rg4r")?; + let receiver=Address::from_str("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqv5dsed9par23x4g58seaw58j3ym5ml2hs8ztche")?; + + let configuration = TransactionBuilderConfiguration::new_with_network(network_info.clone())?; + + let iterator = InputIterator::new(vec![(&sender).into()], configuration.network_info()); + let mut builder = SimpleTransactionBuilder::new(configuration, iterator); + + let context = dao::DepositContext::new((&receiver).into(), 510_0000_0000u64); + let mut contexts = HandlerContexts::default(); + contexts.add_context(Box::new(context) as Box<_>); + + builder.set_change_lock((&sender).into()); + let mut tx_with_groups = builder.build(&mut contexts)?; + + let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + + let private_keys = vec![h256!( + "0x6c9ed03816e3111e49384b8d180174ad08e29feb1393ea1b51cef1c505d4e36a" + )]; + TransactionSigner::new(&network_info).sign_transaction( + &mut tx_with_groups, + &SignContexts::new_sighash_h256(private_keys)?, + )?; + + let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + + let tx_hash = CkbRpcClient::new(network_info.url.as_str()) + .send_transaction(json_tx.inner, None) + .expect("send transaction"); + // example tx: 8b7ab7770c821fa8dc70738d5d6ef43da46706541c258b9a02edf66948039798 + println!(">>> tx {} sent! <<<", tx_hash); + + Ok(()) +} diff --git a/examples/dao_withdraw_phrase1_example.rs b/examples/dao_withdraw_phrase1_example.rs new file mode 100644 index 00000000..5698c1d5 --- /dev/null +++ b/examples/dao_withdraw_phrase1_example.rs @@ -0,0 +1,61 @@ +use ckb_sdk::{ + transaction::{ + builder::{CkbTransactionBuilder, SimpleTransactionBuilder}, + handler::{dao, HandlerContexts}, + input::InputIterator, + signer::{SignContexts, TransactionSigner}, + TransactionBuilderConfiguration, + }, + Address, CkbRpcClient, NetworkInfo, +}; +use ckb_types::h256; +use std::{error::Error as StdErr, str::FromStr}; + +fn main() -> Result<(), Box> { + let network_info = NetworkInfo::testnet(); + let sender = Address::from_str("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq2qf8keemy2p5uu0g0gn8cd4ju23s5269qk8rg4r")?; + + let configuration = TransactionBuilderConfiguration::new_with_network(network_info.clone())?; + + let iterator = InputIterator::new(vec![(&sender).into()], configuration.network_info()); + let mut builder = SimpleTransactionBuilder::new(configuration, iterator); + + let mut context = dao::WithdrawPhrase1Context::new(network_info.url.clone()); + let input_outpoint = serde_json::from_str::( + r#" + { + "tx_hash": "0x2aba579894cdf5f6c4afd3ada52792c4405fe6ba64d05226fb63fa5c1ec6f666", + "index": "0x0" + } + "#, + ) + .unwrap(); + context.add_input_outpoint(input_outpoint.into(), None); + let mut contexts = HandlerContexts::default(); + contexts.add_context(Box::new(context) as Box<_>); + + builder.set_change_lock((&sender).into()); + let mut tx_with_groups = builder.build(&mut contexts)?; + + let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + + let private_keys = vec![h256!( + "0x6c9ed03816e3111e49384b8d180174ad08e29feb1393ea1b51cef1c505d4e36a" + )]; + TransactionSigner::new(&network_info).sign_transaction( + &mut tx_with_groups, + &SignContexts::new_sighash_h256(private_keys)?, + )?; + + let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + + let tx_hash = CkbRpcClient::new(network_info.url.as_str()) + .send_transaction(json_tx.inner, None) + .expect("send transaction"); + // example tx: b615b9cbb566af18dd2d860836b89e07a86dfcc7af510595dcb404f1b19e6d7e + println!(">>> tx {} sent! <<<", tx_hash); + + Ok(()) +} diff --git a/examples/dao_withdraw_phrase2_example.rs b/examples/dao_withdraw_phrase2_example.rs new file mode 100644 index 00000000..e29239b7 --- /dev/null +++ b/examples/dao_withdraw_phrase2_example.rs @@ -0,0 +1,61 @@ +use ckb_sdk::{ + transaction::{ + builder::{CkbTransactionBuilder, SimpleTransactionBuilder}, + handler::{dao, HandlerContexts}, + input::InputIterator, + signer::{SignContexts, TransactionSigner}, + TransactionBuilderConfiguration, + }, + Address, CkbRpcClient, NetworkInfo, +}; +use ckb_types::h256; +use std::{error::Error as StdErr, str::FromStr}; + +fn main() -> Result<(), Box> { + let network_info = NetworkInfo::testnet(); + let sender = Address::from_str("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq2qf8keemy2p5uu0g0gn8cd4ju23s5269qk8rg4r")?; + + let configuration = TransactionBuilderConfiguration::new_with_network(network_info.clone())?; + + let iterator = InputIterator::new(vec![(&sender).into()], configuration.network_info()); + let mut builder = SimpleTransactionBuilder::new(configuration, iterator); + + let input_outpoint = serde_json::from_str::( + r#" + { + "tx_hash": "0x770f930ed3bf35664cb6a112edce3287712f0613c74c1f1176e099ee51268489", + "index": "0x0" + } + "#, + ) + .unwrap(); + let context = + dao::WithdrawPhrase2Context::new(vec![input_outpoint.into()], network_info.url.clone()); + let mut contexts = HandlerContexts::default(); + contexts.add_context(Box::new(context) as Box<_>); + + builder.set_change_lock((&sender).into()); + let mut tx_with_groups = builder.build(&mut contexts)?; + + let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + + let private_keys = vec![h256!( + "0x6c9ed03816e3111e49384b8d180174ad08e29feb1393ea1b51cef1c505d4e36a" + )]; + TransactionSigner::new(&network_info).sign_transaction( + &mut tx_with_groups, + &SignContexts::new_sighash_h256(private_keys)?, + )?; + + let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + + let tx_hash = CkbRpcClient::new(network_info.url.as_str()) + .send_transaction(json_tx.inner, None) + .expect("send transaction"); + // example tx: 0xaae93c573848a632f06f01c7c444c90aa490253f35b4212d147882266960a267 + println!(">>> tx {} sent! <<<", tx_hash); + + Ok(()) +} diff --git a/examples/omnilock_example.rs b/examples/omnilock_example.rs new file mode 100644 index 00000000..a35b9ee2 --- /dev/null +++ b/examples/omnilock_example.rs @@ -0,0 +1,58 @@ +use ckb_sdk::{ + transaction::{ + builder::{CkbTransactionBuilder, SimpleTransactionBuilder}, + handler::{omnilock, HandlerContexts}, + input::InputIterator, + signer::{SignContexts, TransactionSigner}, + TransactionBuilderConfiguration, + }, + unlock::OmniLockConfig, + Address, CkbRpcClient, NetworkInfo, +}; +use ckb_types::{core::Capacity, h256}; +use std::{error::Error as StdErr, str::FromStr}; + +fn main() -> Result<(), Box> { + let network_info = NetworkInfo::testnet(); + let sender = Address::from_str("ckt1qrejnmlar3r452tcg57gvq8patctcgy8acync0hxfnyka35ywafvkqgqgpy7m88v3gxnn3apazvlpkkt32xz3tg5qq3kzjf3")?; + let receiver = Address::from_str("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqv5dsed9par23x4g58seaw58j3ym5ml2hs8ztche")?; + + let configuration = TransactionBuilderConfiguration::new_with_network(network_info.clone())?; + + let iterator = InputIterator::new_with_address(&[sender.clone()], configuration.network_info()); + let mut builder = SimpleTransactionBuilder::new(configuration, iterator); + + builder.add_output_from_addr(&receiver, Capacity::bytes(128)?); + builder.set_change_addr(&sender); + + let omni_cfg = OmniLockConfig::from_addr(&sender).unwrap(); + let context = omnilock::OmnilockScriptContext::new(omni_cfg.clone(), network_info.url.clone()); + + let mut contexts = HandlerContexts::default(); + contexts.add_context(Box::new(context) as Box<_>); + + let mut tx_with_groups = builder.build(&mut contexts)?; + + let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + + let private_key = h256!("0x6c9ed03816e3111e49384b8d180174ad08e29feb1393ea1b51cef1c505d4e36a"); + TransactionSigner::new(&network_info).sign_transaction( + &mut tx_with_groups, + &SignContexts::new_omnilock( + secp256k1::SecretKey::from_slice(private_key.as_bytes())?, + omni_cfg, + ), + )?; + + let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + + let tx_hash = CkbRpcClient::new(network_info.url.as_str()) + .send_transaction(json_tx.inner, None) + .expect("send transaction"); + // example tx: 0xc0c9954a3299b540e63351146a301438372abf93682d96c7cce691c334dd5757 + println!(">>> tx {} sent! <<<", tx_hash); + + Ok(()) +} diff --git a/examples/omnilock_multisig_example.rs b/examples/omnilock_multisig_example.rs new file mode 100644 index 00000000..7a48b29a --- /dev/null +++ b/examples/omnilock_multisig_example.rs @@ -0,0 +1,77 @@ +use ckb_sdk::{ + transaction::{ + builder::{CkbTransactionBuilder, SimpleTransactionBuilder}, + handler::{omnilock, HandlerContexts}, + input::InputIterator, + signer::{SignContexts, TransactionSigner}, + TransactionBuilderConfiguration, + }, + unlock::{MultisigConfig, OmniLockConfig}, + Address, CkbRpcClient, NetworkInfo, +}; +use ckb_types::{core::Capacity, h160, h256}; +use std::{error::Error as StdErr, str::FromStr}; + +fn main() -> Result<(), Box> { + let network_info = NetworkInfo::testnet(); + let sender = Address::from_str("ckt1qrejnmlar3r452tcg57gvq8patctcgy8acync0hxfnyka35ywafvkqgxhjvp3k9pf88upngryvuxc346q7fq5qmlqqlrhr0p")?; + let receiver = Address::from_str("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqv5dsed9par23x4g58seaw58j3ym5ml2hs8ztche")?; + + let configuration = TransactionBuilderConfiguration::new_with_network(network_info.clone())?; + + let iterator = InputIterator::new_with_address(&[sender.clone()], configuration.network_info()); + let mut builder = SimpleTransactionBuilder::new(configuration, iterator); + + builder.add_output_from_addr(&receiver, Capacity::bytes(128)?); + builder.set_change_addr(&sender); + + let mut omni_cfg = OmniLockConfig::from_addr(&sender).unwrap(); + let multisig_config = MultisigConfig::new_with( + vec![ + h160!("0x7336b0ba900684cb3cb00f0d46d4f64c0994a562"), + h160!("0x5724c1e3925a5206944d753a6f3edaedf977d77f"), + ], + 0, + 2, + ) + .unwrap(); + omni_cfg.set_multisig_config(Some(multisig_config)); + let context = omnilock::OmnilockScriptContext::new(omni_cfg.clone(), network_info.url.clone()); + + let mut contexts = HandlerContexts::default(); + contexts.add_context(Box::new(context) as Box<_>); + + let mut tx_with_groups = builder.build(&mut contexts)?; + + let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + + let signer = TransactionSigner::new(&network_info); + let private_key = h256!("0x7438f7b35c355e3d2fb9305167a31a72d22ddeafb80a21cc99ff6329d92e8087"); + signer.sign_transaction( + &mut tx_with_groups, + &SignContexts::new_omnilock( + secp256k1::SecretKey::from_slice(private_key.as_bytes())?, + omni_cfg.clone(), + ), + )?; + let private_key = h256!("0x4fd809631a6aa6e3bb378dd65eae5d71df895a82c91a615a1e8264741515c79c"); + signer.sign_transaction( + &mut tx_with_groups, + &SignContexts::new_omnilock( + secp256k1::SecretKey::from_slice(private_key.as_bytes())?, + omni_cfg, + ), + )?; + + let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + + let tx_hash = CkbRpcClient::new(network_info.url.as_str()) + .send_transaction(json_tx.inner, None) + .expect("send transaction"); + // example tx: 3c5062f75f8c9dc799a3286ebef070cd3aa1b51575244c912076b90cb915a374 + println!(">>> tx {} sent! <<<", tx_hash); + + Ok(()) +} diff --git a/examples/send_ckb_example.rs b/examples/send_ckb_example.rs index 35a88806..93dd85bc 100644 --- a/examples/send_ckb_example.rs +++ b/examples/send_ckb_example.rs @@ -28,7 +28,7 @@ fn main() -> Result<(), Box> { let addr = Address::from_str(sender)?; builder.add_output_from_addr(&receiver, Capacity::shannons(510_0000_0000u64)); builder.set_change_addr(&addr); - let mut tx_with_groups = builder.build(&Default::default())?; + let mut tx_with_groups = builder.build(&mut Default::default())?; let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); diff --git a/examples/send_ckb_multisig_example.rs b/examples/send_ckb_multisig_example.rs index 5b45aa94..91e42ebe 100644 --- a/examples/send_ckb_multisig_example.rs +++ b/examples/send_ckb_multisig_example.rs @@ -40,7 +40,7 @@ fn main() -> Result<(), Box> { builder.add_output_from_addr(&addr, Capacity::shannons(501_0000_0000u64)); builder.set_change_addr(&sender_addr); let mut tx_with_groups = - builder.build(&HandlerContexts::new_multisig(multisig_config.clone()))?; + builder.build(&mut HandlerContexts::new_multisig(multisig_config.clone()))?; let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); diff --git a/examples/sudt_issue_example.rs b/examples/sudt_issue_example.rs new file mode 100644 index 00000000..c70fbee2 --- /dev/null +++ b/examples/sudt_issue_example.rs @@ -0,0 +1,57 @@ +use ckb_sdk::{ + transaction::{ + builder::{CkbTransactionBuilder, SimpleTransactionBuilder}, + handler::{udt, HandlerContexts}, + input::InputIterator, + signer::{SignContexts, TransactionSigner}, + TransactionBuilderConfiguration, + }, + Address, CkbRpcClient, NetworkInfo, +}; +use ckb_types::h256; +use std::{error::Error as StdErr, str::FromStr}; + +fn main() -> Result<(), Box> { + let network_info = NetworkInfo::testnet(); + let owner = Address::from_str("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdamwzrffgc54ef48493nfd2sd0h4cjnxg4850up")?; + let receiver=Address::from_str("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqv5dsed9par23x4g58seaw58j3ym5ml2hs8ztche")?; + + let configuration = TransactionBuilderConfiguration::new_with_network(network_info.clone())?; + + let iterator = InputIterator::new(vec![(&owner).into()], configuration.network_info()); + let mut builder = SimpleTransactionBuilder::new(configuration, iterator); + + let context = udt::UdtIssueContext::new_default( + (&owner).into(), + (&receiver).into(), + 10u128, + &network_info, + ); + let mut contexts = HandlerContexts::default(); + contexts.add_context(Box::new(context) as Box<_>); + + builder.set_change_lock((&owner).into()); + let mut tx_with_groups = builder.build(&mut contexts)?; + + let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + + let private_keys = vec![h256!( + "0x0c982052ffd4af5f3bbf232301dcddf468009161fc48ba1426e3ce0929fb59f8" + )]; + TransactionSigner::new(&network_info).sign_transaction( + &mut tx_with_groups, + &SignContexts::new_sighash_h256(private_keys)?, + )?; + + let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + + let tx_hash = CkbRpcClient::new(network_info.url.as_str()) + .send_transaction(json_tx.inner, None) + .expect("send transaction"); + // example tx: e992819c59233c05eb83552203695840b5e76a2c3faf606adee4a1d1df4837d9 + println!(">>> tx {} sent! <<<", tx_hash); + + Ok(()) +} diff --git a/examples/sudt_transfer_example.rs b/examples/sudt_transfer_example.rs new file mode 100644 index 00000000..4a205c64 --- /dev/null +++ b/examples/sudt_transfer_example.rs @@ -0,0 +1,61 @@ +use ckb_sdk::{ + transaction::{ + builder::{CkbTransactionBuilder, SimpleTransactionBuilder}, + handler::{udt, HandlerContexts}, + input::InputIterator, + signer::{SignContexts, TransactionSigner}, + TransactionBuilderConfiguration, + }, + Address, CkbRpcClient, NetworkInfo, +}; +use ckb_types::h256; +use std::{error::Error as StdErr, str::FromStr}; + +fn main() -> Result<(), Box> { + let network_info = NetworkInfo::testnet(); + let sender = Address::from_str("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdamwzrffgc54ef48493nfd2sd0h4cjnxg4850up")?; + let receiver=Address::from_str("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqd0pdquvfuq077aemn447shf4d8u5f4a0glzz2g4")?; + + let configuration = TransactionBuilderConfiguration::new_with_network(network_info.clone())?; + + let iterator = InputIterator::new(vec![(&sender).into()], configuration.network_info()); + let mut builder = SimpleTransactionBuilder::new(configuration, iterator); + + let context = udt::UdtTransferContext::from_owner( + &(&sender).into(), + (&sender).into(), + (&receiver).into(), + 1u128, + &network_info, + ); + // context.set_keep_zero_udt_cell(false); + // let udt_change_address = Address::from_str("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqv5dsed9par23x4g58seaw58j3ym5ml2hs8ztche")?; + // context.set_udt_change_lock(Some((&udt_change_address).into())); + let mut contexts = HandlerContexts::default(); + contexts.add_context(Box::new(context) as Box<_>); + + builder.set_change_lock((&sender).into()); + let mut tx_with_groups = builder.build(&mut contexts)?; + + let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + + let private_keys = vec![h256!( + "0x0c982052ffd4af5f3bbf232301dcddf468009161fc48ba1426e3ce0929fb59f8" + )]; + TransactionSigner::new(&network_info).sign_transaction( + &mut tx_with_groups, + &SignContexts::new_sighash_h256(private_keys)?, + )?; + + let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + + let tx_hash = CkbRpcClient::new(network_info.url.as_str()) + .send_transaction(json_tx.inner, None) + .expect("send transaction"); + // example tx: d231a37f1f8787b98b6021783240205cf7185a45be4f304d6e8f2437f252f704 + println!(">>> tx {} sent! <<<", tx_hash); + + Ok(()) +} diff --git a/src/core/advanced_builders.rs b/src/core/advanced_builders.rs index 8a39d59e..af0af9e4 100644 --- a/src/core/advanced_builders.rs +++ b/src/core/advanced_builders.rs @@ -1,4 +1,8 @@ -use ckb_types::{constants, core, packed, prelude::*}; +use ckb_types::{ + constants, core, + packed::{self, WitnessArgs}, + prelude::*, +}; use derive_getters::Getters; /// An advanced builder for [`TransactionView`]. @@ -122,7 +126,7 @@ macro_rules! def_setter_for_vector { def_setter_for_vector!(packed, $field, $type, $func_push, $func_extend, $func_set); }; (set_i, $field:ident, $type:ident, $func_push:ident, $func_extend:ident, $func_set:ident, $func_set_i: ident) => { - def_setter_for_vector!(packed, $field, $type, $func_push, $func_extend, $func_set); + def_setter_for_vector!($field, $type, $func_push, $func_extend, $func_set); pub fn $func_set_i(&mut self, i: usize, v: packed::$type) -> &mut Self { self.$field[i] = v; @@ -138,11 +142,12 @@ macro_rules! def_dedup_setter_for_vector { $comment_push:expr, $comment_extend:expr, ) => { #[doc = $comment_push] - pub fn $func_push(&mut self, v: $prefix::$type) -> &mut Self { - if !self.$field.contains(&v) { - self.$field.push(v); + pub fn $func_push(&mut self, v: $prefix::$type) -> usize { + if let Some(idx) = self.$field.iter().position(|x| x == &v) { + return idx; } - self + self.$field.push(v); + self.$field.len() - 1 } #[doc = $comment_extend] pub fn $func_extend(&mut self, v: T) -> &mut Self @@ -221,6 +226,46 @@ impl TransactionBuilder { set_outputs_data ); + fn get_witness_obj(&self, i: usize) -> WitnessArgs { + let witness_data = self.witnesses[i].raw_data(); + if witness_data.is_empty() { + WitnessArgs::default() + } else { + WitnessArgs::from_slice(witness_data.as_ref()).unwrap() + } + } + pub fn set_witness_lock(&mut self, i: usize, v: Option) -> &mut Self { + let current_witness = self.get_witness_obj(i); + self.witnesses[i] = current_witness + .as_builder() + .lock(v.pack()) + .build() + .as_bytes() + .pack(); + self + } + + pub fn set_witness_input(&mut self, i: usize, v: Option) -> &mut Self { + self.witnesses[i] = self + .get_witness_obj(i) + .as_builder() + .input_type(v.pack()) + .build() + .as_bytes() + .pack(); + self + } + + pub fn set_witness_output(&mut self, i: usize, v: Option) -> &mut Self { + self.witnesses[i] = self + .get_witness_obj(i) + .as_builder() + .output_type(v.pack()) + .build() + .as_bytes() + .pack(); + self + } /// Converts into [`TransactionView`](struct.TransactionView.html). pub fn build(self) -> core::TransactionView { let Self { @@ -247,4 +292,13 @@ impl TransactionBuilder { tx.into_view() } + + pub(crate) fn set_input_since(&mut self, i: usize, since: u64) -> &mut Self { + self.inputs[i] = self.inputs[i] + .clone() + .as_builder() + .since(since.pack()) + .build(); + self + } } diff --git a/src/tests/transaction/mod.rs b/src/tests/transaction/mod.rs index 3050d6d3..ac42796e 100644 --- a/src/tests/transaction/mod.rs +++ b/src/tests/transaction/mod.rs @@ -1 +1,2 @@ pub mod sighash; +pub mod typeid; diff --git a/src/tests/transaction/sighash.rs b/src/tests/transaction/sighash.rs index 162366d5..fcd012aa 100644 --- a/src/tests/transaction/sighash.rs +++ b/src/tests/transaction/sighash.rs @@ -43,7 +43,9 @@ fn test_transfer_from_sighash() { let mut builder = SimpleTransactionBuilder::new(configuration, iterator); builder.add_output(output.clone(), ckb_types::packed::Bytes::default()); builder.set_change_lock(sender.clone()); - let mut tx_with_groups = builder.build(&Default::default()).expect("build failed"); + let mut tx_with_groups = builder + .build(&mut Default::default()) + .expect("build failed"); let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); @@ -106,7 +108,9 @@ fn test_transfer_from_sighash_samll_to_fee() { let mut builder = SimpleTransactionBuilder::new(configuration, iterator); builder.add_output(output.clone(), ckb_types::packed::Bytes::default()); builder.set_change_lock(sender.clone()); - let mut tx_with_groups = builder.build(&Default::default()).expect("build failed"); + let mut tx_with_groups = builder + .build(&mut Default::default()) + .expect("build failed"); let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); @@ -167,7 +171,9 @@ fn test_transfer_from_sighash_samll_to_receiver() { let mut builder = SimpleTransactionBuilder::new(configuration, iterator); builder.add_output(output, ckb_types::packed::Bytes::default()); builder.set_change_lock(sender.clone()); - let mut tx_with_groups = builder.build(&Default::default()).expect("build failed"); + let mut tx_with_groups = builder + .build(&mut Default::default()) + .expect("build failed"); let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); diff --git a/src/tests/transaction/typeid.rs b/src/tests/transaction/typeid.rs new file mode 100644 index 00000000..f2f03df5 --- /dev/null +++ b/src/tests/transaction/typeid.rs @@ -0,0 +1,69 @@ +use ckb_types::{packed::CellOutput, prelude::*}; + +use crate::{ + constants::{self, ONE_CKB}, + tests::{build_sighash_script, init_context, ACCOUNT1_ARG, ACCOUNT1_KEY, FEE_RATE}, + transaction::{ + builder::{CkbTransactionBuilder, SimpleTransactionBuilder}, + input::InputIterator, + signer::{SignContexts, TransactionSigner}, + TransactionBuilderConfiguration, + }, + NetworkInfo, ScriptId, +}; + +#[test] +fn test_deploy_id() { + let sender = build_sighash_script(ACCOUNT1_ARG); + let ctx = init_context(Vec::new(), vec![(sender.clone(), Some(10_0000 * ONE_CKB))]); + + let network_info = NetworkInfo::testnet(); + let type_script = ScriptId::new_type(constants::TYPE_ID_CODE_HASH.clone()).dummy_script(); + + let output = CellOutput::new_builder() + .capacity((120 * ONE_CKB).pack()) + .lock(sender.clone()) + .type_(Some(type_script).pack()) + .build(); + let configuration = + TransactionBuilderConfiguration::new_with_network(network_info.clone()).unwrap(); + + let iterator = InputIterator::new_with_cell_collector( + vec![sender.clone()], + Box::new(ctx.to_live_cells_context()) as Box<_>, + ); + let mut builder = SimpleTransactionBuilder::new(configuration, iterator); + builder.add_output(output, bytes::Bytes::from(vec![0x01u8; 64]).pack()); + builder.set_change_lock(sender.clone()); + let mut tx_with_groups = builder + .build(&mut Default::default()) + .expect("build failed"); + + let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + + TransactionSigner::new(&network_info) + .sign_transaction( + &mut tx_with_groups, + &SignContexts::new_sighash_h256(vec![ACCOUNT1_KEY.clone()]).unwrap(), + ) + .unwrap(); + + let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + + let tx = tx_with_groups.get_tx_view().clone(); + let script_groups = tx_with_groups.script_groups.clone(); + assert_eq!(script_groups.len(), 2); + assert_eq!(tx.header_deps().len(), 0); + assert_eq!(tx.cell_deps().len(), 1); + assert_eq!(tx.inputs().len(), 1); + for out_point in tx.input_pts_iter() { + assert_eq!(ctx.get_input(&out_point).unwrap().0.lock(), sender); + } + assert_eq!(tx.outputs().len(), 2); + // assert_eq!(tx.output(0).unwrap(), output); + assert_eq!(tx.output(1).unwrap().lock(), sender); + + ctx.verify(tx, FEE_RATE).unwrap(); +} diff --git a/src/transaction/builder/mod.rs b/src/transaction/builder/mod.rs index fd98b3de..82de9755 100644 --- a/src/transaction/builder/mod.rs +++ b/src/transaction/builder/mod.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::{collections::HashMap, vec}; use super::{ handler::HandlerContexts, @@ -22,7 +22,7 @@ pub use fee_calculator::FeeCalculator; pub trait CkbTransactionBuilder { fn build( &mut self, - contexts: &HandlerContexts, + contexts: &mut HandlerContexts, ) -> Result; } @@ -36,6 +36,12 @@ pub struct SimpleTransactionBuilder { reward: u64, } +pub struct PrepareTransactionViewer<'a> { + pub(crate) transaction_inputs: &'a mut Vec, + pub(crate) tx: &'a mut TransactionBuilder, + pub(crate) reward: &'a mut u64, +} + impl SimpleTransactionBuilder { pub fn new(configuration: TransactionBuilderConfiguration, input_iter: InputIterator) -> Self { Self { @@ -132,8 +138,68 @@ impl SimpleTransactionBuilder { .capacity((capacity + delta_capacity).pack()) .build(); tx_builder.set_output(idx, output); + + Ok(()) + } + + fn prepare_transaction( + viewer: &mut PrepareTransactionViewer, + configuration: &TransactionBuilderConfiguration, + contexts: &mut HandlerContexts, + ) -> Result<(), TxBuilderError> { + for handler in configuration.get_script_handlers() { + for context in &mut contexts.contexts { + if handler.prepare_transaction(viewer, context.as_mut())? { + break; + } + } + } + Ok(()) + } + + fn post_build( + type_groups: &HashMap, + configuration: &TransactionBuilderConfiguration, + tx_builder: &mut TransactionBuilder, + contexts: &mut HandlerContexts, + ) -> Result<(), TxBuilderError> { + for idx in type_groups + .values() + .flat_map(|group| group.output_indices.iter()) + { + for handler in configuration.get_script_handlers() { + for context in &mut contexts.contexts { + if handler.post_build(*idx, tx_builder, context.as_mut())? { + break; + } + } + } + } Ok(()) } + fn rebuild_type_script_group( + tx_builder: &TransactionBuilder, + type_groups: HashMap, + ) -> HashMap { + let mut ret = HashMap::default(); + for (key, old_group) in type_groups.into_iter() { + if !old_group.input_indices.is_empty() { + let new_group = ret + .entry(key) + .or_insert_with(|| ScriptGroup::from_type_script(&old_group.script)); + new_group.input_indices.extend(old_group.input_indices); + } + for idx in old_group.output_indices { + let output = tx_builder.get_outputs().get(idx).unwrap(); + let type_ = output.type_().to_opt().unwrap(); + let new_group = ret + .entry(type_.calc_script_hash()) + .or_insert_with(|| ScriptGroup::from_type_script(&type_)); + new_group.output_indices.push(idx); + } + } + ret + } } macro_rules! celloutput_capacity { @@ -143,11 +209,22 @@ macro_rules! celloutput_capacity { }}; } +macro_rules! prepare_veiwer { + ($self:ident) => { + PrepareTransactionViewer { + transaction_inputs: &mut $self.transaction_inputs, + tx: &mut $self.tx, + reward: &mut $self.reward, + } + }; +} + impl CkbTransactionBuilder for SimpleTransactionBuilder { fn build( &mut self, - contexts: &HandlerContexts, + contexts: &mut HandlerContexts, ) -> Result { + Self::prepare_transaction(&mut prepare_veiwer!(self), &self.configuration, contexts)?; let mut lock_groups: HashMap = HashMap::default(); let mut type_groups: HashMap = HashMap::default(); let mut outputs_capacity = 0u64; @@ -171,9 +248,9 @@ impl CkbTransactionBuilder for SimpleTransactionBuilder { InputView::new(&self.transaction_inputs, &mut self.input_iter).enumerate() { let input = input?; - self.tx.input(input.cell_input()); + self.tx.input(input.cell_input(0)); let previous_output = input.previous_output(); - self.tx.witness(packed::Bytes::default()); + self.tx.witness(Default::default()); let lock_script = previous_output.lock(); let script_group = lock_groups .entry(lock_script.calc_script_hash()) @@ -269,7 +346,9 @@ impl CkbTransactionBuilder for SimpleTransactionBuilder { if !state.is_success() { return Err(TxBuilderError::BalanceCapacity(state.into())); } - let script_groups = lock_groups + Self::post_build(&type_groups, &self.configuration, &mut self.tx, contexts)?; + let type_groups = Self::rebuild_type_script_group(&self.tx, type_groups); + let script_groups: Vec = lock_groups .into_values() .chain(type_groups.into_values()) .collect(); diff --git a/src/transaction/handler/dao.rs b/src/transaction/handler/dao.rs new file mode 100644 index 00000000..edcbf766 --- /dev/null +++ b/src/transaction/handler/dao.rs @@ -0,0 +1,373 @@ +use std::collections::HashMap; + +use anyhow::anyhow; +use ckb_types::{ + core::{Capacity, DepType, ScriptHashType}, + h256, + packed::{CellDep, CellInput, CellOutput, OutPoint, Script}, + prelude::{Builder, Entity, Pack, Unpack}, +}; +use lazy_static::lazy_static; + +use crate::{ + constants, + traits::{ + DefaultHeaderDepResolver, DefaultTransactionDependencyProvider, HeaderDepResolver, + TransactionDependencyProvider, + }, + transaction::{builder::PrepareTransactionViewer, input::TransactionInput}, + tx_builder::{ + dao::{DaoDepositReceiver, DaoPrepareItem}, + TxBuilderError, + }, + util::{calculate_dao_maximum_withdraw4, minimal_unlock_point}, + NetworkInfo, NetworkType, ScriptGroup, Since, SinceType, +}; + +use super::{HandlerContext, ScriptHandler}; + +pub const DAO_DATA_LEN: usize = 8; +lazy_static! { + static ref DAO_TYPE_SCRIPT: Script = Script::new_builder() + .code_hash(constants::DAO_TYPE_HASH.pack()) + .hash_type(ScriptHashType::Type.into()) + .build(); + static ref DEPOSIT_CELL_DATA: ckb_types::packed::Bytes = + bytes::Bytes::from(vec![0u8; DAO_DATA_LEN]).pack(); +} + +pub struct DaoScriptHandler { + cell_deps: Vec, +} + +#[derive(Clone, Debug, Default)] +pub struct DepositContext { + // lock script, capacity list + pub receivers: Vec, +} + +impl DepositContext { + pub fn new(lock_script: Script, capacity: u64) -> Self { + let mut ret = Self::default(); + ret.add_output(lock_script, capacity); + ret + } + + pub fn add_output(&mut self, lock_script: Script, capacity: u64) { + self.receivers + .push(DaoDepositReceiver::new(lock_script, capacity)); + } +} + +impl HandlerContext for DepositContext {} + +#[derive(Default)] +pub struct WithdrawPhrase1Context { + pub items: Vec, + pub rpc_url: String, +} + +impl WithdrawPhrase1Context { + /// add input. + /// If `receiver_lock` is `None` copy the lock script from input with same + /// index, otherwise replace the lock script with the given script. + pub fn add_input(&mut self, input: CellInput, receiver_lock: Option