Add TxBuilder coin selection methods

This commit is contained in:
Zoe Faltibà 2022-06-09 13:28:23 +02:00
parent 1a5a628a5d
commit 5f12900c6d
No known key found for this signature in database
GPG Key ID: 05CB7E816D1A93A8
2 changed files with 114 additions and 2 deletions

View File

@ -210,8 +210,24 @@ interface TxBuilder {
TxBuilder add_recipient(string address, u64 amount); TxBuilder add_recipient(string address, u64 amount);
TxBuilder add_unspendable(OutPoint unspendable);
TxBuilder add_utxo(OutPoint outpoint);
TxBuilder add_utxos(sequence<OutPoint> outpoints);
TxBuilder do_not_spend_change();
TxBuilder manually_selected_only();
TxBuilder only_spend_change();
TxBuilder unspendable(sequence<OutPoint> unspendable);
TxBuilder fee_rate(float sat_per_vbyte); TxBuilder fee_rate(float sat_per_vbyte);
TxBuilder fee_absolute(u64 fee_amount);
TxBuilder drain_wallet(); TxBuilder drain_wallet();
TxBuilder drain_to(string address); TxBuilder drain_to(string address);

View File

@ -1,7 +1,7 @@
use bdk::bitcoin::hashes::hex::ToHex; use bdk::bitcoin::hashes::hex::ToHex;
use bdk::bitcoin::secp256k1::Secp256k1; use bdk::bitcoin::secp256k1::Secp256k1;
use bdk::bitcoin::util::psbt::PartiallySignedTransaction; use bdk::bitcoin::util::psbt::PartiallySignedTransaction;
use bdk::bitcoin::{Address, Network, Script, Txid}; use bdk::bitcoin::{Address, Network, OutPoint as BdkOutPoint, Script, Txid};
use bdk::blockchain::any::{AnyBlockchain, AnyBlockchainConfig}; use bdk::blockchain::any::{AnyBlockchain, AnyBlockchainConfig};
use bdk::blockchain::{ use bdk::blockchain::{
electrum::ElectrumBlockchainConfig, esplora::EsploraBlockchainConfig, ConfigurableBlockchain, electrum::ElectrumBlockchainConfig, esplora::EsploraBlockchainConfig, ConfigurableBlockchain,
@ -12,13 +12,15 @@ use bdk::database::{AnyDatabaseConfig, ConfigurableDatabase};
use bdk::keys::bip39::{Language, Mnemonic, WordCount}; use bdk::keys::bip39::{Language, Mnemonic, WordCount};
use bdk::keys::{DerivableKey, ExtendedKey, GeneratableKey, GeneratedKey}; use bdk::keys::{DerivableKey, ExtendedKey, GeneratableKey, GeneratedKey};
use bdk::miniscript::BareCtx; use bdk::miniscript::BareCtx;
use bdk::wallet::tx_builder::ChangeSpendPolicy;
use bdk::wallet::AddressIndex as BdkAddressIndex; use bdk::wallet::AddressIndex as BdkAddressIndex;
use bdk::wallet::AddressInfo as BdkAddressInfo; use bdk::wallet::AddressInfo as BdkAddressInfo;
use bdk::{ use bdk::{
BlockTime, Error, FeeRate, KeychainKind, SignOptions, SyncOptions as BdkSyncOptions, BlockTime, Error, FeeRate, KeychainKind, SignOptions, SyncOptions as BdkSyncOptions,
Wallet as BdkWallet, Wallet as BdkWallet,
}; };
use std::convert::{From, TryFrom}; use std::collections::HashSet;
use std::convert::{From, TryFrom, TryInto};
use std::fmt; use std::fmt;
use std::ops::Deref; use std::ops::Deref;
use std::str::FromStr; use std::str::FromStr;
@ -173,11 +175,21 @@ struct Wallet {
wallet_mutex: Mutex<BdkWallet<AnyDatabase>>, wallet_mutex: Mutex<BdkWallet<AnyDatabase>>,
} }
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct OutPoint { pub struct OutPoint {
txid: String, txid: String,
vout: u32, vout: u32,
} }
impl From<&OutPoint> for BdkOutPoint {
fn from(x: &OutPoint) -> BdkOutPoint {
BdkOutPoint {
txid: Txid::from_str(&x.txid).unwrap(),
vout: x.vout,
}
}
}
pub struct TxOut { pub struct TxOut {
value: u64, value: u64,
address: String, address: String,
@ -394,7 +406,12 @@ enum RbfValue {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct TxBuilder { struct TxBuilder {
recipients: Vec<(String, u64)>, recipients: Vec<(String, u64)>,
utxos: Vec<OutPoint>,
unspendable: HashSet<OutPoint>,
change_policy: ChangeSpendPolicy,
manually_selected_only: bool,
fee_rate: Option<f32>, fee_rate: Option<f32>,
fee_absolute: Option<u64>,
drain_wallet: bool, drain_wallet: bool,
drain_to: Option<String>, drain_to: Option<String>,
rbf: Option<RbfValue>, rbf: Option<RbfValue>,
@ -405,7 +422,12 @@ impl TxBuilder {
fn new() -> Self { fn new() -> Self {
TxBuilder { TxBuilder {
recipients: Vec::new(), recipients: Vec::new(),
utxos: Vec::new(),
unspendable: HashSet::new(),
change_policy: ChangeSpendPolicy::ChangeAllowed,
manually_selected_only: false,
fee_rate: None, fee_rate: None,
fee_absolute: None,
drain_wallet: false, drain_wallet: false,
drain_to: None, drain_to: None,
rbf: None, rbf: None,
@ -422,6 +444,56 @@ impl TxBuilder {
}) })
} }
fn add_unspendable(&self, unspendable: OutPoint) -> Arc<Self> {
let mut unspendable_hash_set = self.unspendable.clone();
unspendable_hash_set.insert(unspendable);
Arc::new(TxBuilder {
unspendable: unspendable_hash_set,
..self.clone()
})
}
fn add_utxo(&self, outpoint: OutPoint) -> Arc<Self> {
self.add_utxos(vec![outpoint])
}
fn add_utxos(&self, mut outpoints: Vec<OutPoint>) -> Arc<Self> {
let mut utxos = self.utxos.to_vec();
utxos.append(&mut outpoints);
Arc::new(TxBuilder {
utxos,
..self.clone()
})
}
fn do_not_spend_change(&self) -> Arc<Self> {
Arc::new(TxBuilder {
change_policy: ChangeSpendPolicy::ChangeForbidden,
..self.clone()
})
}
fn manually_selected_only(&self) -> Arc<Self> {
Arc::new(TxBuilder {
manually_selected_only: true,
..self.clone()
})
}
fn only_spend_change(&self) -> Arc<Self> {
Arc::new(TxBuilder {
change_policy: ChangeSpendPolicy::OnlyChange,
..self.clone()
})
}
fn unspendable(&self, unspendable: Vec<OutPoint>) -> Arc<Self> {
Arc::new(TxBuilder {
unspendable: unspendable.into_iter().collect(),
..self.clone()
})
}
fn fee_rate(&self, sat_per_vb: f32) -> Arc<Self> { fn fee_rate(&self, sat_per_vb: f32) -> Arc<Self> {
Arc::new(TxBuilder { Arc::new(TxBuilder {
fee_rate: Some(sat_per_vb), fee_rate: Some(sat_per_vb),
@ -429,6 +501,13 @@ impl TxBuilder {
}) })
} }
fn fee_absolute(&self, fee_amount: u64) -> Arc<Self> {
Arc::new(TxBuilder {
fee_absolute: Some(fee_amount),
..self.clone()
})
}
fn drain_wallet(&self) -> Arc<Self> { fn drain_wallet(&self) -> Arc<Self> {
Arc::new(TxBuilder { Arc::new(TxBuilder {
drain_wallet: true, drain_wallet: true,
@ -470,9 +549,26 @@ impl TxBuilder {
for (address, amount) in &self.recipients { for (address, amount) in &self.recipients {
tx_builder.add_recipient(to_script_pubkey(address)?, *amount); tx_builder.add_recipient(to_script_pubkey(address)?, *amount);
} }
tx_builder.change_policy(self.change_policy);
if !self.utxos.is_empty() {
let bdk_utxos: Vec<BdkOutPoint> = self.utxos.iter().map(BdkOutPoint::from).collect();
let utxos: &[BdkOutPoint] = bdk_utxos[..].try_into().unwrap();
tx_builder.add_utxos(utxos)?;
}
if !self.unspendable.is_empty() {
let bdk_unspendable: Vec<BdkOutPoint> =
self.unspendable.iter().map(BdkOutPoint::from).collect();
tx_builder.unspendable(bdk_unspendable);
}
if self.manually_selected_only {
tx_builder.manually_selected_only();
}
if let Some(sat_per_vb) = self.fee_rate { if let Some(sat_per_vb) = self.fee_rate {
tx_builder.fee_rate(FeeRate::from_sat_per_vb(sat_per_vb)); tx_builder.fee_rate(FeeRate::from_sat_per_vb(sat_per_vb));
} }
if let Some(fee_amount) = self.fee_absolute {
tx_builder.fee_absolute(fee_amount);
}
if self.drain_wallet { if self.drain_wallet {
tx_builder.drain_wallet(); tx_builder.drain_wallet();
} }