|
1 | 1 | use bdk_bitcoind_rpc::Emitter; |
2 | | -use bdk_chain::{bdk_core, Balance}; |
| 2 | +use bdk_chain::spk_client::{ |
| 3 | + FullScanRequestBuilder, FullScanResponse, SyncRequestBuilder, SyncResponse, |
| 4 | +}; |
| 5 | +use bdk_chain::{bdk_core, Balance, KeychainIndexed}; |
| 6 | +use bdk_esplora::esplora_client; |
| 7 | +use bdk_esplora::esplora_client::Builder; |
| 8 | +use bdk_esplora::EsploraExt; |
3 | 9 | use bdk_testenv::{bitcoincore_rpc::RpcApi, TestEnv}; |
4 | 10 | use bdk_tx::{ |
5 | 11 | create_psbt, create_selection, get_candidate_inputs, CreatePsbtParams, CreateSelectionParams, |
6 | 12 | InputGroup, Output, Signer, |
7 | 13 | }; |
8 | | -use bitcoin::{key::Secp256k1, Address, Amount, BlockHash, FeeRate}; |
| 14 | +use bdk_wallet::{AddressInfo, KeychainKind, LocalOutput, SignOptions}; |
| 15 | +use bitcoin::address::NetworkChecked; |
| 16 | +use bitcoin::{key::Secp256k1, Address, Amount, BlockHash, FeeRate, Network, OutPoint}; |
| 17 | +use miniscript::descriptor::KeyMap; |
9 | 18 | use miniscript::{plan::Assets, Descriptor, DescriptorPublicKey}; |
| 19 | +use std::collections::BTreeMap; |
| 20 | +use std::process::exit; |
| 21 | +use std::str::FromStr; |
10 | 22 |
|
11 | 23 | const EXTERNAL: &str = "external"; |
12 | 24 | const INTERNAL: &str = "internal"; |
@@ -148,3 +160,114 @@ fn synopsis() -> anyhow::Result<()> { |
148 | 160 |
|
149 | 161 | Ok(()) |
150 | 162 | } |
| 163 | + |
| 164 | +#[test] |
| 165 | +fn bdk_wallet_simple_tx() -> anyhow::Result<()> { |
| 166 | + const STOP_GAP: usize = 20; |
| 167 | + const PARALLEL_REQUESTS: usize = 1; |
| 168 | + let secp = bitcoin::secp256k1::Secp256k1::new(); |
| 169 | + |
| 170 | + let descriptor_private: &str = "tr(tprv8ZgxMBicQKsPdNRGG6HuFapxQCFxsDDf7TDsV8tdUgZDdiiyA6dB2ssN4RSXyp52V3MRBm4KqAps3Txng59rNMUtUEtMPDphKkKDXmamd2T/86'/1'/0'/0/*)#usy7l3tt"; |
| 171 | + let change_descriptor_private: &str = "tr(tprv8ZgxMBicQKsPdNRGG6HuFapxQCFxsDDf7TDsV8tdUgZDdiiyA6dB2ssN4RSXyp52V3MRBm4KqAps3Txng59rNMUtUEtMPDphKkKDXmamd2T/86'/1'/0'/1/*)#dyplzymn"; |
| 172 | + |
| 173 | + let (descriptor, _): (Descriptor<DescriptorPublicKey>, KeyMap) = Descriptor::parse_descriptor(&secp, "tr(tprv8ZgxMBicQKsPdNRGG6HuFapxQCFxsDDf7TDsV8tdUgZDdiiyA6dB2ssN4RSXyp52V3MRBm4KqAps3Txng59rNMUtUEtMPDphKkKDXmamd2T/86'/1'/0'/0/*)#usy7l3tt")?; |
| 174 | + let (change_descriptor, _): (Descriptor<DescriptorPublicKey>, KeyMap) = Descriptor::parse_descriptor(&secp, "tr(tprv8ZgxMBicQKsPdNRGG6HuFapxQCFxsDDf7TDsV8tdUgZDdiiyA6dB2ssN4RSXyp52V3MRBm4KqAps3Txng59rNMUtUEtMPDphKkKDXmamd2T/86'/1'/0'/1/*)#dyplzymn")?; |
| 175 | + |
| 176 | + // Create the wallet |
| 177 | + let mut wallet = bdk_wallet::Wallet::create(descriptor_private, change_descriptor_private) |
| 178 | + .network(Network::Regtest) |
| 179 | + .create_wallet_no_persist()?; |
| 180 | + |
| 181 | + let client: esplora_client::BlockingClient = |
| 182 | + Builder::new("http://127.0.0.1:3002").build_blocking(); |
| 183 | + |
| 184 | + println!("Syncing wallet..."); |
| 185 | + let full_scan_request: FullScanRequestBuilder<KeychainKind> = wallet.start_full_scan(); |
| 186 | + let update: FullScanResponse<KeychainKind> = |
| 187 | + client.full_scan(full_scan_request, STOP_GAP, PARALLEL_REQUESTS)?; |
| 188 | + |
| 189 | + // Apply the update from the full scan to the wallet |
| 190 | + wallet.apply_update(update)?; |
| 191 | + |
| 192 | + let balance = wallet.balance(); |
| 193 | + println!("Wallet balance: {} sat", balance.total().to_sat()); |
| 194 | + |
| 195 | + if balance.total().to_sat() < 300000 { |
| 196 | + println!("Your wallet does not have sufficient balance for the following steps!"); |
| 197 | + // Reveal a new address from your external keychain |
| 198 | + let address: AddressInfo = wallet.reveal_next_address(KeychainKind::External); |
| 199 | + println!( |
| 200 | + "Send coins to {} (address generated at index {})", |
| 201 | + address.address, address.index |
| 202 | + ); |
| 203 | + exit(0) |
| 204 | + } |
| 205 | + |
| 206 | + let local_outputs: Vec<LocalOutput> = wallet.list_unspent().collect(); |
| 207 | + dbg!(&local_outputs.len()); |
| 208 | + // dbg!(&local_outputs); |
| 209 | + let outpoints: Vec<KeychainIndexed<KeychainKind, OutPoint>> = local_outputs |
| 210 | + .into_iter() |
| 211 | + .map(|o| ((o.keychain, o.derivation_index), o.outpoint.clone())) |
| 212 | + .collect(); |
| 213 | + |
| 214 | + let mut descriptors_map = BTreeMap::new(); |
| 215 | + descriptors_map.insert(KeychainKind::External, descriptor.clone()); |
| 216 | + descriptors_map.insert(KeychainKind::Internal, change_descriptor.clone()); |
| 217 | + |
| 218 | + let input_candidates_individual = get_candidate_inputs( |
| 219 | + &wallet.tx_graph(), |
| 220 | + &wallet.local_chain(), |
| 221 | + outpoints, |
| 222 | + descriptors_map, |
| 223 | + Assets::new(), |
| 224 | + )?; |
| 225 | + // dbg!(&input_candidates_0); |
| 226 | + |
| 227 | + let recipient_address: Address<NetworkChecked> = |
| 228 | + Address::from_str("bcrt1qe908k9zu8m4jgzdddgg0lkj73yctfqueg7pea9")? |
| 229 | + .require_network(Network::Regtest)?; |
| 230 | + |
| 231 | + let input_candidates_groups: Vec<InputGroup> = input_candidates_individual |
| 232 | + .into_iter() |
| 233 | + .map(|i| i.into()) |
| 234 | + .collect(); |
| 235 | + |
| 236 | + let selection = create_selection(CreateSelectionParams { |
| 237 | + input_candidates: input_candidates_groups, |
| 238 | + must_spend: Default::default(), |
| 239 | + change_descriptor: change_descriptor.at_derivation_index(0)?, |
| 240 | + target_feerate: FeeRate::from_sat_per_vb(5).unwrap(), |
| 241 | + long_term_feerate: Some(FeeRate::from_sat_per_vb(1).unwrap()), |
| 242 | + target_outputs: vec![Output::with_script( |
| 243 | + recipient_address.script_pubkey(), |
| 244 | + Amount::from_sat(200_000), |
| 245 | + )], |
| 246 | + max_rounds: 100_000, |
| 247 | + })?; |
| 248 | + |
| 249 | + dbg!(&selection.inputs.len()); |
| 250 | + |
| 251 | + let (mut psbt, _) = create_psbt(CreatePsbtParams { |
| 252 | + inputs: selection.inputs, |
| 253 | + outputs: selection.outputs, |
| 254 | + ..Default::default() |
| 255 | + })?; |
| 256 | + let signed = wallet.sign(&mut psbt, SignOptions::default())?; |
| 257 | + assert!(signed); |
| 258 | + let tx = psbt.extract_tx()?; |
| 259 | + |
| 260 | + client.broadcast(&tx)?; |
| 261 | + dbg!("tx broadcast: {}", tx.compute_txid()); |
| 262 | + |
| 263 | + println!("Syncing wallet again..."); |
| 264 | + let sync_request = wallet.start_sync_with_revealed_spks(); |
| 265 | + let update_2: SyncResponse = client.sync(sync_request, PARALLEL_REQUESTS)?; |
| 266 | + |
| 267 | + wallet.apply_update(update_2)?; |
| 268 | + |
| 269 | + let balance_2 = wallet.balance(); |
| 270 | + println!("Wallet balance: {} sat", balance_2.total().to_sat()); |
| 271 | + |
| 272 | + Ok(()) |
| 273 | +} |
0 commit comments