Skip to content

Commit 21fd10b

Browse files
exploring bdk-tx for Wallet type
1 parent 3e61d2a commit 21fd10b

File tree

5 files changed

+194
-0
lines changed

5 files changed

+194
-0
lines changed

Cargo.toml

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

wallet/Cargo.toml

Lines changed: 1 addition & 0 deletions
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 = { path = "../../bdk-tx" }
2627

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

wallet/src/wallet/mod.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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,77 @@ 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+
transaction_params: TransactionParams,
2446+
) -> Result<Psbt, CreateBdkTxError> {
2447+
let local_outputs: Vec<LocalOutput> = self.list_unspent().collect();
2448+
let outpoints: Vec<KeychainIndexed<KeychainKind, OutPoint>> = local_outputs
2449+
.into_iter()
2450+
.map(|o| ((o.keychain, o.derivation_index), o.outpoint.clone()))
2451+
.collect();
2452+
// let descriptors = self.keychains();
2453+
let descriptors: Vec<(KeychainKind, &ExtendedDescriptor)> = self.keychains().collect();
2454+
2455+
let mut descriptors_map = BTreeMap::new();
2456+
let _ = descriptors
2457+
.into_iter()
2458+
.for_each(|(kind, desc)| {
2459+
descriptors_map.insert(kind, desc.clone());
2460+
});
2461+
dbg!(&descriptors_map);
2462+
2463+
let input_candidates: Vec<InputGroup> = InputCandidates::new(
2464+
&self.tx_graph(),
2465+
&self.local_chain(),
2466+
self.local_chain().tip().block_id().clone(),
2467+
outpoints,
2468+
descriptors_map,
2469+
Assets::new(),
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 = transaction_params
2478+
.outputs
2479+
.into_iter()
2480+
.map(|o| Output::with_script(o.0, o.1))
2481+
.collect::<Vec<_>>();
2482+
2483+
let (selection, metrics) = create_selection(CreateSelectionParams::new(
2484+
input_candidates,
2485+
public_change_descriptor
2486+
.at_derivation_index(next_change_index)
2487+
.map_err(|_| CreateBdkTxError::CannotCreateTx)?,
2488+
outputs_vector,
2489+
transaction_params.target_feerate,
2490+
))
2491+
.map_err(|_| CreateBdkTxError::CannotCreateTx)?;
2492+
2493+
let (psbt, _) = create_psbt(CreatePsbtParams::new(selection))
2494+
.map_err(|_| CreateBdkTxError::CannotCreateTx)?;
2495+
2496+
Ok(psbt)
2497+
}
2498+
}
2499+
2500+
#[derive(Debug)]
2501+
pub enum CreateBdkTxError {
2502+
CannotCreateTx,
2503+
}
2504+
24282505
impl AsRef<bdk_chain::tx_graph::TxGraph<ConfirmationBlockTime>> for Wallet {
24292506
fn as_ref(&self) -> &bdk_chain::tx_graph::TxGraph<ConfirmationBlockTime> {
24302507
self.indexed_graph.graph()

0 commit comments

Comments
 (0)