feat: add MVP transaction builder type

This commit is contained in:
thunderbiscuit 2023-10-19 09:49:16 -04:00
parent 372f79a10f
commit 1521811e9b
No known key found for this signature in database
GPG Key ID: 88253696EB836462
3 changed files with 279 additions and 267 deletions

View File

@ -1,7 +1,7 @@
namespace bdk {}; namespace bdk {};
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// bdk crate // bdk crate - root module
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
enum KeychainKind { enum KeychainKind {
@ -86,6 +86,17 @@ interface Wallet {
interface Update {}; interface Update {};
interface TxBuilder {
constructor();
TxBuilder add_recipient(Script script, u64 amount);
TxBuilder fee_rate(float sat_per_vbyte);
[Throws=BdkError]
string finish([ByRef] Wallet wallet);
};
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// bdk crate - descriptor module // bdk crate - descriptor module
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------

View File

@ -6,6 +6,7 @@ mod psbt;
mod wallet; mod wallet;
use bdk::bitcoin::address::{NetworkChecked, NetworkUnchecked}; use bdk::bitcoin::address::{NetworkChecked, NetworkUnchecked};
use bdk::bitcoin::blockdata::script::ScriptBuf as BdkScriptBuf;
use bdk::bitcoin::Address as BdkAddress; use bdk::bitcoin::Address as BdkAddress;
use bdk::bitcoin::Network as BdkNetwork; use bdk::bitcoin::Network as BdkNetwork;
use bdk::wallet::AddressIndex as BdkAddressIndex; use bdk::wallet::AddressIndex as BdkAddressIndex;
@ -23,6 +24,7 @@ use crate::keys::DerivationPath;
use crate::keys::DescriptorPublicKey; use crate::keys::DescriptorPublicKey;
use crate::keys::DescriptorSecretKey; use crate::keys::DescriptorSecretKey;
use crate::keys::Mnemonic; use crate::keys::Mnemonic;
use crate::wallet::TxBuilder;
use crate::wallet::Update; use crate::wallet::Update;
use crate::wallet::Wallet; use crate::wallet::Wallet;
use bdk::keys::bip39::WordCount; use bdk::keys::bip39::WordCount;
@ -453,11 +455,11 @@ impl Address {
self.inner.network.into() self.inner.network.into()
} }
// fn script_pubkey(&self) -> Arc<Script> { fn script_pubkey(&self) -> Arc<Script> {
// Arc::new(Script { Arc::new(Script {
// inner: self.inner.script_pubkey(), inner: self.inner.script_pubkey(),
// }) })
// } }
fn to_qr_uri(&self) -> String { fn to_qr_uri(&self) -> String {
self.inner.to_qr_uri() self.inner.to_qr_uri()
@ -495,24 +497,24 @@ impl From<Address> for BdkAddress {
// program: Vec<u8>, // program: Vec<u8>,
// }, // },
// } // }
//
// /// A Bitcoin script. /// A Bitcoin script.
// #[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
// pub struct Script { pub struct Script {
// inner: BdkScript, inner: BdkScriptBuf,
// } }
//
// impl Script { impl Script {
// fn new(raw_output_script: Vec<u8>) -> Self { fn new(raw_output_script: Vec<u8>) -> Self {
// let script: BdkScript = BdkScript::from(raw_output_script); let script: BdkScriptBuf = BdkScriptBuf::from(raw_output_script);
// Script { inner: script } Script { inner: script }
// } }
//
// fn to_bytes(&self) -> Vec<u8> { fn to_bytes(&self) -> Vec<u8> {
// self.inner.to_bytes() self.inner.to_bytes()
// } }
// } }
//
// impl From<BdkScript> for Script { // impl From<BdkScript> for Script {
// fn from(bdk_script: BdkScript) -> Self { // fn from(bdk_script: BdkScript) -> Self {
// Script { inner: bdk_script } // Script { inner: bdk_script }

View File

@ -1,10 +1,11 @@
use crate::descriptor::Descriptor; use crate::descriptor::Descriptor;
use crate::Balance;
use crate::{AddressIndex, AddressInfo, Network}; use crate::{AddressIndex, AddressInfo, Network};
use crate::{Balance, Script};
use bdk::wallet::Update as BdkUpdate; use bdk::wallet::Update as BdkUpdate;
use bdk::Error as BdkError;
use bdk::Wallet as BdkWallet; use bdk::Wallet as BdkWallet;
use bdk::{Error as BdkError, FeeRate};
use std::sync::{Arc, Mutex, MutexGuard}; use std::sync::{Arc, Mutex, MutexGuard};
use bdk::bitcoin::blockdata::script::ScriptBuf as BdkScriptBuf;
#[derive(Debug)] #[derive(Debug)]
pub struct Wallet { pub struct Wallet {
@ -314,247 +315,245 @@ pub struct Update(pub(crate) BdkUpdate);
// } // }
// } // }
// //
// /// A transaction builder. /// A transaction builder.
// /// After creating the TxBuilder, you set options on it until finally calling finish to consume the builder and generate the transaction. /// After creating the TxBuilder, you set options on it until finally calling finish to consume the builder and generate the transaction.
// /// Each method on the TxBuilder returns an instance of a new TxBuilder with the option set/added. /// Each method on the TxBuilder returns an instance of a new TxBuilder with the option set/added.
// #[derive(Clone, Debug)] #[derive(Clone, Debug)]
// pub(crate) struct TxBuilder { pub(crate) struct TxBuilder {
// pub(crate) recipients: Vec<(BdkScript, u64)>, pub(crate) recipients: Vec<(BdkScriptBuf, u64)>,
// pub(crate) utxos: Vec<OutPoint>, // pub(crate) utxos: Vec<OutPoint>,
// pub(crate) unspendable: HashSet<OutPoint>, // pub(crate) unspendable: HashSet<OutPoint>,
// pub(crate) change_policy: ChangeSpendPolicy, // pub(crate) change_policy: ChangeSpendPolicy,
// pub(crate) manually_selected_only: bool, // pub(crate) manually_selected_only: bool,
// pub(crate) fee_rate: Option<f32>, pub(crate) fee_rate: Option<f32>,
// pub(crate) fee_absolute: Option<u64>, // pub(crate) fee_absolute: Option<u64>,
// pub(crate) drain_wallet: bool, // pub(crate) drain_wallet: bool,
// pub(crate) drain_to: Option<BdkScript>, // pub(crate) drain_to: Option<BdkScript>,
// pub(crate) rbf: Option<RbfValue>, // pub(crate) rbf: Option<RbfValue>,
// pub(crate) data: Vec<u8>, // pub(crate) data: Vec<u8>,
// } }
//
// impl TxBuilder { impl TxBuilder {
// pub(crate) fn new() -> Self { pub(crate) fn new() -> Self {
// TxBuilder { TxBuilder {
// recipients: Vec::new(), recipients: Vec::new(),
// utxos: Vec::new(), // utxos: Vec::new(),
// unspendable: HashSet::new(), // unspendable: HashSet::new(),
// change_policy: ChangeSpendPolicy::ChangeAllowed, // change_policy: ChangeSpendPolicy::ChangeAllowed,
// manually_selected_only: false, // manually_selected_only: false,
// fee_rate: None, fee_rate: None,
// fee_absolute: None, // fee_absolute: None,
// drain_wallet: false, // drain_wallet: false,
// drain_to: None, // drain_to: None,
// rbf: None, // rbf: None,
// data: Vec::new(), // data: Vec::new(),
// } }
// } }
//
// /// Add a recipient to the internal list. /// Add a recipient to the internal list.
// pub(crate) fn add_recipient(&self, script: Arc<Script>, amount: u64) -> Arc<Self> { pub(crate) fn add_recipient(&self, script: Arc<Script>, amount: u64) -> Arc<Self> {
// let mut recipients: Vec<(BdkScript, u64)> = self.recipients.clone(); let mut recipients: Vec<(BdkScriptBuf, u64)> = self.recipients.clone();
// recipients.append(&mut vec![(script.inner.clone(), amount)]); recipients.append(&mut vec![(script.inner.clone(), amount)]);
// Arc::new(TxBuilder {
// recipients, Arc::new(TxBuilder {
// ..self.clone() recipients,
// }) ..self.clone()
// } })
// }
// pub(crate) fn set_recipients(&self, recipients: Vec<ScriptAmount>) -> Arc<Self> {
// let recipients = recipients // pub(crate) fn set_recipients(&self, recipients: Vec<ScriptAmount>) -> Arc<Self> {
// .iter() // let recipients = recipients
// .map(|script_amount| (script_amount.script.inner.clone(), script_amount.amount)) // .iter()
// .collect(); // .map(|script_amount| (script_amount.script.inner.clone(), script_amount.amount))
// Arc::new(TxBuilder { // .collect();
// recipients, // Arc::new(TxBuilder {
// ..self.clone() // recipients,
// }) // ..self.clone()
// } // })
// // }
// /// Add a utxo to the internal list of unspendable utxos. Its important to note that the "must-be-spent"
// /// utxos added with [TxBuilder.addUtxo] have priority over this. See the Rust docs of the two linked methods for more details. // /// Add a utxo to the internal list of unspendable utxos. Its important to note that the "must-be-spent"
// pub(crate) fn add_unspendable(&self, unspendable: OutPoint) -> Arc<Self> { // /// utxos added with [TxBuilder.addUtxo] have priority over this. See the Rust docs of the two linked methods for more details.
// let mut unspendable_hash_set = self.unspendable.clone(); // pub(crate) fn add_unspendable(&self, unspendable: OutPoint) -> Arc<Self> {
// unspendable_hash_set.insert(unspendable); // let mut unspendable_hash_set = self.unspendable.clone();
// Arc::new(TxBuilder { // unspendable_hash_set.insert(unspendable);
// unspendable: unspendable_hash_set, // Arc::new(TxBuilder {
// ..self.clone() // unspendable: unspendable_hash_set,
// }) // ..self.clone()
// } // })
// // }
// /// Add an outpoint to the internal list of UTXOs that must be spent. These have priority over the "unspendable" //
// /// utxos, meaning that if a utxo is present both in the "utxos" and the "unspendable" list, it will be spent. // /// Add an outpoint to the internal list of UTXOs that must be spent. These have priority over the "unspendable"
// pub(crate) fn add_utxo(&self, outpoint: OutPoint) -> Arc<Self> { // /// utxos, meaning that if a utxo is present both in the "utxos" and the "unspendable" list, it will be spent.
// self.add_utxos(vec![outpoint]) // pub(crate) fn add_utxo(&self, outpoint: OutPoint) -> Arc<Self> {
// } // self.add_utxos(vec![outpoint])
// // }
// /// Add the list of outpoints to the internal list of UTXOs that must be spent. If an error occurs while adding //
// /// any of the UTXOs then none of them are added and the error is returned. These have priority over the "unspendable" // /// Add the list of outpoints to the internal list of UTXOs that must be spent. If an error occurs while adding
// /// utxos, meaning that if a utxo is present both in the "utxos" and the "unspendable" list, it will be spent. // /// any of the UTXOs then none of them are added and the error is returned. These have priority over the "unspendable"
// pub(crate) fn add_utxos(&self, mut outpoints: Vec<OutPoint>) -> Arc<Self> { // /// utxos, meaning that if a utxo is present both in the "utxos" and the "unspendable" list, it will be spent.
// let mut utxos = self.utxos.to_vec(); // pub(crate) fn add_utxos(&self, mut outpoints: Vec<OutPoint>) -> Arc<Self> {
// utxos.append(&mut outpoints); // let mut utxos = self.utxos.to_vec();
// Arc::new(TxBuilder { // utxos.append(&mut outpoints);
// utxos, // Arc::new(TxBuilder {
// ..self.clone() // utxos,
// }) // ..self.clone()
// } // })
// // }
// /// Do not spend change outputs. This effectively adds all the change outputs to the "unspendable" list. See TxBuilder.unspendable. //
// pub(crate) fn do_not_spend_change(&self) -> Arc<Self> { // /// Do not spend change outputs. This effectively adds all the change outputs to the "unspendable" list. See TxBuilder.unspendable.
// Arc::new(TxBuilder { // pub(crate) fn do_not_spend_change(&self) -> Arc<Self> {
// change_policy: ChangeSpendPolicy::ChangeForbidden, // Arc::new(TxBuilder {
// ..self.clone() // change_policy: ChangeSpendPolicy::ChangeForbidden,
// }) // ..self.clone()
// } // })
// // }
// /// Only spend utxos added by [add_utxo]. The wallet will not add additional utxos to the transaction even if they are //
// /// needed to make the transaction valid. // /// Only spend utxos added by [add_utxo]. The wallet will not add additional utxos to the transaction even if they are
// pub(crate) fn manually_selected_only(&self) -> Arc<Self> { // /// needed to make the transaction valid.
// Arc::new(TxBuilder { // pub(crate) fn manually_selected_only(&self) -> Arc<Self> {
// manually_selected_only: true, // Arc::new(TxBuilder {
// ..self.clone() // manually_selected_only: true,
// }) // ..self.clone()
// } // })
// // }
// /// Only spend change outputs. This effectively adds all the non-change outputs to the "unspendable" list. See TxBuilder.unspendable. //
// pub(crate) fn only_spend_change(&self) -> Arc<Self> { // /// Only spend change outputs. This effectively adds all the non-change outputs to the "unspendable" list. See TxBuilder.unspendable.
// Arc::new(TxBuilder { // pub(crate) fn only_spend_change(&self) -> Arc<Self> {
// change_policy: ChangeSpendPolicy::OnlyChange, // Arc::new(TxBuilder {
// ..self.clone() // change_policy: ChangeSpendPolicy::OnlyChange,
// }) // ..self.clone()
// } // })
// // }
// /// Replace the internal list of unspendable utxos with a new list. Its important to note that the "must-be-spent" utxos added with //
// /// TxBuilder.addUtxo have priority over these. See the Rust docs of the two linked methods for more details. // /// Replace the internal list of unspendable utxos with a new list. Its important to note that the "must-be-spent" utxos added with
// pub(crate) fn unspendable(&self, unspendable: Vec<OutPoint>) -> Arc<Self> { // /// TxBuilder.addUtxo have priority over these. See the Rust docs of the two linked methods for more details.
// Arc::new(TxBuilder { // pub(crate) fn unspendable(&self, unspendable: Vec<OutPoint>) -> Arc<Self> {
// unspendable: unspendable.into_iter().collect(), // Arc::new(TxBuilder {
// ..self.clone() // unspendable: unspendable.into_iter().collect(),
// }) // ..self.clone()
// } // })
// // }
// /// Set a custom fee rate. //
// pub(crate) fn fee_rate(&self, sat_per_vb: f32) -> Arc<Self> { /// Set a custom fee rate.
// Arc::new(TxBuilder { pub(crate) fn fee_rate(&self, sat_per_vb: f32) -> Arc<Self> {
// fee_rate: Some(sat_per_vb), Arc::new(TxBuilder {
// ..self.clone() fee_rate: Some(sat_per_vb),
// }) ..self.clone()
// } })
// }
// /// Set an absolute fee. //
// pub(crate) fn fee_absolute(&self, fee_amount: u64) -> Arc<Self> { // /// Set an absolute fee.
// Arc::new(TxBuilder { // pub(crate) fn fee_absolute(&self, fee_amount: u64) -> Arc<Self> {
// fee_absolute: Some(fee_amount), // Arc::new(TxBuilder {
// ..self.clone() // fee_absolute: Some(fee_amount),
// }) // ..self.clone()
// } // })
// // }
// /// Spend all the available inputs. This respects filters like TxBuilder.unspendable and the change policy. //
// pub(crate) fn drain_wallet(&self) -> Arc<Self> { // /// Spend all the available inputs. This respects filters like TxBuilder.unspendable and the change policy.
// Arc::new(TxBuilder { // pub(crate) fn drain_wallet(&self) -> Arc<Self> {
// drain_wallet: true, // Arc::new(TxBuilder {
// ..self.clone() // drain_wallet: true,
// }) // ..self.clone()
// } // })
// // }
// /// Sets the address to drain excess coins to. Usually, when there are excess coins they are sent to a change address //
// /// generated by the wallet. This option replaces the usual change address with an arbitrary ScriptPubKey of your choosing. // /// Sets the address to drain excess coins to. Usually, when there are excess coins they are sent to a change address
// /// Just as with a change output, if the drain output is not needed (the excess coins are too small) it will not be included // /// generated by the wallet. This option replaces the usual change address with an arbitrary ScriptPubKey of your choosing.
// /// in the resulting transaction. The only difference is that it is valid to use drain_to without setting any ordinary recipients // /// Just as with a change output, if the drain output is not needed (the excess coins are too small) it will not be included
// /// with add_recipient (but it is perfectly fine to add recipients as well). If you choose not to set any recipients, you should // /// in the resulting transaction. The only difference is that it is valid to use drain_to without setting any ordinary recipients
// /// either provide the utxos that the transaction should spend via add_utxos, or set drain_wallet to spend all of them. // /// with add_recipient (but it is perfectly fine to add recipients as well). If you choose not to set any recipients, you should
// /// When bumping the fees of a transaction made with this option, you probably want to use BumpFeeTxBuilder.allow_shrinking // /// either provide the utxos that the transaction should spend via add_utxos, or set drain_wallet to spend all of them.
// /// to allow this output to be reduced to pay for the extra fees. // /// When bumping the fees of a transaction made with this option, you probably want to use BumpFeeTxBuilder.allow_shrinking
// pub(crate) fn drain_to(&self, script: Arc<Script>) -> Arc<Self> { // /// to allow this output to be reduced to pay for the extra fees.
// Arc::new(TxBuilder { // pub(crate) fn drain_to(&self, script: Arc<Script>) -> Arc<Self> {
// drain_to: Some(script.inner.clone()), // Arc::new(TxBuilder {
// ..self.clone() // drain_to: Some(script.inner.clone()),
// }) // ..self.clone()
// } // })
// // }
// /// Enable signaling RBF. This will use the default `nsequence` value of `0xFFFFFFFD`. //
// pub(crate) fn enable_rbf(&self) -> Arc<Self> { // /// Enable signaling RBF. This will use the default `nsequence` value of `0xFFFFFFFD`.
// Arc::new(TxBuilder { // pub(crate) fn enable_rbf(&self) -> Arc<Self> {
// rbf: Some(RbfValue::Default), // Arc::new(TxBuilder {
// ..self.clone() // rbf: Some(RbfValue::Default),
// }) // ..self.clone()
// } // })
// // }
// /// Enable signaling RBF with a specific nSequence value. This can cause conflicts if the wallet's descriptors contain an //
// /// "older" (OP_CSV) operator and the given `nsequence` is lower than the CSV value. If the `nsequence` is higher than `0xFFFFFFFD` // /// Enable signaling RBF with a specific nSequence value. This can cause conflicts if the wallet's descriptors contain an
// /// an error will be thrown, since it would not be a valid nSequence to signal RBF. // /// "older" (OP_CSV) operator and the given `nsequence` is lower than the CSV value. If the `nsequence` is higher than `0xFFFFFFFD`
// pub(crate) fn enable_rbf_with_sequence(&self, nsequence: u32) -> Arc<Self> { // /// an error will be thrown, since it would not be a valid nSequence to signal RBF.
// Arc::new(TxBuilder { // pub(crate) fn enable_rbf_with_sequence(&self, nsequence: u32) -> Arc<Self> {
// rbf: Some(RbfValue::Value(nsequence)), // Arc::new(TxBuilder {
// ..self.clone() // rbf: Some(RbfValue::Value(nsequence)),
// }) // ..self.clone()
// } // })
// // }
// /// Add data as an output using OP_RETURN. //
// pub(crate) fn add_data(&self, data: Vec<u8>) -> Arc<Self> { // /// Add data as an output using OP_RETURN.
// Arc::new(TxBuilder { // pub(crate) fn add_data(&self, data: Vec<u8>) -> Arc<Self> {
// data, // Arc::new(TxBuilder {
// ..self.clone() // data,
// }) // ..self.clone()
// } // })
// // }
// /// Finish building the transaction. Returns the BIP174 PSBT. //
// pub(crate) fn finish(&self, wallet: &Wallet) -> Result<TxBuilderResult, BdkError> { /// Finish building the transaction. Returns the BIP174 PSBT.
// let wallet = wallet.get_wallet(); /// TODO: The TxBuilder in bdk returns a Psbt type
// let mut tx_builder = wallet.build_tx(); pub(crate) fn finish(&self, wallet: &Wallet) -> Result<String, BdkError> {
// for (script, amount) in &self.recipients { // TODO: I had to change the wallet here to be mutable. Why is that now required with the 1.0 API?
// tx_builder.add_recipient(script.clone(), *amount); let mut wallet = wallet.get_wallet();
// } let mut tx_builder = wallet.build_tx();
// tx_builder.change_policy(self.change_policy); // TODO: I'm not yet clear on the Script/ScriptBuf differences and whether this is the best
// if !self.utxos.is_empty() { // way to do this.
// let bdk_utxos: Vec<BdkOutPoint> = self.utxos.iter().map(BdkOutPoint::from).collect(); for (script, amount) in &self.recipients {
// let utxos: &[BdkOutPoint] = &bdk_utxos; tx_builder.add_recipient(script.clone(), *amount);
// tx_builder.add_utxos(utxos)?; }
// } // tx_builder.change_policy(self.change_policy);
// if !self.unspendable.is_empty() { // if !self.utxos.is_empty() {
// let bdk_unspendable: Vec<BdkOutPoint> = // let bdk_utxos: Vec<BdkOutPoint> = self.utxos.iter().map(BdkOutPoint::from).collect();
// self.unspendable.iter().map(BdkOutPoint::from).collect(); // let utxos: &[BdkOutPoint] = &bdk_utxos;
// tx_builder.unspendable(bdk_unspendable); // tx_builder.add_utxos(utxos)?;
// } // }
// if self.manually_selected_only { // if !self.unspendable.is_empty() {
// tx_builder.manually_selected_only(); // let bdk_unspendable: Vec<BdkOutPoint> =
// } // self.unspendable.iter().map(BdkOutPoint::from).collect();
// if let Some(sat_per_vb) = self.fee_rate { // tx_builder.unspendable(bdk_unspendable);
// tx_builder.fee_rate(FeeRate::from_sat_per_vb(sat_per_vb)); // }
// } // if self.manually_selected_only {
// if let Some(fee_amount) = self.fee_absolute { // tx_builder.manually_selected_only();
// tx_builder.fee_absolute(fee_amount); // }
// } if let Some(sat_per_vb) = self.fee_rate {
// if self.drain_wallet { tx_builder.fee_rate(FeeRate::from_sat_per_vb(sat_per_vb));
// tx_builder.drain_wallet(); }
// } // if let Some(fee_amount) = self.fee_absolute {
// if let Some(script) = &self.drain_to { // tx_builder.fee_absolute(fee_amount);
// tx_builder.drain_to(script.clone()); // }
// } // if self.drain_wallet {
// if let Some(rbf) = &self.rbf { // tx_builder.drain_wallet();
// match *rbf { // }
// RbfValue::Default => { // if let Some(script) = &self.drain_to {
// tx_builder.enable_rbf(); // tx_builder.drain_to(script.clone());
// } // }
// RbfValue::Value(nsequence) => { // if let Some(rbf) = &self.rbf {
// tx_builder.enable_rbf_with_sequence(Sequence(nsequence)); // match *rbf {
// } // RbfValue::Default => {
// } // tx_builder.enable_rbf();
// } // }
// if !&self.data.is_empty() { // RbfValue::Value(nsequence) => {
// tx_builder.add_data(self.data.as_slice()); // tx_builder.enable_rbf_with_sequence(Sequence(nsequence));
// } // }
// // }
// tx_builder // }
// .finish() // if !&self.data.is_empty() {
// .map(|(psbt, tx_details)| TxBuilderResult { // tx_builder.add_data(self.data.as_slice());
// psbt: Arc::new(PartiallySignedTransaction { // }
// inner: Mutex::new(psbt),
// }), tx_builder.finish().map(|psbt| psbt.serialize_hex())
// transaction_details: TransactionDetails::from(tx_details), }
// }) }
// }
// }
// //
// /// The BumpFeeTxBuilder is used to bump the fee on a transaction that has been broadcast and has its RBF flag set to true. // /// The BumpFeeTxBuilder is used to bump the fee on a transaction that has been broadcast and has its RBF flag set to true.
// #[derive(Clone)] // #[derive(Clone)]