[workspace]
members = ["macros", "testutils", "testutils-macros"]
+
+# Generate docs with nightly to add the "features required" badge
+# https://stackoverflow.com/questions/61417452/how-to-get-a-feature-requirement-tag-in-the-documentation-generated-by-cargo-do
+[package.metadata.docs.rs]
+features = ["compiler", "electrum", "esplora", "compact_filters", "key-value-db"]
+# defines the configuration attribute `docsrs`
+rustdoc-args = ["--cfg", "docsrs"]
use magical_bitcoin_wallet::bitcoin;
use magical_bitcoin_wallet::database::MemoryDatabase;
use magical_bitcoin_wallet::descriptor::HDKeyPaths;
-use magical_bitcoin_wallet::types::ScriptType;
use magical_bitcoin_wallet::wallet::address_validator::{AddressValidator, AddressValidatorError};
+use magical_bitcoin_wallet::ScriptType;
use magical_bitcoin_wallet::{OfflineWallet, Wallet};
use bitcoin::hashes::hex::FromHex;
// SOFTWARE.
use std::collections::HashSet;
+use std::fmt;
use std::path::Path;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
})
}
- fn process_tx<D: BatchDatabase + DatabaseUtils>(
+ fn process_tx<D: BatchDatabase>(
&self,
database: &mut D,
tx: &Transaction,
vec![Capability::FullHistory].into_iter().collect()
}
- fn setup<D: BatchDatabase + DatabaseUtils, P: 'static + Progress>(
+ fn setup<D: BatchDatabase, P: 'static + Progress>(
&self,
_stop_gap: Option<usize>, // TODO: move to electrum and esplora only
database: &mut D,
Global(Box<crate::error::Error>),
}
+impl fmt::Display for CompactFiltersError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{:?}", self)
+ }
+}
+
+impl std::error::Error for CompactFiltersError {}
+
macro_rules! impl_error {
( $from:ty, $to:ident ) => {
impl std::convert::From<$from> for CompactFiltersError {
*self.connected.read().unwrap()
}
- pub fn reader_thread(
+ fn reader_thread(
network: Network,
connection: TcpStream,
reader_thread_responses: Arc<RwLock<ResponsesMap>>,
use self::utils::{ELSGetHistoryRes, ELSListUnspentRes, ElectrumLikeSync};
use super::*;
-use crate::database::{BatchDatabase, DatabaseUtils};
+use crate::database::BatchDatabase;
use crate::error::Error;
use crate::FeeRate;
.collect()
}
- fn setup<D: BatchDatabase + DatabaseUtils, P: Progress>(
+ fn setup<D: BatchDatabase, P: Progress>(
&self,
stop_gap: Option<usize>,
database: &mut D,
// SOFTWARE.
use std::collections::{HashMap, HashSet};
+use std::fmt;
use futures::stream::{self, StreamExt, TryStreamExt};
.collect()
}
- fn setup<D: BatchDatabase + DatabaseUtils, P: Progress>(
+ fn setup<D: BatchDatabase, P: Progress>(
&self,
stop_gap: Option<usize>,
database: &mut D,
TransactionNotFound(Txid),
}
+impl fmt::Display for EsploraError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{:?}", self)
+ }
+}
+
+impl std::error::Error for EsploraError {}
+
impl From<reqwest::Error> for EsploraError {
fn from(other: reqwest::Error) -> Self {
EsploraError::Reqwest(other)
use bitcoin::{Transaction, Txid};
-use crate::database::{BatchDatabase, DatabaseUtils};
+use crate::database::BatchDatabase;
use crate::error::Error;
use crate::FeeRate;
-pub mod utils;
+pub(crate) mod utils;
#[cfg(feature = "electrum")]
+#[cfg_attr(docsrs, doc(cfg(feature = "electrum")))]
pub mod electrum;
#[cfg(feature = "electrum")]
pub use self::electrum::ElectrumBlockchain;
#[cfg(feature = "esplora")]
+#[cfg_attr(docsrs, doc(cfg(feature = "esplora")))]
pub mod esplora;
#[cfg(feature = "esplora")]
pub use self::esplora::EsploraBlockchain;
#[cfg(feature = "compact_filters")]
+#[cfg_attr(docsrs, doc(cfg(feature = "compact_filters")))]
pub mod compact_filters;
+#[cfg(feature = "compact_filters")]
+pub use self::compact_filters::CompactFiltersBlockchain;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Capability {
pub trait OnlineBlockchain: Blockchain {
fn get_capabilities(&self) -> HashSet<Capability>;
- fn setup<D: BatchDatabase + DatabaseUtils, P: 'static + Progress>(
+ fn setup<D: BatchDatabase, P: 'static + Progress>(
&self,
stop_gap: Option<usize>,
database: &mut D,
progress_update: P,
) -> Result<(), Error>;
- fn sync<D: BatchDatabase + DatabaseUtils, P: 'static + Progress>(
+ fn sync<D: BatchDatabase, P: 'static + Progress>(
&self,
stop_gap: Option<usize>,
database: &mut D,
maybe_await!(self.deref().get_capabilities())
}
- fn setup<D: BatchDatabase + DatabaseUtils, P: 'static + Progress>(
+ fn setup<D: BatchDatabase, P: 'static + Progress>(
&self,
stop_gap: Option<usize>,
database: &mut D,
maybe_await!(self.deref().setup(stop_gap, database, progress_update))
}
- fn sync<D: BatchDatabase + DatabaseUtils, P: 'static + Progress>(
+ fn sync<D: BatchDatabase, P: 'static + Progress>(
&self,
stop_gap: Option<usize>,
database: &mut D,
// Provided methods down here...
- fn electrum_like_setup<D: BatchDatabase + DatabaseUtils, P: Progress>(
+ fn electrum_like_setup<D: BatchDatabase, P: Progress>(
&self,
stop_gap: Option<usize>,
database: &mut D,
Ok(())
}
- fn check_tx_and_descendant<D: DatabaseUtils + BatchDatabase>(
+ fn check_tx_and_descendant<D: BatchDatabase>(
&self,
database: &mut D,
txid: &Txid,
Ok(to_check_later)
}
- fn check_history<D: DatabaseUtils + BatchDatabase>(
+ fn check_history<D: BatchDatabase>(
&self,
database: &mut D,
script_pubkey: Script,
use crate::types::ScriptType;
use crate::{FeeRate, TxBuilder, Wallet};
-fn parse_addressee(s: &str) -> Result<(Address, u64), String> {
+fn parse_recipient(s: &str) -> Result<(Address, u64), String> {
let parts: Vec<_> = s.split(":").collect();
if parts.len() != 2 {
return Err("Invalid format".to_string());
OutPoint::from_str(s).map_err(|e| format!("{:?}", e))
}
-fn addressee_validator(s: String) -> Result<(), String> {
- parse_addressee(&s).map(|_| ())
+fn recipient_validator(s: String) -> Result<(), String> {
+ parse_recipient(&s).map(|_| ())
}
fn outpoint_validator(s: String) -> Result<(), String> {
Arg::with_name("to")
.long("to")
.value_name("ADDRESS:SAT")
- .help("Adds an addressee to the transaction")
+ .help("Adds a recipient to the transaction")
.takes_value(true)
.number_of_values(1)
.required(true)
.multiple(true)
- .validator(addressee_validator),
+ .validator(recipient_validator),
)
.arg(
Arg::with_name("send_all")
.short("all")
.long("send_all")
- .help("Sends all the funds (or all the selected utxos). Requires only one addressees of value 0"),
+ .help("Sends all the funds (or all the selected utxos). Requires only one recipients of value 0"),
)
.arg(
Arg::with_name("enable_rbf")
"satoshi": wallet.get_balance()?
}))
} else if let Some(sub_matches) = matches.subcommand_matches("create_tx") {
- let addressees = sub_matches
+ let recipients = sub_matches
.values_of("to")
.unwrap()
- .map(|s| parse_addressee(s))
+ .map(|s| parse_recipient(s))
.collect::<Result<Vec<_>, _>>()
.map_err(|s| Error::Generic(s))?;
- let mut tx_builder = TxBuilder::from_addressees(addressees);
+ let mut tx_builder = TxBuilder::with_recipients(recipients);
if sub_matches.is_present("send_all") {
tx_builder = tx_builder.send_all();
}))
} else if let Some(sub_matches) = matches.subcommand_matches("finalize_psbt") {
let psbt = base64::decode(&sub_matches.value_of("psbt").unwrap()).unwrap();
- let mut psbt: PartiallySignedTransaction = deserialize(&psbt).unwrap();
+ let psbt: PartiallySignedTransaction = deserialize(&psbt).unwrap();
let assume_height = sub_matches
.value_of("assume_height")
.and_then(|s| Some(s.parse().unwrap()));
- let finalized = wallet.finalize_psbt(&mut psbt, assume_height)?;
+ let (psbt, finalized) = wallet.finalize_psbt(psbt, assume_height)?;
Ok(json!({
"psbt": base64::encode(&serialize(&psbt)),
"is_finalized": finalized,
use crate::error::Error;
use crate::types::*;
-#[cfg(any(feature = "key-value-db", feature = "default"))]
-pub mod keyvalue;
-pub mod memory;
+#[cfg(feature = "key-value-db")]
+pub(crate) mod keyvalue;
+pub mod memory;
pub use memory::MemoryDatabase;
pub trait BatchOperations {
fn commit_batch(&mut self, batch: Self::Batch) -> Result<(), Error>;
}
-pub trait DatabaseUtils: Database {
+pub(crate) trait DatabaseUtils: Database {
fn is_mine(&self, script: &Script) -> Result<bool, Error> {
self.get_path_from_script_pubkey(script)
.map(|o| o.is_some())
}
}
+impl std::error::Error for Error {}
+
impl_error!(bitcoin::util::bip32::Error, BIP32);
impl_error!(bitcoin::util::base58::Error, Base58);
impl_error!(bitcoin::util::key::Error, PK);
use std::cmp::max;
use std::collections::{BTreeMap, HashSet, VecDeque};
+use std::fmt;
use std::sync::Arc;
use serde::ser::SerializeMap;
IncompatibleConditions,
}
+impl fmt::Display for PolicyError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{:?}", self)
+ }
+}
+
+impl std::error::Error for PolicyError {}
+
impl Policy {
- pub fn new(item: SatisfiableItem) -> Self {
+ fn new(item: SatisfiableItem) -> Self {
Policy {
id: item.id(),
item,
}
}
- pub fn make_and(a: Option<Policy>, b: Option<Policy>) -> Result<Option<Policy>, PolicyError> {
+ fn make_and(a: Option<Policy>, b: Option<Policy>) -> Result<Option<Policy>, PolicyError> {
match (a, b) {
(None, None) => Ok(None),
(Some(x), None) | (None, Some(x)) => Ok(Some(x)),
}
}
- pub fn make_or(a: Option<Policy>, b: Option<Policy>) -> Result<Option<Policy>, PolicyError> {
+ fn make_or(a: Option<Policy>, b: Option<Policy>) -> Result<Option<Policy>, PolicyError> {
match (a, b) {
(None, None) => Ok(None),
(Some(x), None) | (None, Some(x)) => Ok(Some(x)),
}
}
- pub fn make_thresh(
- items: Vec<Policy>,
- threshold: usize,
- ) -> Result<Option<Policy>, PolicyError> {
+ fn make_thresh(items: Vec<Policy>, threshold: usize) -> Result<Option<Policy>, PolicyError> {
if threshold == 0 {
return Ok(None);
}
TransactionNotFound,
TransactionConfirmed,
IrreplaceableTransaction,
- FeeRateTooLow(crate::wallet::utils::FeeRate),
+ FeeRateTooLow {
+ required: crate::types::FeeRate,
+ },
ChecksumMismatch,
DifferentDescriptorStructure,
// only enables the `doc_cfg` feature when
// the `docsrs` configuration attribute is defined
-#[cfg_attr(docsrs, feature(doc_cfg))]
+#![cfg_attr(docsrs, feature(doc_cfg))]
pub extern crate bitcoin;
extern crate log;
#[cfg(feature = "electrum")]
pub extern crate electrum_client;
-#[cfg(feature = "electrum")]
-pub use electrum_client::client::Client;
#[cfg(feature = "esplora")]
pub extern crate reqwest;
-#[cfg(feature = "esplora")]
-pub use blockchain::esplora::EsploraBlockchain;
#[cfg(feature = "key-value-db")]
pub extern crate sled;
pub mod blockchain;
pub mod database;
pub mod descriptor;
-pub mod psbt;
-pub mod types;
+pub(crate) mod psbt;
+pub(crate) mod types;
pub mod wallet;
-pub use descriptor::ExtendedDescriptor;
+pub use error::Error;
+pub use types::*;
+pub use wallet::address_validator;
+pub use wallet::signer;
pub use wallet::tx_builder::TxBuilder;
-pub use wallet::utils::FeeRate;
pub use wallet::{OfflineWallet, Wallet};
+
+#[cfg(feature = "esplora")]
+pub use blockchain::esplora::EsploraBlockchain;
+
+#[cfg(feature = "electrum")]
+pub use blockchain::electrum::ElectrumBlockchain;
}
}
+#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
+// Internally stored as satoshi/vbyte
+pub struct FeeRate(f32);
+
+impl FeeRate {
+ pub fn from_btc_per_kvb(btc_per_kvb: f32) -> Self {
+ FeeRate(btc_per_kvb * 1e5)
+ }
+
+ pub fn from_sat_per_vb(sat_per_vb: f32) -> Self {
+ FeeRate(sat_per_vb)
+ }
+
+ pub fn default_min_relay_fee() -> Self {
+ FeeRate(1.0)
+ }
+
+ pub fn as_sat_vb(&self) -> f32 {
+ self.0
+ }
+}
+
+impl std::default::Default for FeeRate {
+ fn default() -> Self {
+ FeeRate::default_min_relay_fee()
+ }
+}
+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct UTXO {
pub outpoint: OutPoint,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
+use std::fmt;
+
use bitcoin::Script;
use crate::descriptor::HDKeyPaths;
InvalidScript,
}
+impl fmt::Display for AddressValidatorError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{:?}", self)
+ }
+}
+
+impl std::error::Error for AddressValidatorError {}
+
pub trait AddressValidator {
fn validate(
&self,
let addr = testutils!(@external descriptors, 10);
wallet
- .create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]))
+ .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
.unwrap();
}
}
use bitcoin::{Script, TxIn};
use crate::error::Error;
-use crate::types::UTXO;
+use crate::types::{FeeRate, UTXO};
pub type DefaultCoinSelectionAlgorithm = DumbCoinSelection;
#[derive(Debug)]
pub struct CoinSelectionResult {
pub txin: Vec<(TxIn, Script)>,
- pub total_amount: u64,
+ pub selected_amount: u64,
pub fee_amount: f32,
}
&self,
utxos: Vec<UTXO>,
use_all_utxos: bool,
- fee_rate: f32,
- outgoing_amount: u64,
+ fee_rate: FeeRate,
+ amount_needed: u64,
input_witness_weight: usize,
fee_amount: f32,
) -> Result<CoinSelectionResult, Error>;
&self,
mut utxos: Vec<UTXO>,
use_all_utxos: bool,
- fee_rate: f32,
+ fee_rate: FeeRate,
outgoing_amount: u64,
input_witness_weight: usize,
mut fee_amount: f32,
) -> Result<CoinSelectionResult, Error> {
let mut txin = Vec::new();
- let calc_fee_bytes = |wu| (wu as f32) * fee_rate / 4.0;
+ let calc_fee_bytes = |wu| (wu as f32) * fee_rate.as_sat_vb() / 4.0;
log::debug!(
- "outgoing_amount = `{}`, fee_amount = `{}`, fee_rate = `{}`",
+ "outgoing_amount = `{}`, fee_amount = `{}`, fee_rate = `{:?}`",
outgoing_amount,
fee_amount,
fee_rate
// sort so that we pick them starting from the larger.
utxos.sort_by(|a, b| a.txout.value.partial_cmp(&b.txout.value).unwrap());
- let mut total_amount: u64 = 0;
- while use_all_utxos || total_amount < outgoing_amount + (fee_amount.ceil() as u64) {
+ let mut selected_amount: u64 = 0;
+ while use_all_utxos || selected_amount < outgoing_amount + (fee_amount.ceil() as u64) {
let utxo = match utxos.pop() {
Some(utxo) => utxo,
- None if total_amount < outgoing_amount + (fee_amount.ceil() as u64) => {
+ None if selected_amount < outgoing_amount + (fee_amount.ceil() as u64) => {
return Err(Error::InsufficientFunds)
}
None if use_all_utxos => break,
);
txin.push((new_in, utxo.txout.script_pubkey));
- total_amount += utxo.txout.value;
+ selected_amount += utxo.txout.value;
}
Ok(CoinSelectionResult {
txin,
fee_amount,
- total_amount,
+ selected_amount,
})
}
}
let utxos = get_test_utxos();
let result = DumbCoinSelection
- .coin_select(utxos, false, 1.0, 250_000, P2WPKH_WITNESS_SIZE, 50.0)
+ .coin_select(
+ utxos,
+ false,
+ FeeRate::from_sat_per_vb(1.0),
+ 250_000,
+ P2WPKH_WITNESS_SIZE,
+ 50.0,
+ )
.unwrap();
assert_eq!(result.txin.len(), 2);
- assert_eq!(result.total_amount, 300_000);
+ assert_eq!(result.selected_amount, 300_000);
assert_eq!(result.fee_amount, 186.0);
}
let utxos = get_test_utxos();
let result = DumbCoinSelection
- .coin_select(utxos, true, 1.0, 20_000, P2WPKH_WITNESS_SIZE, 50.0)
+ .coin_select(
+ utxos,
+ true,
+ FeeRate::from_sat_per_vb(1.0),
+ 20_000,
+ P2WPKH_WITNESS_SIZE,
+ 50.0,
+ )
.unwrap();
assert_eq!(result.txin.len(), 2);
- assert_eq!(result.total_amount, 300_000);
+ assert_eq!(result.selected_amount, 300_000);
assert_eq!(result.fee_amount, 186.0);
}
let utxos = get_test_utxos();
let result = DumbCoinSelection
- .coin_select(utxos, false, 1.0, 20_000, P2WPKH_WITNESS_SIZE, 50.0)
+ .coin_select(
+ utxos,
+ false,
+ FeeRate::from_sat_per_vb(1.0),
+ 20_000,
+ P2WPKH_WITNESS_SIZE,
+ 50.0,
+ )
.unwrap();
assert_eq!(result.txin.len(), 1);
- assert_eq!(result.total_amount, 200_000);
+ assert_eq!(result.selected_amount, 200_000);
assert_eq!(result.fee_amount, 118.0);
}
let utxos = get_test_utxos();
DumbCoinSelection
- .coin_select(utxos, false, 1.0, 500_000, P2WPKH_WITNESS_SIZE, 50.0)
+ .coin_select(
+ utxos,
+ false,
+ FeeRate::from_sat_per_vb(1.0),
+ 500_000,
+ P2WPKH_WITNESS_SIZE,
+ 50.0,
+ )
.unwrap();
}
let utxos = get_test_utxos();
DumbCoinSelection
- .coin_select(utxos, false, 1000.0, 250_000, P2WPKH_WITNESS_SIZE, 50.0)
+ .coin_select(
+ utxos,
+ false,
+ FeeRate::from_sat_per_vb(1000.0),
+ 250_000,
+ P2WPKH_WITNESS_SIZE,
+ 50.0,
+ )
.unwrap();
}
}
pub mod signer;
pub mod time;
pub mod tx_builder;
-pub mod utils;
+pub(crate) mod utils;
+
+pub use utils::IsDust;
use address_validator::AddressValidator;
use signer::{Signer, SignerId, SignerOrdering, SignersContainer};
use tx_builder::TxBuilder;
-use utils::{After, FeeRate, IsDust, Older};
+use utils::{After, Older};
use crate::blockchain::{Blockchain, OfflineBlockchain, OnlineBlockchain, Progress};
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
&self,
builder: TxBuilder<Cs>,
) -> Result<(PSBT, TransactionDetails), Error> {
- if builder.addressees.is_empty() {
+ if builder.recipients.is_empty() {
return Err(Error::NoAddressees);
}
output: vec![],
};
- let fee_rate = builder.fee_rate.unwrap_or_default().as_sat_vb();
- if builder.send_all && builder.addressees.len() != 1 {
+ let fee_rate = builder.fee_rate.unwrap_or_default();
+ if builder.send_all && builder.recipients.len() != 1 {
return Err(Error::SendAllMultipleOutputs);
}
let mut outgoing: u64 = 0;
let mut received: u64 = 0;
- let calc_fee_bytes = |wu| (wu as f32) * fee_rate / 4.0;
+ let calc_fee_bytes = |wu| (wu as f32) * fee_rate.as_sat_vb() / 4.0;
fee_amount += calc_fee_bytes(tx.get_weight());
- for (index, (address, satoshi)) in builder.addressees.iter().enumerate() {
+ for (index, (address, satoshi)) in builder.recipients.iter().enumerate() {
let value = match builder.send_all {
true => 0,
false if satoshi.is_dust() => return Err(Error::OutputBelowDustLimit(index)),
)?;
let coin_selection::CoinSelectionResult {
txin,
- total_amount,
+ selected_amount,
mut fee_amount,
} = builder.coin_selection.coin_select(
available_utxos,
};
let mut fee_amount = fee_amount.ceil() as u64;
- let change_val = total_amount - outgoing - fee_amount;
+ let change_val = selected_amount - outgoing - fee_amount;
if !builder.send_all && !change_val.is_dust() {
let mut change_output = change_output.unwrap();
change_output.value = change_val;
}
// sort input/outputs according to the chosen algorithm
- builder.ordering.modify_tx(&mut tx);
+ builder.ordering.sort_tx(&mut tx);
let txid = tx.txid();
let psbt = self.complete_transaction(tx, prev_script_pubkeys, builder)?;
txid,
timestamp: time::get_timestamp(),
received,
- sent: total_amount,
+ sent: selected_amount,
fees: fee_amount,
height: None,
};
let new_feerate = builder.fee_rate.unwrap_or_default();
if new_feerate < required_feerate {
- return Err(Error::FeeRateTooLow(required_feerate));
+ return Err(Error::FeeRateTooLow {
+ required: required_feerate,
+ });
}
let mut fee_difference =
(new_feerate.as_sat_vb() * tx.get_weight() as f32 / 4.0).ceil() as u64 - details.fees;
)?;
let coin_selection::CoinSelectionResult {
txin,
- total_amount,
+ selected_amount,
fee_amount,
} = builder.coin_selection.coin_select(
available_utxos,
use_all_utxos,
- new_feerate.as_sat_vb(),
+ new_feerate,
fee_difference
.checked_sub(removed_change_output.value)
.unwrap_or(0),
.for_each(|i| i.sequence = tx.input[0].sequence);
tx.input.extend_from_slice(&mut txin);
- details.sent += total_amount;
- total_amount
+ details.sent += selected_amount;
+ selected_amount
} else {
// otherwise just remove the output and add 0 new coins
0
}
// sort input/outputs according to the chosen algorithm
- builder.ordering.modify_tx(&mut tx);
+ builder.ordering.sort_tx(&mut tx);
// TODO: check that we are not replacing more than 100 txs from mempool
}
// attempt to finalize
- let finalized = self.finalize_psbt(&mut psbt, assume_height)?;
-
- Ok((psbt, finalized))
+ self.finalize_psbt(psbt, assume_height)
}
pub fn policies(&self, script_type: ScriptType) -> Result<Option<Policy>, Error> {
pub fn finalize_psbt(
&self,
- psbt: &mut PSBT,
+ mut psbt: PSBT,
assume_height: Option<u32>,
- ) -> Result<bool, Error> {
+ ) -> Result<(PSBT, bool), Error> {
let mut tx = psbt.global.unsigned_tx.clone();
for (n, (input, psbt_input)) in tx.input.iter_mut().zip(psbt.inputs.iter()).enumerate() {
desc
} else {
debug!("Couldn't find the right derived descriptor for input {}", n);
- return Ok(false);
+ return Ok((psbt, false));
};
match desc.satisfy(
Ok(_) => continue,
Err(e) => {
debug!("satisfy error {:?} for input {}", e, n);
- return Ok(false);
+ return Ok((psbt, false));
}
}
}
psbt_input.final_script_witness = Some(input.witness);
}
- Ok(true)
+ Ok((psbt, true))
}
// Internals
#[test]
#[should_panic(expected = "NoAddressees")]
- fn test_create_tx_empty_addressees() {
+ fn test_create_tx_empty_recipients() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
wallet
- .create_tx(TxBuilder::from_addressees(vec![]).version(0))
+ .create_tx(TxBuilder::with_recipients(vec![]).version(0))
.unwrap();
}
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
wallet
- .create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]).version(0))
+ .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).version(0))
.unwrap();
}
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
let addr = wallet.get_new_address().unwrap();
wallet
- .create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]).version(1))
+ .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).version(1))
.unwrap();
}
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]).version(42))
+ .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).version(42))
.unwrap();
assert_eq!(psbt.global.unsigned_tx.version, 42);
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]))
+ .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
.unwrap();
assert_eq!(psbt.global.unsigned_tx.lock_time, 0);
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]))
+ .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
.unwrap();
assert_eq!(psbt.global.unsigned_tx.lock_time, 100_000);
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]).nlocktime(630_000))
+ .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).nlocktime(630_000))
.unwrap();
assert_eq!(psbt.global.unsigned_tx.lock_time, 630_000);
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]).nlocktime(630_000))
+ .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).nlocktime(630_000))
.unwrap();
assert_eq!(psbt.global.unsigned_tx.lock_time, 630_000);
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
let addr = wallet.get_new_address().unwrap();
wallet
- .create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]).nlocktime(50000))
+ .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).nlocktime(50000))
.unwrap();
}
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]))
+ .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
.unwrap();
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 6);
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]).enable_rbf())
+ .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).enable_rbf())
.unwrap();
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 0xFFFFFFFD);
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
let addr = wallet.get_new_address().unwrap();
wallet
- .create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]).enable_rbf_with_sequence(3))
+ .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).enable_rbf_with_sequence(3))
.unwrap();
}
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]))
+ .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
.unwrap();
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 0xFFFFFFFE);
let addr = wallet.get_new_address().unwrap();
wallet
.create_tx(
- TxBuilder::from_addressees(vec![(addr, 25_000)])
+ TxBuilder::with_recipients(vec![(addr, 25_000)])
.enable_rbf_with_sequence(0xFFFFFFFE),
)
.unwrap();
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
.create_tx(
- TxBuilder::from_addressees(vec![(addr, 25_000)])
+ TxBuilder::with_recipients(vec![(addr, 25_000)])
.enable_rbf_with_sequence(0xDEADBEEF),
)
.unwrap();
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]))
+ .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
.unwrap();
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 0xFFFFFFFF);
let addr = wallet.get_new_address().unwrap();
wallet
.create_tx(
- TxBuilder::from_addressees(vec![(addr.clone(), 25_000)]).do_not_spend_change(),
+ TxBuilder::with_recipients(vec![(addr.clone(), 25_000)]).do_not_spend_change(),
)
.unwrap();
}
let addr = wallet.get_new_address().unwrap();
wallet
.create_tx(
- TxBuilder::from_addressees(vec![(addr.clone(), 25_000), (addr, 10_000)]).send_all(),
+ TxBuilder::with_recipients(vec![(addr.clone(), 25_000), (addr, 10_000)]).send_all(),
)
.unwrap();
}
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, details) = wallet
- .create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 0)]).send_all())
+ .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
.unwrap();
assert_eq!(psbt.global.unsigned_tx.output.len(), 1);
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, details) = wallet
- .create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 0)]).send_all())
+ .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
.unwrap();
assert_fee_rate!(psbt.extract_tx(), details.fees, FeeRate::default(), @add_signature);
let addr = wallet.get_new_address().unwrap();
let (psbt, details) = wallet
.create_tx(
- TxBuilder::from_addressees(vec![(addr.clone(), 0)])
+ TxBuilder::with_recipients(vec![(addr.clone(), 0)])
.fee_rate(FeeRate::from_sat_per_vb(5.0))
.send_all(),
)
let addr = wallet.get_new_address().unwrap();
let (psbt, details) = wallet
.create_tx(
- TxBuilder::from_addressees(vec![(addr.clone(), 25_000)])
+ TxBuilder::with_recipients(vec![(addr.clone(), 25_000)])
.ordering(TxOrdering::Untouched),
)
.unwrap();
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 49_800)]))
+ .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 49_800)]))
.unwrap();
assert_eq!(psbt.global.unsigned_tx.output.len(), 1);
// very high fee rate, so that the only output would be below dust
wallet
.create_tx(
- TxBuilder::from_addressees(vec![(addr.clone(), 0)])
+ TxBuilder::with_recipients(vec![(addr.clone(), 0)])
.send_all()
- .fee_rate(super::utils::FeeRate::from_sat_per_vb(453.0)),
+ .fee_rate(crate::FeeRate::from_sat_per_vb(453.0)),
)
.unwrap();
}
let addr = wallet.get_new_address().unwrap();
let (psbt, details) = wallet
.create_tx(
- TxBuilder::from_addressees(vec![(addr.clone(), 30_000), (addr.clone(), 10_000)])
+ TxBuilder::with_recipients(vec![(addr.clone(), 30_000), (addr.clone(), 10_000)])
.ordering(super::tx_builder::TxOrdering::BIP69Lexicographic),
)
.unwrap();
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 30_000)]))
+ .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 30_000)]))
.unwrap();
assert_eq!(psbt.inputs[0].sighash_type, Some(bitcoin::SigHashType::All));
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
.create_tx(
- TxBuilder::from_addressees(vec![(addr.clone(), 30_000)])
+ TxBuilder::with_recipients(vec![(addr.clone(), 30_000)])
.sighash(bitcoin::SigHashType::Single),
)
.unwrap();
let (wallet, _, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 0)]).send_all())
+ .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
.unwrap();
assert_eq!(psbt.inputs[0].hd_keypaths.len(), 1);
let addr = testutils!(@external descriptors, 5);
let (psbt, _) = wallet
- .create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 0)]).send_all())
+ .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
.unwrap();
assert_eq!(psbt.outputs[0].hd_keypaths.len(), 1);
get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 0)]).send_all())
+ .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
.unwrap();
assert_eq!(
get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 0)]).send_all())
+ .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
.unwrap();
assert_eq!(psbt.inputs[0].redeem_script, None);
get_funded_wallet("sh(wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)))");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 0)]).send_all())
+ .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
.unwrap();
let script = Script::from(
get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 0)]).send_all())
+ .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
.unwrap();
assert!(psbt.inputs[0].non_witness_utxo.is_some());
get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 0)]).send_all())
+ .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
.unwrap();
assert!(psbt.inputs[0].non_witness_utxo.is_none());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
.create_tx(
- TxBuilder::from_addressees(vec![(addr.clone(), 0)])
+ TxBuilder::with_recipients(vec![(addr.clone(), 0)])
.force_non_witness_utxo()
.send_all(),
)
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, mut details) = wallet
- .create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]))
+ .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
.unwrap();
let tx = psbt.extract_tx();
let txid = tx.txid();
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, mut details) = wallet
- .create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]))
+ .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
.unwrap();
let tx = psbt.extract_tx();
let txid = tx.txid();
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, mut details) = wallet
- .create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]).enable_rbf())
+ .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).enable_rbf())
.unwrap();
let tx = psbt.extract_tx();
let txid = tx.txid();
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet
- .create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 25_000)]).enable_rbf())
+ .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 25_000)]).enable_rbf())
.unwrap();
let mut tx = psbt.extract_tx();
let txid = tx.txid();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet
.create_tx(
- TxBuilder::from_addressees(vec![(addr.clone(), 0)])
+ TxBuilder::with_recipients(vec![(addr.clone(), 0)])
.send_all()
.enable_rbf(),
)
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet
.create_tx(
- TxBuilder::from_addressees(vec![(addr.clone(), 0)])
+ TxBuilder::with_recipients(vec![(addr.clone(), 0)])
.utxos(vec![OutPoint {
txid: incoming_txid,
vout: 0,
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet
- .create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 45_000)]).enable_rbf())
+ .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 45_000)]).enable_rbf())
.unwrap();
let mut tx = psbt.extract_tx();
let txid = tx.txid();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet
.create_tx(
- TxBuilder::from_addressees(vec![(addr.clone(), 0)])
+ TxBuilder::with_recipients(vec![(addr.clone(), 0)])
.send_all()
.add_utxo(OutPoint {
txid: incoming_txid,
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet
- .create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 45_000)]).enable_rbf())
+ .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 45_000)]).enable_rbf())
.unwrap();
let mut tx = psbt.extract_tx();
assert_eq!(tx.input.len(), 1);
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet
- .create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 45_000)]).enable_rbf())
+ .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 45_000)]).enable_rbf())
.unwrap();
let mut tx = psbt.extract_tx();
let txid = tx.txid();
let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 0)]).send_all())
+ .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
.unwrap();
let (signed_psbt, finalized) = wallet.sign(psbt, None).unwrap();
get_funded_wallet("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 0)]).send_all())
+ .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
.unwrap();
let (signed_psbt, finalized) = wallet.sign(psbt, None).unwrap();
let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
let addr = wallet.get_new_address().unwrap();
let (mut psbt, _) = wallet
- .create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 0)]).send_all())
+ .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
.unwrap();
psbt.inputs[0].hd_keypaths.clear();
MissingHDKeypath,
}
+impl fmt::Display for SignerError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{:?}", self)
+ }
+}
+
+impl std::error::Error for SignerError {}
+
/// Trait for signers
pub trait Signer: fmt::Debug {
fn sign(
input_index: Option<usize>,
) -> Result<(), SignerError>;
- fn sign_whole_tx(&self) -> bool {
- false
- }
+ fn sign_whole_tx(&self) -> bool;
fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
None
derived_key.private_key.sign(psbt, Some(input_index))
}
+ fn sign_whole_tx(&self) -> bool {
+ false
+ }
+
fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
Some(DescriptorSecretKey::XPrv(self.clone()))
}
Ok(())
}
+ fn sign_whole_tx(&self) -> bool {
+ false
+ }
+
fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
Some(DescriptorSecretKey::PrivKey(self.clone()))
}
}
}
-pub trait ComputeSighash {
+pub(crate) trait ComputeSighash {
fn sighash(
psbt: &psbt::PartiallySignedTransaction,
input_index: usize,
}
#[cfg(not(target_arch = "wasm32"))]
-pub struct Instant(SystemInstant);
+pub(crate) struct Instant(SystemInstant);
#[cfg(target_arch = "wasm32")]
-pub struct Instant(Duration);
+pub(crate) struct Instant(Duration);
impl Instant {
#[cfg(not(target_arch = "wasm32"))]
use bitcoin::{Address, OutPoint, SigHashType, Transaction};
use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm};
-use super::utils::FeeRate;
-use crate::types::UTXO;
+use crate::types::{FeeRate, UTXO};
#[derive(Debug, Default)]
pub struct TxBuilder<Cs: CoinSelectionAlgorithm> {
- pub(crate) addressees: Vec<(Address, u64)>,
+ pub(crate) recipients: Vec<(Address, u64)>,
pub(crate) send_all: bool,
pub(crate) fee_rate: Option<FeeRate>,
pub(crate) policy_path: Option<BTreeMap<String, Vec<usize>>>,
Self::default()
}
- pub fn from_addressees(addressees: Vec<(Address, u64)>) -> Self {
- Self::default().set_addressees(addressees)
+ pub fn with_recipients(recipients: Vec<(Address, u64)>) -> Self {
+ Self::default().set_recipients(recipients)
}
}
impl<Cs: CoinSelectionAlgorithm> TxBuilder<Cs> {
- pub fn set_addressees(mut self, addressees: Vec<(Address, u64)>) -> Self {
- self.addressees = addressees;
+ pub fn set_recipients(mut self, recipients: Vec<(Address, u64)>) -> Self {
+ self.recipients = recipients;
self
}
- pub fn add_addressee(mut self, address: Address, amount: u64) -> Self {
- self.addressees.push((address, amount));
+ pub fn add_recipient(mut self, address: Address, amount: u64) -> Self {
+ self.recipients.push((address, amount));
self
}
pub fn coin_selection<P: CoinSelectionAlgorithm>(self, coin_selection: P) -> TxBuilder<P> {
TxBuilder {
- addressees: self.addressees,
+ recipients: self.recipients,
send_all: self.send_all,
fee_rate: self.fee_rate,
policy_path: self.policy_path,
}
impl TxOrdering {
- pub fn modify_tx(&self, tx: &mut Transaction) {
+ pub fn sort_tx(&self, tx: &mut Transaction) {
match self {
TxOrdering::Untouched => {}
TxOrdering::Shuffle => {
let original_tx = ordering_test_tx!();
let mut tx = original_tx.clone();
- TxOrdering::Untouched.modify_tx(&mut tx);
+ TxOrdering::Untouched.sort_tx(&mut tx);
assert_eq!(original_tx, tx);
}
let original_tx = ordering_test_tx!();
let mut tx = original_tx.clone();
- TxOrdering::Shuffle.modify_tx(&mut tx);
+ TxOrdering::Shuffle.sort_tx(&mut tx);
assert_eq!(original_tx.input, tx.input);
assert_ne!(original_tx.output, tx.output);
let original_tx = ordering_test_tx!();
let mut tx = original_tx.clone();
- TxOrdering::BIP69Lexicographic.modify_tx(&mut tx);
+ TxOrdering::BIP69Lexicographic.sort_tx(&mut tx);
assert_eq!(
tx.input[0].previous_output,
}
}
-#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
-// Internally stored as satoshi/vbyte
-pub struct FeeRate(f32);
-
-impl FeeRate {
- pub fn from_btc_per_kvb(btc_per_kvb: f32) -> Self {
- FeeRate(btc_per_kvb * 1e5)
- }
-
- pub fn from_sat_per_vb(sat_per_vb: f32) -> Self {
- FeeRate(sat_per_vb)
- }
-
- pub fn default_min_relay_fee() -> Self {
- FeeRate(1.0)
- }
-
- pub fn as_sat_vb(&self) -> f32 {
- self.0
- }
-}
-
-impl std::default::Default for FeeRate {
- fn default() -> Self {
- FeeRate::default_min_relay_fee()
- }
-}
-
pub struct After {
pub current_height: Option<u32>,
pub assume_height_reached: bool,
#[cfg(test)]
mod test {
- use super::*;
+ use crate::types::FeeRate;
#[test]
fn test_fee_from_btc_per_kb() {
use #root_ident::descriptor::ExtendedDescriptor;
use #root_ident::database::MemoryDatabase;
use #root_ident::types::ScriptType;
- use #root_ident::{Wallet, TxBuilder};
+ use #root_ident::{Wallet, TxBuilder, FeeRate};
use super::*;
wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000);
- let (psbt, details) = wallet.create_tx(TxBuilder::from_addressees(vec![(node_addr, 25_000)])).unwrap();
+ let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr, 25_000)])).unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction");
let tx = psbt.extract_tx();
wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000);
- let (psbt, details) = wallet.create_tx(TxBuilder::from_addressees(vec![(node_addr, 25_000)])).unwrap();
+ let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr, 25_000)])).unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction");
let sent_txid = wallet.broadcast(psbt.extract_tx()).unwrap();
let mut total_sent = 0;
for _ in 0..5 {
- let (psbt, details) = wallet.create_tx(TxBuilder::from_addressees(vec![(node_addr.clone(), 5_000)])).unwrap();
+ let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.clone(), 5_000)])).unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000);
- let (psbt, details) = wallet.create_tx(TxBuilder::from_addressees(vec![(node_addr.clone(), 5_000)]).enable_rbf()).unwrap();
+ let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.clone(), 5_000)]).enable_rbf()).unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000);
- let (psbt, details) = wallet.create_tx(TxBuilder::from_addressees(vec![(node_addr.clone(), 49_000)]).enable_rbf()).unwrap();
+ let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.clone(), 49_000)]).enable_rbf()).unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 75_000);
- let (psbt, details) = wallet.create_tx(TxBuilder::from_addressees(vec![(node_addr.clone(), 49_000)]).enable_rbf()).unwrap();
+ let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.clone(), 49_000)]).enable_rbf()).unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 75_000);
- let (psbt, details) = wallet.create_tx(TxBuilder::from_addressees(vec![(node_addr.clone(), 49_000)]).enable_rbf()).unwrap();
+ let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.clone(), 49_000)]).enable_rbf()).unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap();