Skip to content

Commit 673d602

Browse files
committed
wip: implement create_psbt for Wallet
1 parent 6905189 commit 673d602

File tree

4 files changed

+453
-3
lines changed

4 files changed

+453
-3
lines changed

wallet/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ bitcoin = { version = "0.32.6", features = [ "serde", "base64" ], default-featur
2222
serde = { version = "^1.0", features = ["derive"] }
2323
serde_json = { version = "^1.0" }
2424
bdk_chain = { version = "0.23.1", features = [ "miniscript", "serde" ], default-features = false }
25+
bdk_tx = { version = "0.1.0" }
26+
bdk_coin_select = { version = "0.4.0" }
2527

2628
# Optional dependencies
2729
bip39 = { version = "2.0", optional = true }
@@ -58,3 +60,7 @@ required-features = ["all-keys"]
5860
name = "miniscriptc"
5961
path = "examples/compiler.rs"
6062
required-features = ["compiler"]
63+
64+
[[example]]
65+
name = "psbt"
66+
required-features = ["test-utils"]

wallet/examples/psbt.rs

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
#![allow(unused_imports)]
2+
#![allow(clippy::print_stdout)]
3+
4+
use std::collections::HashMap;
5+
use std::str::FromStr;
6+
use std::sync::Arc;
7+
8+
use bdk_chain::BlockId;
9+
use bdk_chain::ConfirmationBlockTime;
10+
use bdk_chain::TxUpdate;
11+
use bdk_wallet::psbt_params::{Params, SelectionStrategy::*};
12+
use bdk_wallet::test_utils::*;
13+
use bdk_wallet::{KeychainKind::*, Update, Wallet};
14+
use bitcoin::FeeRate;
15+
use bitcoin::{
16+
consensus,
17+
secp256k1::{self, rand},
18+
Address, Amount, OutPoint, TxIn, TxOut,
19+
};
20+
use miniscript::descriptor::Descriptor;
21+
use miniscript::descriptor::KeyMap;
22+
use rand::Rng;
23+
24+
// This example shows how to create a PSBT using BDK Wallet.
25+
26+
const NETWORK: bitcoin::Network = bitcoin::Network::Signet;
27+
const SEND_TO: &str = "tb1pw3g5qvnkryghme7pyal228ekj6vq48zc5k983lqtlr2a96n4xw0q5ejknw";
28+
const AMOUNT: Amount = Amount::from_sat(42_000);
29+
const FEERATE: f64 = 2.0; // sat/vb
30+
31+
fn main() -> anyhow::Result<()> {
32+
let (desc, change_desc) = get_test_wpkh_and_change_desc();
33+
let secp = secp256k1::Secp256k1::new();
34+
let mut rng = rand::thread_rng();
35+
36+
// Assuming these are private descriptors, parse the KeyMap now which will come
37+
// in handy when it comes to signing the PSBT.
38+
let keymap: KeyMap = [desc.to_string(), change_desc.to_string()]
39+
.iter()
40+
.flat_map(|s| Descriptor::parse_descriptor(&secp, s).unwrap().1)
41+
.collect();
42+
43+
// Create wallet and fund it.
44+
let mut wallet = Wallet::create(desc, change_desc)
45+
.network(NETWORK)
46+
.create_wallet_no_persist()?;
47+
48+
fund_wallet(&mut wallet, &mut rng)?;
49+
50+
let utxos = wallet
51+
.list_unspent()
52+
.map(|output| (output.outpoint, output))
53+
.collect::<HashMap<_, _>>();
54+
55+
// Build params.
56+
let mut params = Params::default();
57+
let addr = Address::from_str(SEND_TO)?.require_network(NETWORK)?;
58+
let feerate = feerate_unchecked(FEERATE);
59+
params
60+
.add_recipients([(addr, AMOUNT)])
61+
.feerate(feerate)
62+
.coin_selection(SingleRandomDraw);
63+
64+
// Create PSBT (which also returns the Finalizer).
65+
let (mut psbt, finalizer) = wallet.create_psbt(params, &mut rng)?;
66+
67+
// dbg!(&psbt);
68+
69+
let tx = &psbt.unsigned_tx;
70+
for txin in &tx.input {
71+
let op = txin.previous_output;
72+
let output = utxos.get(&op).unwrap();
73+
println!("TxIn: {}", output.txout.value);
74+
}
75+
for txout in &tx.output {
76+
println!("TxOut: {}", txout.value);
77+
}
78+
79+
let signer = bdk_tx::Signer(keymap);
80+
let sign_res = psbt.sign(&signer, &secp);
81+
println!("Signed: {}", sign_res.is_ok());
82+
83+
let finalize_res = finalizer.finalize(&mut psbt);
84+
println!("Finalized: {}", finalize_res.is_finalized());
85+
86+
let tx = psbt.extract_tx()?;
87+
88+
let feerate = wallet.calculate_fee_rate(&tx)?;
89+
println!("Feerate: {} sat/vb", bdk_wallet::floating_rate!(feerate));
90+
91+
// println!("{}", consensus::encode::serialize_hex(&tx));
92+
93+
Ok(())
94+
}
95+
96+
fn fund_wallet(wallet: &mut Wallet, rng: &mut impl Rng) -> anyhow::Result<()> {
97+
let anchor = ConfirmationBlockTime {
98+
block_id: BlockId {
99+
height: 260071,
100+
hash: "000000099f67ae6469d1ad0525d756e24d4b02fbf27d65b3f413d5feb367ec48".parse()?,
101+
},
102+
confirmation_time: 1752184658,
103+
};
104+
insert_checkpoint(wallet, anchor.block_id);
105+
106+
// Fund wallet with several random utxos
107+
for i in 0..21 {
108+
let value = 10_000 * (i + 1) + (100 * rng.gen_range(0..10));
109+
let addr = wallet.reveal_next_address(External).address;
110+
receive_output_to_addr(
111+
wallet,
112+
addr,
113+
Amount::from_sat(value),
114+
ReceiveTo::Block(anchor),
115+
);
116+
}
117+
118+
Ok(())
119+
}
120+
121+
// Note: this is borrowed from `test-utils`, but here the tx appears as a coinbase tx
122+
// and inserting it does not automatically include a timestamp.
123+
fn receive_output_to_addr(
124+
wallet: &mut Wallet,
125+
addr: Address,
126+
value: Amount,
127+
receive_to: impl Into<ReceiveTo>,
128+
) -> OutPoint {
129+
let tx = bitcoin::Transaction {
130+
lock_time: bitcoin::absolute::LockTime::ZERO,
131+
version: bitcoin::transaction::Version::TWO,
132+
input: vec![TxIn::default()],
133+
output: vec![TxOut {
134+
script_pubkey: addr.script_pubkey(),
135+
value,
136+
}],
137+
};
138+
139+
// Insert tx
140+
let txid = tx.compute_txid();
141+
let mut tx_update = TxUpdate::default();
142+
tx_update.txs = vec![Arc::new(tx)];
143+
wallet
144+
.apply_update(Update {
145+
tx_update,
146+
..Default::default()
147+
})
148+
.unwrap();
149+
150+
// Insert anchor or last-seen.
151+
match receive_to.into() {
152+
ReceiveTo::Block(anchor) => insert_anchor(wallet, txid, anchor),
153+
ReceiveTo::Mempool(last_seen) => insert_seen_at(wallet, txid, last_seen),
154+
}
155+
156+
OutPoint { txid, vout: 0 }
157+
}

0 commit comments

Comments
 (0)