Skip to content

Commit 58a69dc

Browse files
exploring bdk-tx for Wallet type
1 parent 3e61d2a commit 58a69dc

File tree

5 files changed

+260
-2
lines changed

5 files changed

+260
-2
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ members = [
66
"examples/example_wallet_esplora_blocking",
77
"examples/example_wallet_esplora_async",
88
"examples/example_wallet_rpc",
9+
"examples/example_wallet_bdk_tx",
910
]
1011

1112
[workspace.package]
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "example_wallet_bdk_tx"
3+
version = "0.2.0"
4+
edition = "2021"
5+
publish = false
6+
7+
[dependencies]
8+
bdk_wallet = { path = "../../wallet", features = ["file_store"] }
9+
bdk_esplora = { version = "0.20", features = ["blocking"] }
10+
11+
anyhow = "1"
+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
use std::{collections::BTreeSet, io::Write};
2+
3+
use bdk_esplora::{esplora_client, EsploraExt};
4+
use bdk_wallet::bitcoin::FeeRate;
5+
use bdk_wallet::{
6+
bitcoin::{Amount, Network},
7+
file_store::Store,
8+
KeychainKind, SignOptions, TransactionParams, Wallet,
9+
};
10+
11+
const DB_MAGIC: &str = "bdk_wallet_esplora_example";
12+
const DB_PATH: &str = "bdk-example-bdk-tx.db";
13+
const SEND_AMOUNT: Amount = Amount::from_sat(5000);
14+
const STOP_GAP: usize = 5;
15+
const PARALLEL_REQUESTS: usize = 5;
16+
17+
const NETWORK: Network = Network::Signet;
18+
const EXTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/1'/0/*)";
19+
const INTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/1'/1/*)";
20+
const ESPLORA_URL: &str = "http://signet.bitcoindevkit.net";
21+
22+
fn main() -> Result<(), anyhow::Error> {
23+
let mut db = Store::<bdk_wallet::ChangeSet>::open_or_create_new(DB_MAGIC.as_bytes(), DB_PATH)?;
24+
25+
let wallet_opt = Wallet::load()
26+
.descriptor(KeychainKind::External, Some(EXTERNAL_DESC))
27+
.descriptor(KeychainKind::Internal, Some(INTERNAL_DESC))
28+
.extract_keys()
29+
.check_network(NETWORK)
30+
.load_wallet(&mut db)?;
31+
let mut wallet = match wallet_opt {
32+
Some(wallet) => wallet,
33+
None => Wallet::create(EXTERNAL_DESC, INTERNAL_DESC)
34+
.network(NETWORK)
35+
.create_wallet(&mut db)?,
36+
};
37+
38+
let address = wallet.next_unused_address(KeychainKind::External);
39+
wallet.persist(&mut db)?;
40+
println!(
41+
"Next unused address: ({}) {}",
42+
address.index, address.address
43+
);
44+
45+
let balance = wallet.balance();
46+
println!("Wallet balance before syncing: {}", balance.total());
47+
48+
print!("Syncing...");
49+
let client = esplora_client::Builder::new(ESPLORA_URL).build_blocking();
50+
51+
let request = wallet.start_full_scan().inspect({
52+
let mut stdout = std::io::stdout();
53+
let mut once = BTreeSet::<KeychainKind>::new();
54+
move |keychain, spk_i, _| {
55+
if once.insert(keychain) {
56+
print!("\nScanning keychain [{:?}] ", keychain);
57+
}
58+
print!(" {:<3}", spk_i);
59+
stdout.flush().expect("must flush")
60+
}
61+
});
62+
63+
let update = client.full_scan(request, STOP_GAP, PARALLEL_REQUESTS)?;
64+
65+
wallet.apply_update(update)?;
66+
wallet.persist(&mut db)?;
67+
println!();
68+
69+
let balance = wallet.balance();
70+
println!("Wallet balance after syncing: {}", balance.total());
71+
72+
if balance.total() < SEND_AMOUNT {
73+
println!(
74+
"Please send at least {} to the receiving address",
75+
SEND_AMOUNT
76+
);
77+
std::process::exit(0);
78+
}
79+
80+
// ----------------------------
81+
// TRANSACTION BUILDER WORKFLOW
82+
// ----------------------------
83+
// let mut tx_builder = wallet.build_tx();
84+
// tx_builder
85+
// .add_recipient(address.script_pubkey(), SEND_AMOUNT)
86+
// .fee_rate(FeeRate::from_sat_per_vb(4).unwrap());
87+
// let mut psbt = tx_builder.finish()?;
88+
89+
// ----------------------------
90+
// USING BDK-TX THROUGH A NEW WALLET METHOD INSTEAD
91+
// ----------------------------
92+
// let transaction_params = TransactionParams {
93+
// outputs: vec![(address.script_pubkey(), SEND_AMOUNT)],
94+
// target_feerate: FeeRate::from_sat_per_vb(4).unwrap(),
95+
// must_spend: Vec::new(),
96+
// };
97+
// let mut psbt = wallet.create_complex_transaction(transaction_params).unwrap();
98+
99+
let mut psbt = wallet
100+
.create_transaction(
101+
vec![(address.script_pubkey(), SEND_AMOUNT)],
102+
FeeRate::from_sat_per_vb(4).unwrap(),
103+
)
104+
.unwrap();
105+
106+
let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
107+
assert!(finalized);
108+
109+
let tx = psbt.extract_tx()?;
110+
client.broadcast(&tx)?;
111+
println!("Tx broadcasted! Txid: {}", tx.compute_txid());
112+
113+
Ok(())
114+
}

wallet/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ serde = { version = "^1.0", features = ["derive"] }
2323
serde_json = { version = "^1.0" }
2424
bdk_chain = { version = "0.21.1", features = [ "miniscript", "serde" ], default-features = false }
2525
bdk_file_store = { version = "0.18.1", optional = true }
26+
bdk_tx = { git = "https://github.com/bitcoindevkit/bdk-tx.git", rev = "6e2414ed7e701c04d0af46ff477c2dbf9a9f75b2" }
2627

2728
# Optional dependencies
2829
bip39 = { version = "2.0", optional = true }

wallet/src/wallet/mod.rs

+133-2
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ use alloc::{
1919
sync::Arc,
2020
vec::Vec,
2121
};
22-
use core::{cmp::Ordering, fmt, mem, ops::Deref};
23-
2422
use bdk_chain::{
2523
indexed_tx_graph,
2624
indexer::keychain_txout::KeychainTxOutIndex,
@@ -43,11 +41,13 @@ use bitcoin::{
4341
transaction, Address, Amount, Block, BlockHash, FeeRate, Network, OutPoint, Psbt, ScriptBuf,
4442
Sequence, Transaction, TxOut, Txid, Weight, Witness,
4543
};
44+
use core::{cmp::Ordering, fmt, mem, ops::Deref};
4645
use miniscript::{
4746
descriptor::KeyMap,
4847
psbt::{PsbtExt, PsbtInputExt, PsbtInputSatisfier},
4948
};
5049
use rand_core::RngCore;
50+
use std::collections::BTreeSet;
5151

5252
mod changeset;
5353
pub mod coin_selection;
@@ -77,7 +77,13 @@ use crate::wallet::{
7777

7878
// re-exports
7979
pub use bdk_chain::Balance;
80+
use bdk_tx::{
81+
create_psbt, create_selection, CreatePsbtParams, CreateSelectionParams, InputCandidates,
82+
InputGroup, Output,
83+
};
84+
use chain::KeychainIndexed;
8085
pub use changeset::ChangeSet;
86+
use miniscript::plan::Assets;
8187
pub use params::*;
8288
pub use persisted::*;
8389
pub use utils::IsDust;
@@ -2425,6 +2431,131 @@ impl Wallet {
24252431
}
24262432
}
24272433

2434+
pub struct TransactionParams {
2435+
pub outputs: Vec<(ScriptBuf, Amount)>,
2436+
pub target_feerate: FeeRate,
2437+
pub must_spend: Vec<LocalOutput>,
2438+
// cannot_spend: Vec<LocalOutput>,
2439+
}
2440+
2441+
/// Methods that use the bdk_tx crate to build transactions
2442+
impl Wallet {
2443+
pub fn create_transaction(
2444+
&mut self,
2445+
outputs: Vec<(ScriptBuf, Amount)>,
2446+
target_feerate: FeeRate,
2447+
) -> Result<Psbt, CreateBdkTxError> {
2448+
let local_outputs: Vec<LocalOutput> = self.list_unspent().collect();
2449+
let outpoints: Vec<KeychainIndexed<KeychainKind, OutPoint>> = local_outputs
2450+
.into_iter()
2451+
.map(|o| ((o.keychain, o.derivation_index), o.outpoint.clone()))
2452+
.collect();
2453+
// let descriptors = self.keychains();
2454+
let descriptors: Vec<(KeychainKind, &ExtendedDescriptor)> = self.keychains().collect();
2455+
2456+
let mut descriptors_map = BTreeMap::new();
2457+
let _ = descriptors.into_iter().for_each(|(kind, desc)| {
2458+
descriptors_map.insert(kind, desc.clone());
2459+
});
2460+
dbg!(&descriptors_map);
2461+
2462+
let input_candidates: Vec<InputGroup> = InputCandidates::new(
2463+
&self.tx_graph(), // tx_graph
2464+
&self.local_chain(), // chain
2465+
self.local_chain().tip().block_id().clone(), // chain_tip
2466+
outpoints, // outpoints
2467+
descriptors_map, // descriptors
2468+
BTreeSet::default(), // allow_malleable
2469+
Assets::new(), // additional_assets
2470+
)
2471+
.unwrap()
2472+
.into_single_groups(|_| true);
2473+
2474+
let next_change_index: u32 = self.reveal_next_address(KeychainKind::Internal).index;
2475+
let public_change_descriptor = self.public_descriptor(KeychainKind::Internal);
2476+
2477+
let outputs_vector = outputs
2478+
.into_iter()
2479+
.map(|o| Output::with_script(o.0, o.1))
2480+
.collect::<Vec<_>>();
2481+
2482+
let (selection, metrics) = create_selection(CreateSelectionParams::new(
2483+
input_candidates,
2484+
public_change_descriptor
2485+
.at_derivation_index(next_change_index)
2486+
.map_err(|_| CreateBdkTxError::CannotCreateTx)?,
2487+
outputs_vector,
2488+
target_feerate,
2489+
))
2490+
.map_err(|_| CreateBdkTxError::CannotCreateTx)?;
2491+
2492+
let (psbt, _) = create_psbt(CreatePsbtParams::new(selection))
2493+
.map_err(|_| CreateBdkTxError::CannotCreateTx)?;
2494+
2495+
Ok(psbt)
2496+
}
2497+
2498+
pub fn create_complex_transaction(
2499+
&mut self,
2500+
transaction_params: TransactionParams,
2501+
) -> Result<Psbt, CreateBdkTxError> {
2502+
let local_outputs: Vec<LocalOutput> = self.list_unspent().collect();
2503+
let outpoints: Vec<KeychainIndexed<KeychainKind, OutPoint>> = local_outputs
2504+
.into_iter()
2505+
.map(|o| ((o.keychain, o.derivation_index), o.outpoint.clone()))
2506+
.collect();
2507+
// let descriptors = self.keychains();
2508+
let descriptors: Vec<(KeychainKind, &ExtendedDescriptor)> = self.keychains().collect();
2509+
2510+
let mut descriptors_map = BTreeMap::new();
2511+
let _ = descriptors.into_iter().for_each(|(kind, desc)| {
2512+
descriptors_map.insert(kind, desc.clone());
2513+
});
2514+
dbg!(&descriptors_map);
2515+
2516+
let input_candidates: Vec<InputGroup> = InputCandidates::new(
2517+
&self.tx_graph(), // tx_graph
2518+
&self.local_chain(), // chain
2519+
self.local_chain().tip().block_id().clone(), // chain_tip
2520+
outpoints, // outpoints
2521+
descriptors_map, // descriptors
2522+
BTreeSet::default(), // allow_malleable
2523+
Assets::new(), // additional_assets
2524+
)
2525+
.unwrap()
2526+
.into_single_groups(|_| true);
2527+
2528+
let next_change_index: u32 = self.reveal_next_address(KeychainKind::Internal).index;
2529+
let public_change_descriptor = self.public_descriptor(KeychainKind::Internal);
2530+
2531+
let outputs_vector = transaction_params
2532+
.outputs
2533+
.into_iter()
2534+
.map(|o| Output::with_script(o.0, o.1))
2535+
.collect::<Vec<_>>();
2536+
2537+
let (selection, metrics) = create_selection(CreateSelectionParams::new(
2538+
input_candidates,
2539+
public_change_descriptor
2540+
.at_derivation_index(next_change_index)
2541+
.map_err(|_| CreateBdkTxError::CannotCreateTx)?,
2542+
outputs_vector,
2543+
transaction_params.target_feerate,
2544+
))
2545+
.map_err(|_| CreateBdkTxError::CannotCreateTx)?;
2546+
2547+
let (psbt, _) = create_psbt(CreatePsbtParams::new(selection))
2548+
.map_err(|_| CreateBdkTxError::CannotCreateTx)?;
2549+
2550+
Ok(psbt)
2551+
}
2552+
}
2553+
2554+
#[derive(Debug)]
2555+
pub enum CreateBdkTxError {
2556+
CannotCreateTx,
2557+
}
2558+
24282559
impl AsRef<bdk_chain::tx_graph::TxGraph<ConfirmationBlockTime>> for Wallet {
24292560
fn as_ref(&self) -> &bdk_chain::tx_graph::TxGraph<ConfirmationBlockTime> {
24302561
self.indexed_graph.graph()

0 commit comments

Comments
 (0)