|
| 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