General cleanup for the docs
This commit is contained in:
parent
d61e974dbe
commit
c0867a6adc
@ -83,3 +83,10 @@ required-features = ["cli-utils"]
|
|||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["macros", "testutils", "testutils-macros"]
|
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"]
|
||||||
|
@ -27,8 +27,8 @@ use std::sync::Arc;
|
|||||||
use magical_bitcoin_wallet::bitcoin;
|
use magical_bitcoin_wallet::bitcoin;
|
||||||
use magical_bitcoin_wallet::database::MemoryDatabase;
|
use magical_bitcoin_wallet::database::MemoryDatabase;
|
||||||
use magical_bitcoin_wallet::descriptor::HDKeyPaths;
|
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::wallet::address_validator::{AddressValidator, AddressValidatorError};
|
||||||
|
use magical_bitcoin_wallet::ScriptType;
|
||||||
use magical_bitcoin_wallet::{OfflineWallet, Wallet};
|
use magical_bitcoin_wallet::{OfflineWallet, Wallet};
|
||||||
|
|
||||||
use bitcoin::hashes::hex::FromHex;
|
use bitcoin::hashes::hex::FromHex;
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
// SOFTWARE.
|
// SOFTWARE.
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
use std::fmt;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
@ -116,7 +117,7 @@ impl CompactFilters {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_tx<D: BatchDatabase + DatabaseUtils>(
|
fn process_tx<D: BatchDatabase>(
|
||||||
&self,
|
&self,
|
||||||
database: &mut D,
|
database: &mut D,
|
||||||
tx: &Transaction,
|
tx: &Transaction,
|
||||||
@ -207,7 +208,7 @@ impl OnlineBlockchain for CompactFiltersBlockchain {
|
|||||||
vec![Capability::FullHistory].into_iter().collect()
|
vec![Capability::FullHistory].into_iter().collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup<D: BatchDatabase + DatabaseUtils, P: 'static + Progress>(
|
fn setup<D: BatchDatabase, P: 'static + Progress>(
|
||||||
&self,
|
&self,
|
||||||
_stop_gap: Option<usize>, // TODO: move to electrum and esplora only
|
_stop_gap: Option<usize>, // TODO: move to electrum and esplora only
|
||||||
database: &mut D,
|
database: &mut D,
|
||||||
@ -460,6 +461,14 @@ pub enum CompactFiltersError {
|
|||||||
Global(Box<crate::error::Error>),
|
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 {
|
macro_rules! impl_error {
|
||||||
( $from:ty, $to:ident ) => {
|
( $from:ty, $to:ident ) => {
|
||||||
impl std::convert::From<$from> for CompactFiltersError {
|
impl std::convert::From<$from> for CompactFiltersError {
|
||||||
|
@ -257,7 +257,7 @@ impl Peer {
|
|||||||
*self.connected.read().unwrap()
|
*self.connected.read().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reader_thread(
|
fn reader_thread(
|
||||||
network: Network,
|
network: Network,
|
||||||
connection: TcpStream,
|
connection: TcpStream,
|
||||||
reader_thread_responses: Arc<RwLock<ResponsesMap>>,
|
reader_thread_responses: Arc<RwLock<ResponsesMap>>,
|
||||||
|
@ -33,7 +33,7 @@ use electrum_client::{Client, ElectrumApi};
|
|||||||
|
|
||||||
use self::utils::{ELSGetHistoryRes, ELSListUnspentRes, ElectrumLikeSync};
|
use self::utils::{ELSGetHistoryRes, ELSListUnspentRes, ElectrumLikeSync};
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::database::{BatchDatabase, DatabaseUtils};
|
use crate::database::BatchDatabase;
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::FeeRate;
|
use crate::FeeRate;
|
||||||
|
|
||||||
@ -73,7 +73,7 @@ impl OnlineBlockchain for ElectrumBlockchain {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup<D: BatchDatabase + DatabaseUtils, P: Progress>(
|
fn setup<D: BatchDatabase, P: Progress>(
|
||||||
&self,
|
&self,
|
||||||
stop_gap: Option<usize>,
|
stop_gap: Option<usize>,
|
||||||
database: &mut D,
|
database: &mut D,
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
// SOFTWARE.
|
// SOFTWARE.
|
||||||
|
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
use futures::stream::{self, StreamExt, TryStreamExt};
|
use futures::stream::{self, StreamExt, TryStreamExt};
|
||||||
|
|
||||||
@ -92,7 +93,7 @@ impl OnlineBlockchain for EsploraBlockchain {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup<D: BatchDatabase + DatabaseUtils, P: Progress>(
|
fn setup<D: BatchDatabase, P: Progress>(
|
||||||
&self,
|
&self,
|
||||||
stop_gap: Option<usize>,
|
stop_gap: Option<usize>,
|
||||||
database: &mut D,
|
database: &mut D,
|
||||||
@ -358,6 +359,14 @@ pub enum EsploraError {
|
|||||||
TransactionNotFound(Txid),
|
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 {
|
impl From<reqwest::Error> for EsploraError {
|
||||||
fn from(other: reqwest::Error) -> Self {
|
fn from(other: reqwest::Error) -> Self {
|
||||||
EsploraError::Reqwest(other)
|
EsploraError::Reqwest(other)
|
||||||
|
@ -29,24 +29,29 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use bitcoin::{Transaction, Txid};
|
use bitcoin::{Transaction, Txid};
|
||||||
|
|
||||||
use crate::database::{BatchDatabase, DatabaseUtils};
|
use crate::database::BatchDatabase;
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::FeeRate;
|
use crate::FeeRate;
|
||||||
|
|
||||||
pub mod utils;
|
pub(crate) mod utils;
|
||||||
|
|
||||||
#[cfg(feature = "electrum")]
|
#[cfg(feature = "electrum")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "electrum")))]
|
||||||
pub mod electrum;
|
pub mod electrum;
|
||||||
#[cfg(feature = "electrum")]
|
#[cfg(feature = "electrum")]
|
||||||
pub use self::electrum::ElectrumBlockchain;
|
pub use self::electrum::ElectrumBlockchain;
|
||||||
|
|
||||||
#[cfg(feature = "esplora")]
|
#[cfg(feature = "esplora")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "esplora")))]
|
||||||
pub mod esplora;
|
pub mod esplora;
|
||||||
#[cfg(feature = "esplora")]
|
#[cfg(feature = "esplora")]
|
||||||
pub use self::esplora::EsploraBlockchain;
|
pub use self::esplora::EsploraBlockchain;
|
||||||
|
|
||||||
#[cfg(feature = "compact_filters")]
|
#[cfg(feature = "compact_filters")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "compact_filters")))]
|
||||||
pub mod compact_filters;
|
pub mod compact_filters;
|
||||||
|
#[cfg(feature = "compact_filters")]
|
||||||
|
pub use self::compact_filters::CompactFiltersBlockchain;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub enum Capability {
|
pub enum Capability {
|
||||||
@ -76,13 +81,13 @@ impl Blockchain for OfflineBlockchain {
|
|||||||
pub trait OnlineBlockchain: Blockchain {
|
pub trait OnlineBlockchain: Blockchain {
|
||||||
fn get_capabilities(&self) -> HashSet<Capability>;
|
fn get_capabilities(&self) -> HashSet<Capability>;
|
||||||
|
|
||||||
fn setup<D: BatchDatabase + DatabaseUtils, P: 'static + Progress>(
|
fn setup<D: BatchDatabase, P: 'static + Progress>(
|
||||||
&self,
|
&self,
|
||||||
stop_gap: Option<usize>,
|
stop_gap: Option<usize>,
|
||||||
database: &mut D,
|
database: &mut D,
|
||||||
progress_update: P,
|
progress_update: P,
|
||||||
) -> Result<(), Error>;
|
) -> Result<(), Error>;
|
||||||
fn sync<D: BatchDatabase + DatabaseUtils, P: 'static + Progress>(
|
fn sync<D: BatchDatabase, P: 'static + Progress>(
|
||||||
&self,
|
&self,
|
||||||
stop_gap: Option<usize>,
|
stop_gap: Option<usize>,
|
||||||
database: &mut D,
|
database: &mut D,
|
||||||
@ -163,7 +168,7 @@ impl<T: OnlineBlockchain> OnlineBlockchain for Arc<T> {
|
|||||||
maybe_await!(self.deref().get_capabilities())
|
maybe_await!(self.deref().get_capabilities())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup<D: BatchDatabase + DatabaseUtils, P: 'static + Progress>(
|
fn setup<D: BatchDatabase, P: 'static + Progress>(
|
||||||
&self,
|
&self,
|
||||||
stop_gap: Option<usize>,
|
stop_gap: Option<usize>,
|
||||||
database: &mut D,
|
database: &mut D,
|
||||||
@ -172,7 +177,7 @@ impl<T: OnlineBlockchain> OnlineBlockchain for Arc<T> {
|
|||||||
maybe_await!(self.deref().setup(stop_gap, database, progress_update))
|
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,
|
&self,
|
||||||
stop_gap: Option<usize>,
|
stop_gap: Option<usize>,
|
||||||
database: &mut D,
|
database: &mut D,
|
||||||
|
@ -67,7 +67,7 @@ pub trait ElectrumLikeSync {
|
|||||||
|
|
||||||
// Provided methods down here...
|
// Provided methods down here...
|
||||||
|
|
||||||
fn electrum_like_setup<D: BatchDatabase + DatabaseUtils, P: Progress>(
|
fn electrum_like_setup<D: BatchDatabase, P: Progress>(
|
||||||
&self,
|
&self,
|
||||||
stop_gap: Option<usize>,
|
stop_gap: Option<usize>,
|
||||||
database: &mut D,
|
database: &mut D,
|
||||||
@ -196,7 +196,7 @@ pub trait ElectrumLikeSync {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_tx_and_descendant<D: DatabaseUtils + BatchDatabase>(
|
fn check_tx_and_descendant<D: BatchDatabase>(
|
||||||
&self,
|
&self,
|
||||||
database: &mut D,
|
database: &mut D,
|
||||||
txid: &Txid,
|
txid: &Txid,
|
||||||
@ -320,7 +320,7 @@ pub trait ElectrumLikeSync {
|
|||||||
Ok(to_check_later)
|
Ok(to_check_later)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_history<D: DatabaseUtils + BatchDatabase>(
|
fn check_history<D: BatchDatabase>(
|
||||||
&self,
|
&self,
|
||||||
database: &mut D,
|
database: &mut D,
|
||||||
script_pubkey: Script,
|
script_pubkey: Script,
|
||||||
|
22
src/cli.rs
22
src/cli.rs
@ -40,7 +40,7 @@ use crate::error::Error;
|
|||||||
use crate::types::ScriptType;
|
use crate::types::ScriptType;
|
||||||
use crate::{FeeRate, TxBuilder, Wallet};
|
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();
|
let parts: Vec<_> = s.split(":").collect();
|
||||||
if parts.len() != 2 {
|
if parts.len() != 2 {
|
||||||
return Err("Invalid format".to_string());
|
return Err("Invalid format".to_string());
|
||||||
@ -62,8 +62,8 @@ fn parse_outpoint(s: &str) -> Result<OutPoint, String> {
|
|||||||
OutPoint::from_str(s).map_err(|e| format!("{:?}", e))
|
OutPoint::from_str(s).map_err(|e| format!("{:?}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn addressee_validator(s: String) -> Result<(), String> {
|
fn recipient_validator(s: String) -> Result<(), String> {
|
||||||
parse_addressee(&s).map(|_| ())
|
parse_recipient(&s).map(|_| ())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn outpoint_validator(s: String) -> Result<(), String> {
|
fn outpoint_validator(s: String) -> Result<(), String> {
|
||||||
@ -95,18 +95,18 @@ pub fn make_cli_subcommands<'a, 'b>() -> App<'a, 'b> {
|
|||||||
Arg::with_name("to")
|
Arg::with_name("to")
|
||||||
.long("to")
|
.long("to")
|
||||||
.value_name("ADDRESS:SAT")
|
.value_name("ADDRESS:SAT")
|
||||||
.help("Adds an addressee to the transaction")
|
.help("Adds a recipient to the transaction")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.number_of_values(1)
|
.number_of_values(1)
|
||||||
.required(true)
|
.required(true)
|
||||||
.multiple(true)
|
.multiple(true)
|
||||||
.validator(addressee_validator),
|
.validator(recipient_validator),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("send_all")
|
Arg::with_name("send_all")
|
||||||
.short("all")
|
.short("all")
|
||||||
.long("send_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(
|
||||||
Arg::with_name("enable_rbf")
|
Arg::with_name("enable_rbf")
|
||||||
@ -382,13 +382,13 @@ where
|
|||||||
"satoshi": wallet.get_balance()?
|
"satoshi": wallet.get_balance()?
|
||||||
}))
|
}))
|
||||||
} else if let Some(sub_matches) = matches.subcommand_matches("create_tx") {
|
} else if let Some(sub_matches) = matches.subcommand_matches("create_tx") {
|
||||||
let addressees = sub_matches
|
let recipients = sub_matches
|
||||||
.values_of("to")
|
.values_of("to")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.map(|s| parse_addressee(s))
|
.map(|s| parse_recipient(s))
|
||||||
.collect::<Result<Vec<_>, _>>()
|
.collect::<Result<Vec<_>, _>>()
|
||||||
.map_err(|s| Error::Generic(s))?;
|
.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") {
|
if sub_matches.is_present("send_all") {
|
||||||
tx_builder = tx_builder.send_all();
|
tx_builder = tx_builder.send_all();
|
||||||
@ -503,13 +503,13 @@ where
|
|||||||
}))
|
}))
|
||||||
} else if let Some(sub_matches) = matches.subcommand_matches("finalize_psbt") {
|
} else if let Some(sub_matches) = matches.subcommand_matches("finalize_psbt") {
|
||||||
let psbt = base64::decode(&sub_matches.value_of("psbt").unwrap()).unwrap();
|
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
|
let assume_height = sub_matches
|
||||||
.value_of("assume_height")
|
.value_of("assume_height")
|
||||||
.and_then(|s| Some(s.parse().unwrap()));
|
.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!({
|
Ok(json!({
|
||||||
"psbt": base64::encode(&serialize(&psbt)),
|
"psbt": base64::encode(&serialize(&psbt)),
|
||||||
"is_finalized": finalized,
|
"is_finalized": finalized,
|
||||||
|
@ -28,10 +28,10 @@ use bitcoin::{OutPoint, Script, Transaction, TxOut};
|
|||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::types::*;
|
use crate::types::*;
|
||||||
|
|
||||||
#[cfg(any(feature = "key-value-db", feature = "default"))]
|
#[cfg(feature = "key-value-db")]
|
||||||
pub mod keyvalue;
|
pub(crate) mod keyvalue;
|
||||||
pub mod memory;
|
|
||||||
|
|
||||||
|
pub mod memory;
|
||||||
pub use memory::MemoryDatabase;
|
pub use memory::MemoryDatabase;
|
||||||
|
|
||||||
pub trait BatchOperations {
|
pub trait BatchOperations {
|
||||||
@ -102,7 +102,7 @@ pub trait BatchDatabase: Database {
|
|||||||
fn commit_batch(&mut self, batch: Self::Batch) -> Result<(), Error>;
|
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> {
|
fn is_mine(&self, script: &Script) -> Result<bool, Error> {
|
||||||
self.get_path_from_script_pubkey(script)
|
self.get_path_from_script_pubkey(script)
|
||||||
.map(|o| o.is_some())
|
.map(|o| o.is_some())
|
||||||
|
@ -53,6 +53,8 @@ impl std::fmt::Display for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for Error {}
|
||||||
|
|
||||||
impl_error!(bitcoin::util::bip32::Error, BIP32);
|
impl_error!(bitcoin::util::bip32::Error, BIP32);
|
||||||
impl_error!(bitcoin::util::base58::Error, Base58);
|
impl_error!(bitcoin::util::base58::Error, Base58);
|
||||||
impl_error!(bitcoin::util::key::Error, PK);
|
impl_error!(bitcoin::util::key::Error, PK);
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
use std::cmp::max;
|
use std::cmp::max;
|
||||||
use std::collections::{BTreeMap, HashSet, VecDeque};
|
use std::collections::{BTreeMap, HashSet, VecDeque};
|
||||||
|
use std::fmt;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use serde::ser::SerializeMap;
|
use serde::ser::SerializeMap;
|
||||||
@ -423,8 +424,16 @@ pub enum PolicyError {
|
|||||||
IncompatibleConditions,
|
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 {
|
impl Policy {
|
||||||
pub fn new(item: SatisfiableItem) -> Self {
|
fn new(item: SatisfiableItem) -> Self {
|
||||||
Policy {
|
Policy {
|
||||||
id: item.id(),
|
id: item.id(),
|
||||||
item,
|
item,
|
||||||
@ -433,7 +442,7 @@ impl Policy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
match (a, b) {
|
||||||
(None, None) => Ok(None),
|
(None, None) => Ok(None),
|
||||||
(Some(x), None) | (None, Some(x)) => Ok(Some(x)),
|
(Some(x), None) | (None, Some(x)) => Ok(Some(x)),
|
||||||
@ -441,7 +450,7 @@ impl Policy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
match (a, b) {
|
||||||
(None, None) => Ok(None),
|
(None, None) => Ok(None),
|
||||||
(Some(x), None) | (None, Some(x)) => Ok(Some(x)),
|
(Some(x), None) | (None, Some(x)) => Ok(Some(x)),
|
||||||
@ -449,10 +458,7 @@ impl Policy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn make_thresh(
|
fn make_thresh(items: Vec<Policy>, threshold: usize) -> Result<Option<Policy>, PolicyError> {
|
||||||
items: Vec<Policy>,
|
|
||||||
threshold: usize,
|
|
||||||
) -> Result<Option<Policy>, PolicyError> {
|
|
||||||
if threshold == 0 {
|
if threshold == 0 {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,9 @@ pub enum Error {
|
|||||||
TransactionNotFound,
|
TransactionNotFound,
|
||||||
TransactionConfirmed,
|
TransactionConfirmed,
|
||||||
IrreplaceableTransaction,
|
IrreplaceableTransaction,
|
||||||
FeeRateTooLow(crate::wallet::utils::FeeRate),
|
FeeRateTooLow {
|
||||||
|
required: crate::types::FeeRate,
|
||||||
|
},
|
||||||
|
|
||||||
ChecksumMismatch,
|
ChecksumMismatch,
|
||||||
DifferentDescriptorStructure,
|
DifferentDescriptorStructure,
|
||||||
|
22
src/lib.rs
22
src/lib.rs
@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
// only enables the `doc_cfg` feature when
|
// only enables the `doc_cfg` feature when
|
||||||
// the `docsrs` configuration attribute is defined
|
// the `docsrs` configuration attribute is defined
|
||||||
#[cfg_attr(docsrs, feature(doc_cfg))]
|
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||||
|
|
||||||
pub extern crate bitcoin;
|
pub extern crate bitcoin;
|
||||||
extern crate log;
|
extern crate log;
|
||||||
@ -45,13 +45,9 @@ extern crate lazy_static;
|
|||||||
|
|
||||||
#[cfg(feature = "electrum")]
|
#[cfg(feature = "electrum")]
|
||||||
pub extern crate electrum_client;
|
pub extern crate electrum_client;
|
||||||
#[cfg(feature = "electrum")]
|
|
||||||
pub use electrum_client::client::Client;
|
|
||||||
|
|
||||||
#[cfg(feature = "esplora")]
|
#[cfg(feature = "esplora")]
|
||||||
pub extern crate reqwest;
|
pub extern crate reqwest;
|
||||||
#[cfg(feature = "esplora")]
|
|
||||||
pub use blockchain::esplora::EsploraBlockchain;
|
|
||||||
|
|
||||||
#[cfg(feature = "key-value-db")]
|
#[cfg(feature = "key-value-db")]
|
||||||
pub extern crate sled;
|
pub extern crate sled;
|
||||||
@ -74,11 +70,19 @@ pub mod error;
|
|||||||
pub mod blockchain;
|
pub mod blockchain;
|
||||||
pub mod database;
|
pub mod database;
|
||||||
pub mod descriptor;
|
pub mod descriptor;
|
||||||
pub mod psbt;
|
pub(crate) mod psbt;
|
||||||
pub mod types;
|
pub(crate) mod types;
|
||||||
pub mod wallet;
|
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::tx_builder::TxBuilder;
|
||||||
pub use wallet::utils::FeeRate;
|
|
||||||
pub use wallet::{OfflineWallet, Wallet};
|
pub use wallet::{OfflineWallet, Wallet};
|
||||||
|
|
||||||
|
#[cfg(feature = "esplora")]
|
||||||
|
pub use blockchain::esplora::EsploraBlockchain;
|
||||||
|
|
||||||
|
#[cfg(feature = "electrum")]
|
||||||
|
pub use blockchain::electrum::ElectrumBlockchain;
|
||||||
|
28
src/types.rs
28
src/types.rs
@ -58,6 +58,34 @@ impl AsRef<[u8]> for ScriptType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct UTXO {
|
pub struct UTXO {
|
||||||
pub outpoint: OutPoint,
|
pub outpoint: OutPoint,
|
||||||
|
@ -22,6 +22,8 @@
|
|||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
// SOFTWARE.
|
// SOFTWARE.
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
use bitcoin::Script;
|
use bitcoin::Script;
|
||||||
|
|
||||||
use crate::descriptor::HDKeyPaths;
|
use crate::descriptor::HDKeyPaths;
|
||||||
@ -35,6 +37,14 @@ pub enum AddressValidatorError {
|
|||||||
InvalidScript,
|
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 {
|
pub trait AddressValidator {
|
||||||
fn validate(
|
fn validate(
|
||||||
&self,
|
&self,
|
||||||
@ -81,7 +91,7 @@ mod test {
|
|||||||
|
|
||||||
let addr = testutils!(@external descriptors, 10);
|
let addr = testutils!(@external descriptors, 10);
|
||||||
wallet
|
wallet
|
||||||
.create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]))
|
.create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,14 +26,14 @@ use bitcoin::consensus::encode::serialize;
|
|||||||
use bitcoin::{Script, TxIn};
|
use bitcoin::{Script, TxIn};
|
||||||
|
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::types::UTXO;
|
use crate::types::{FeeRate, UTXO};
|
||||||
|
|
||||||
pub type DefaultCoinSelectionAlgorithm = DumbCoinSelection;
|
pub type DefaultCoinSelectionAlgorithm = DumbCoinSelection;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct CoinSelectionResult {
|
pub struct CoinSelectionResult {
|
||||||
pub txin: Vec<(TxIn, Script)>,
|
pub txin: Vec<(TxIn, Script)>,
|
||||||
pub total_amount: u64,
|
pub selected_amount: u64,
|
||||||
pub fee_amount: f32,
|
pub fee_amount: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,8 +42,8 @@ pub trait CoinSelectionAlgorithm: std::fmt::Debug {
|
|||||||
&self,
|
&self,
|
||||||
utxos: Vec<UTXO>,
|
utxos: Vec<UTXO>,
|
||||||
use_all_utxos: bool,
|
use_all_utxos: bool,
|
||||||
fee_rate: f32,
|
fee_rate: FeeRate,
|
||||||
outgoing_amount: u64,
|
amount_needed: u64,
|
||||||
input_witness_weight: usize,
|
input_witness_weight: usize,
|
||||||
fee_amount: f32,
|
fee_amount: f32,
|
||||||
) -> Result<CoinSelectionResult, Error>;
|
) -> Result<CoinSelectionResult, Error>;
|
||||||
@ -57,16 +57,16 @@ impl CoinSelectionAlgorithm for DumbCoinSelection {
|
|||||||
&self,
|
&self,
|
||||||
mut utxos: Vec<UTXO>,
|
mut utxos: Vec<UTXO>,
|
||||||
use_all_utxos: bool,
|
use_all_utxos: bool,
|
||||||
fee_rate: f32,
|
fee_rate: FeeRate,
|
||||||
outgoing_amount: u64,
|
outgoing_amount: u64,
|
||||||
input_witness_weight: usize,
|
input_witness_weight: usize,
|
||||||
mut fee_amount: f32,
|
mut fee_amount: f32,
|
||||||
) -> Result<CoinSelectionResult, Error> {
|
) -> Result<CoinSelectionResult, Error> {
|
||||||
let mut txin = Vec::new();
|
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!(
|
log::debug!(
|
||||||
"outgoing_amount = `{}`, fee_amount = `{}`, fee_rate = `{}`",
|
"outgoing_amount = `{}`, fee_amount = `{}`, fee_rate = `{:?}`",
|
||||||
outgoing_amount,
|
outgoing_amount,
|
||||||
fee_amount,
|
fee_amount,
|
||||||
fee_rate
|
fee_rate
|
||||||
@ -75,11 +75,11 @@ impl CoinSelectionAlgorithm for DumbCoinSelection {
|
|||||||
// sort so that we pick them starting from the larger.
|
// sort so that we pick them starting from the larger.
|
||||||
utxos.sort_by(|a, b| a.txout.value.partial_cmp(&b.txout.value).unwrap());
|
utxos.sort_by(|a, b| a.txout.value.partial_cmp(&b.txout.value).unwrap());
|
||||||
|
|
||||||
let mut total_amount: u64 = 0;
|
let mut selected_amount: u64 = 0;
|
||||||
while use_all_utxos || total_amount < outgoing_amount + (fee_amount.ceil() as u64) {
|
while use_all_utxos || selected_amount < outgoing_amount + (fee_amount.ceil() as u64) {
|
||||||
let utxo = match utxos.pop() {
|
let utxo = match utxos.pop() {
|
||||||
Some(utxo) => utxo,
|
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)
|
return Err(Error::InsufficientFunds)
|
||||||
}
|
}
|
||||||
None if use_all_utxos => break,
|
None if use_all_utxos => break,
|
||||||
@ -100,13 +100,13 @@ impl CoinSelectionAlgorithm for DumbCoinSelection {
|
|||||||
);
|
);
|
||||||
|
|
||||||
txin.push((new_in, utxo.txout.script_pubkey));
|
txin.push((new_in, utxo.txout.script_pubkey));
|
||||||
total_amount += utxo.txout.value;
|
selected_amount += utxo.txout.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(CoinSelectionResult {
|
Ok(CoinSelectionResult {
|
||||||
txin,
|
txin,
|
||||||
fee_amount,
|
fee_amount,
|
||||||
total_amount,
|
selected_amount,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -154,11 +154,18 @@ mod test {
|
|||||||
let utxos = get_test_utxos();
|
let utxos = get_test_utxos();
|
||||||
|
|
||||||
let result = DumbCoinSelection
|
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();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(result.txin.len(), 2);
|
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);
|
assert_eq!(result.fee_amount, 186.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,11 +174,18 @@ mod test {
|
|||||||
let utxos = get_test_utxos();
|
let utxos = get_test_utxos();
|
||||||
|
|
||||||
let result = DumbCoinSelection
|
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();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(result.txin.len(), 2);
|
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);
|
assert_eq!(result.fee_amount, 186.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,11 +194,18 @@ mod test {
|
|||||||
let utxos = get_test_utxos();
|
let utxos = get_test_utxos();
|
||||||
|
|
||||||
let result = DumbCoinSelection
|
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();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(result.txin.len(), 1);
|
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);
|
assert_eq!(result.fee_amount, 118.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,7 +215,14 @@ mod test {
|
|||||||
let utxos = get_test_utxos();
|
let utxos = get_test_utxos();
|
||||||
|
|
||||||
DumbCoinSelection
|
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();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,7 +232,14 @@ mod test {
|
|||||||
let utxos = get_test_utxos();
|
let utxos = get_test_utxos();
|
||||||
|
|
||||||
DumbCoinSelection
|
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();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,12 +45,14 @@ mod rbf;
|
|||||||
pub mod signer;
|
pub mod signer;
|
||||||
pub mod time;
|
pub mod time;
|
||||||
pub mod tx_builder;
|
pub mod tx_builder;
|
||||||
pub mod utils;
|
pub(crate) mod utils;
|
||||||
|
|
||||||
|
pub use utils::IsDust;
|
||||||
|
|
||||||
use address_validator::AddressValidator;
|
use address_validator::AddressValidator;
|
||||||
use signer::{Signer, SignerId, SignerOrdering, SignersContainer};
|
use signer::{Signer, SignerId, SignerOrdering, SignersContainer};
|
||||||
use tx_builder::TxBuilder;
|
use tx_builder::TxBuilder;
|
||||||
use utils::{After, FeeRate, IsDust, Older};
|
use utils::{After, Older};
|
||||||
|
|
||||||
use crate::blockchain::{Blockchain, OfflineBlockchain, OnlineBlockchain, Progress};
|
use crate::blockchain::{Blockchain, OfflineBlockchain, OnlineBlockchain, Progress};
|
||||||
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
|
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
|
||||||
@ -185,7 +187,7 @@ where
|
|||||||
&self,
|
&self,
|
||||||
builder: TxBuilder<Cs>,
|
builder: TxBuilder<Cs>,
|
||||||
) -> Result<(PSBT, TransactionDetails), Error> {
|
) -> Result<(PSBT, TransactionDetails), Error> {
|
||||||
if builder.addressees.is_empty() {
|
if builder.recipients.is_empty() {
|
||||||
return Err(Error::NoAddressees);
|
return Err(Error::NoAddressees);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,8 +241,8 @@ where
|
|||||||
output: vec![],
|
output: vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
let fee_rate = builder.fee_rate.unwrap_or_default().as_sat_vb();
|
let fee_rate = builder.fee_rate.unwrap_or_default();
|
||||||
if builder.send_all && builder.addressees.len() != 1 {
|
if builder.send_all && builder.recipients.len() != 1 {
|
||||||
return Err(Error::SendAllMultipleOutputs);
|
return Err(Error::SendAllMultipleOutputs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,10 +251,10 @@ where
|
|||||||
let mut outgoing: u64 = 0;
|
let mut outgoing: u64 = 0;
|
||||||
let mut received: 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());
|
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 {
|
let value = match builder.send_all {
|
||||||
true => 0,
|
true => 0,
|
||||||
false if satoshi.is_dust() => return Err(Error::OutputBelowDustLimit(index)),
|
false if satoshi.is_dust() => return Err(Error::OutputBelowDustLimit(index)),
|
||||||
@ -304,7 +306,7 @@ where
|
|||||||
)?;
|
)?;
|
||||||
let coin_selection::CoinSelectionResult {
|
let coin_selection::CoinSelectionResult {
|
||||||
txin,
|
txin,
|
||||||
total_amount,
|
selected_amount,
|
||||||
mut fee_amount,
|
mut fee_amount,
|
||||||
} = builder.coin_selection.coin_select(
|
} = builder.coin_selection.coin_select(
|
||||||
available_utxos,
|
available_utxos,
|
||||||
@ -342,7 +344,7 @@ where
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut fee_amount = fee_amount.ceil() as u64;
|
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() {
|
if !builder.send_all && !change_val.is_dust() {
|
||||||
let mut change_output = change_output.unwrap();
|
let mut change_output = change_output.unwrap();
|
||||||
change_output.value = change_val;
|
change_output.value = change_val;
|
||||||
@ -366,7 +368,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
// sort input/outputs according to the chosen algorithm
|
// 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 txid = tx.txid();
|
||||||
let psbt = self.complete_transaction(tx, prev_script_pubkeys, builder)?;
|
let psbt = self.complete_transaction(tx, prev_script_pubkeys, builder)?;
|
||||||
@ -376,7 +378,7 @@ where
|
|||||||
txid,
|
txid,
|
||||||
timestamp: time::get_timestamp(),
|
timestamp: time::get_timestamp(),
|
||||||
received,
|
received,
|
||||||
sent: total_amount,
|
sent: selected_amount,
|
||||||
fees: fee_amount,
|
fees: fee_amount,
|
||||||
height: None,
|
height: None,
|
||||||
};
|
};
|
||||||
@ -409,7 +411,9 @@ where
|
|||||||
let new_feerate = builder.fee_rate.unwrap_or_default();
|
let new_feerate = builder.fee_rate.unwrap_or_default();
|
||||||
|
|
||||||
if new_feerate < required_feerate {
|
if new_feerate < required_feerate {
|
||||||
return Err(Error::FeeRateTooLow(required_feerate));
|
return Err(Error::FeeRateTooLow {
|
||||||
|
required: required_feerate,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
let mut fee_difference =
|
let mut fee_difference =
|
||||||
(new_feerate.as_sat_vb() * tx.get_weight() as f32 / 4.0).ceil() as u64 - details.fees;
|
(new_feerate.as_sat_vb() * tx.get_weight() as f32 / 4.0).ceil() as u64 - details.fees;
|
||||||
@ -515,12 +519,12 @@ where
|
|||||||
)?;
|
)?;
|
||||||
let coin_selection::CoinSelectionResult {
|
let coin_selection::CoinSelectionResult {
|
||||||
txin,
|
txin,
|
||||||
total_amount,
|
selected_amount,
|
||||||
fee_amount,
|
fee_amount,
|
||||||
} = builder.coin_selection.coin_select(
|
} = builder.coin_selection.coin_select(
|
||||||
available_utxos,
|
available_utxos,
|
||||||
use_all_utxos,
|
use_all_utxos,
|
||||||
new_feerate.as_sat_vb(),
|
new_feerate,
|
||||||
fee_difference
|
fee_difference
|
||||||
.checked_sub(removed_change_output.value)
|
.checked_sub(removed_change_output.value)
|
||||||
.unwrap_or(0),
|
.unwrap_or(0),
|
||||||
@ -538,8 +542,8 @@ where
|
|||||||
.for_each(|i| i.sequence = tx.input[0].sequence);
|
.for_each(|i| i.sequence = tx.input[0].sequence);
|
||||||
tx.input.extend_from_slice(&mut txin);
|
tx.input.extend_from_slice(&mut txin);
|
||||||
|
|
||||||
details.sent += total_amount;
|
details.sent += selected_amount;
|
||||||
total_amount
|
selected_amount
|
||||||
} else {
|
} else {
|
||||||
// otherwise just remove the output and add 0 new coins
|
// otherwise just remove the output and add 0 new coins
|
||||||
0
|
0
|
||||||
@ -570,7 +574,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
// sort input/outputs according to the chosen algorithm
|
// 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
|
// TODO: check that we are not replacing more than 100 txs from mempool
|
||||||
|
|
||||||
@ -620,9 +624,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
// attempt to finalize
|
// attempt to finalize
|
||||||
let finalized = self.finalize_psbt(&mut psbt, assume_height)?;
|
self.finalize_psbt(psbt, assume_height)
|
||||||
|
|
||||||
Ok((psbt, finalized))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn policies(&self, script_type: ScriptType) -> Result<Option<Policy>, Error> {
|
pub fn policies(&self, script_type: ScriptType) -> Result<Option<Policy>, Error> {
|
||||||
@ -650,9 +652,9 @@ where
|
|||||||
|
|
||||||
pub fn finalize_psbt(
|
pub fn finalize_psbt(
|
||||||
&self,
|
&self,
|
||||||
psbt: &mut PSBT,
|
mut psbt: PSBT,
|
||||||
assume_height: Option<u32>,
|
assume_height: Option<u32>,
|
||||||
) -> Result<bool, Error> {
|
) -> Result<(PSBT, bool), Error> {
|
||||||
let mut tx = psbt.global.unsigned_tx.clone();
|
let mut tx = psbt.global.unsigned_tx.clone();
|
||||||
|
|
||||||
for (n, (input, psbt_input)) in tx.input.iter_mut().zip(psbt.inputs.iter()).enumerate() {
|
for (n, (input, psbt_input)) in tx.input.iter_mut().zip(psbt.inputs.iter()).enumerate() {
|
||||||
@ -696,7 +698,7 @@ where
|
|||||||
desc
|
desc
|
||||||
} else {
|
} else {
|
||||||
debug!("Couldn't find the right derived descriptor for input {}", n);
|
debug!("Couldn't find the right derived descriptor for input {}", n);
|
||||||
return Ok(false);
|
return Ok((psbt, false));
|
||||||
};
|
};
|
||||||
|
|
||||||
match desc.satisfy(
|
match desc.satisfy(
|
||||||
@ -710,7 +712,7 @@ where
|
|||||||
Ok(_) => continue,
|
Ok(_) => continue,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug!("satisfy error {:?} for input {}", e, n);
|
debug!("satisfy error {:?} for input {}", e, n);
|
||||||
return Ok(false);
|
return Ok((psbt, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -721,7 +723,7 @@ where
|
|||||||
psbt_input.final_script_witness = Some(input.witness);
|
psbt_input.final_script_witness = Some(input.witness);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(true)
|
Ok((psbt, true))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internals
|
// Internals
|
||||||
@ -1236,10 +1238,10 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(expected = "NoAddressees")]
|
#[should_panic(expected = "NoAddressees")]
|
||||||
fn test_create_tx_empty_addressees() {
|
fn test_create_tx_empty_recipients() {
|
||||||
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
||||||
wallet
|
wallet
|
||||||
.create_tx(TxBuilder::from_addressees(vec![]).version(0))
|
.create_tx(TxBuilder::with_recipients(vec![]).version(0))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1249,7 +1251,7 @@ mod test {
|
|||||||
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
wallet
|
wallet
|
||||||
.create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]).version(0))
|
.create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).version(0))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1261,7 +1263,7 @@ mod test {
|
|||||||
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
|
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
|
||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
wallet
|
wallet
|
||||||
.create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]).version(1))
|
.create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).version(1))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1270,7 +1272,7 @@ mod test {
|
|||||||
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
let (psbt, _) = wallet
|
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();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(psbt.global.unsigned_tx.version, 42);
|
assert_eq!(psbt.global.unsigned_tx.version, 42);
|
||||||
@ -1281,7 +1283,7 @@ mod test {
|
|||||||
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
let (psbt, _) = wallet
|
let (psbt, _) = wallet
|
||||||
.create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]))
|
.create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(psbt.global.unsigned_tx.lock_time, 0);
|
assert_eq!(psbt.global.unsigned_tx.lock_time, 0);
|
||||||
@ -1292,7 +1294,7 @@ mod test {
|
|||||||
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
|
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
|
||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
let (psbt, _) = wallet
|
let (psbt, _) = wallet
|
||||||
.create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]))
|
.create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(psbt.global.unsigned_tx.lock_time, 100_000);
|
assert_eq!(psbt.global.unsigned_tx.lock_time, 100_000);
|
||||||
@ -1303,7 +1305,7 @@ mod test {
|
|||||||
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
let (psbt, _) = wallet
|
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();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(psbt.global.unsigned_tx.lock_time, 630_000);
|
assert_eq!(psbt.global.unsigned_tx.lock_time, 630_000);
|
||||||
@ -1314,7 +1316,7 @@ mod test {
|
|||||||
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
|
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
|
||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
let (psbt, _) = wallet
|
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();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(psbt.global.unsigned_tx.lock_time, 630_000);
|
assert_eq!(psbt.global.unsigned_tx.lock_time, 630_000);
|
||||||
@ -1328,7 +1330,7 @@ mod test {
|
|||||||
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
|
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
|
||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
wallet
|
wallet
|
||||||
.create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]).nlocktime(50000))
|
.create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).nlocktime(50000))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1337,7 +1339,7 @@ mod test {
|
|||||||
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
|
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
|
||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
let (psbt, _) = wallet
|
let (psbt, _) = wallet
|
||||||
.create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]))
|
.create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 6);
|
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 6);
|
||||||
@ -1348,7 +1350,7 @@ mod test {
|
|||||||
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
|
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
|
||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
let (psbt, _) = wallet
|
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();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 0xFFFFFFFD);
|
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 0xFFFFFFFD);
|
||||||
@ -1362,7 +1364,7 @@ mod test {
|
|||||||
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
|
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
|
||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
wallet
|
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();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1371,7 +1373,7 @@ mod test {
|
|||||||
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
|
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
|
||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
let (psbt, _) = wallet
|
let (psbt, _) = wallet
|
||||||
.create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]))
|
.create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 0xFFFFFFFE);
|
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 0xFFFFFFFE);
|
||||||
@ -1384,7 +1386,7 @@ mod test {
|
|||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
wallet
|
wallet
|
||||||
.create_tx(
|
.create_tx(
|
||||||
TxBuilder::from_addressees(vec![(addr, 25_000)])
|
TxBuilder::with_recipients(vec![(addr, 25_000)])
|
||||||
.enable_rbf_with_sequence(0xFFFFFFFE),
|
.enable_rbf_with_sequence(0xFFFFFFFE),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -1396,7 +1398,7 @@ mod test {
|
|||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
let (psbt, _) = wallet
|
let (psbt, _) = wallet
|
||||||
.create_tx(
|
.create_tx(
|
||||||
TxBuilder::from_addressees(vec![(addr, 25_000)])
|
TxBuilder::with_recipients(vec![(addr, 25_000)])
|
||||||
.enable_rbf_with_sequence(0xDEADBEEF),
|
.enable_rbf_with_sequence(0xDEADBEEF),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -1409,7 +1411,7 @@ mod test {
|
|||||||
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
let (psbt, _) = wallet
|
let (psbt, _) = wallet
|
||||||
.create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]))
|
.create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 0xFFFFFFFF);
|
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 0xFFFFFFFF);
|
||||||
@ -1424,7 +1426,7 @@ mod test {
|
|||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
wallet
|
wallet
|
||||||
.create_tx(
|
.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();
|
.unwrap();
|
||||||
}
|
}
|
||||||
@ -1436,7 +1438,7 @@ mod test {
|
|||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
wallet
|
wallet
|
||||||
.create_tx(
|
.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();
|
.unwrap();
|
||||||
}
|
}
|
||||||
@ -1446,7 +1448,7 @@ mod test {
|
|||||||
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
let (psbt, details) = wallet
|
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();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(psbt.global.unsigned_tx.output.len(), 1);
|
assert_eq!(psbt.global.unsigned_tx.output.len(), 1);
|
||||||
@ -1461,7 +1463,7 @@ mod test {
|
|||||||
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
let (psbt, details) = wallet
|
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();
|
.unwrap();
|
||||||
|
|
||||||
assert_fee_rate!(psbt.extract_tx(), details.fees, FeeRate::default(), @add_signature);
|
assert_fee_rate!(psbt.extract_tx(), details.fees, FeeRate::default(), @add_signature);
|
||||||
@ -1473,7 +1475,7 @@ mod test {
|
|||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
let (psbt, details) = wallet
|
let (psbt, details) = wallet
|
||||||
.create_tx(
|
.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))
|
.fee_rate(FeeRate::from_sat_per_vb(5.0))
|
||||||
.send_all(),
|
.send_all(),
|
||||||
)
|
)
|
||||||
@ -1490,7 +1492,7 @@ mod test {
|
|||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
let (psbt, details) = wallet
|
let (psbt, details) = wallet
|
||||||
.create_tx(
|
.create_tx(
|
||||||
TxBuilder::from_addressees(vec![(addr.clone(), 25_000)])
|
TxBuilder::with_recipients(vec![(addr.clone(), 25_000)])
|
||||||
.ordering(TxOrdering::Untouched),
|
.ordering(TxOrdering::Untouched),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -1508,7 +1510,7 @@ mod test {
|
|||||||
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
let (psbt, _) = wallet
|
let (psbt, _) = wallet
|
||||||
.create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 49_800)]))
|
.create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 49_800)]))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(psbt.global.unsigned_tx.output.len(), 1);
|
assert_eq!(psbt.global.unsigned_tx.output.len(), 1);
|
||||||
@ -1523,9 +1525,9 @@ mod test {
|
|||||||
// very high fee rate, so that the only output would be below dust
|
// very high fee rate, so that the only output would be below dust
|
||||||
wallet
|
wallet
|
||||||
.create_tx(
|
.create_tx(
|
||||||
TxBuilder::from_addressees(vec![(addr.clone(), 0)])
|
TxBuilder::with_recipients(vec![(addr.clone(), 0)])
|
||||||
.send_all()
|
.send_all()
|
||||||
.fee_rate(super::utils::FeeRate::from_sat_per_vb(453.0)),
|
.fee_rate(crate::FeeRate::from_sat_per_vb(453.0)),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
@ -1536,7 +1538,7 @@ mod test {
|
|||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
let (psbt, details) = wallet
|
let (psbt, details) = wallet
|
||||||
.create_tx(
|
.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),
|
.ordering(super::tx_builder::TxOrdering::BIP69Lexicographic),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -1555,7 +1557,7 @@ mod test {
|
|||||||
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
let (psbt, _) = wallet
|
let (psbt, _) = wallet
|
||||||
.create_tx(TxBuilder::from_addressees(vec![(addr.clone(), 30_000)]))
|
.create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 30_000)]))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(psbt.inputs[0].sighash_type, Some(bitcoin::SigHashType::All));
|
assert_eq!(psbt.inputs[0].sighash_type, Some(bitcoin::SigHashType::All));
|
||||||
@ -1567,7 +1569,7 @@ mod test {
|
|||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
let (psbt, _) = wallet
|
let (psbt, _) = wallet
|
||||||
.create_tx(
|
.create_tx(
|
||||||
TxBuilder::from_addressees(vec![(addr.clone(), 30_000)])
|
TxBuilder::with_recipients(vec![(addr.clone(), 30_000)])
|
||||||
.sighash(bitcoin::SigHashType::Single),
|
.sighash(bitcoin::SigHashType::Single),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -1586,7 +1588,7 @@ mod test {
|
|||||||
let (wallet, _, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)");
|
let (wallet, _, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)");
|
||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
let (psbt, _) = wallet
|
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();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(psbt.inputs[0].hd_keypaths.len(), 1);
|
assert_eq!(psbt.inputs[0].hd_keypaths.len(), 1);
|
||||||
@ -1610,7 +1612,7 @@ mod test {
|
|||||||
|
|
||||||
let addr = testutils!(@external descriptors, 5);
|
let addr = testutils!(@external descriptors, 5);
|
||||||
let (psbt, _) = wallet
|
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();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(psbt.outputs[0].hd_keypaths.len(), 1);
|
assert_eq!(psbt.outputs[0].hd_keypaths.len(), 1);
|
||||||
@ -1631,7 +1633,7 @@ mod test {
|
|||||||
get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
|
get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
|
||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
let (psbt, _) = wallet
|
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();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -1654,7 +1656,7 @@ mod test {
|
|||||||
get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
|
get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
|
||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
let (psbt, _) = wallet
|
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();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(psbt.inputs[0].redeem_script, None);
|
assert_eq!(psbt.inputs[0].redeem_script, None);
|
||||||
@ -1677,7 +1679,7 @@ mod test {
|
|||||||
get_funded_wallet("sh(wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)))");
|
get_funded_wallet("sh(wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)))");
|
||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
let (psbt, _) = wallet
|
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();
|
.unwrap();
|
||||||
|
|
||||||
let script = Script::from(
|
let script = Script::from(
|
||||||
@ -1697,7 +1699,7 @@ mod test {
|
|||||||
get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
|
get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
|
||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
let (psbt, _) = wallet
|
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();
|
.unwrap();
|
||||||
|
|
||||||
assert!(psbt.inputs[0].non_witness_utxo.is_some());
|
assert!(psbt.inputs[0].non_witness_utxo.is_some());
|
||||||
@ -1710,7 +1712,7 @@ mod test {
|
|||||||
get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
|
get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
|
||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
let (psbt, _) = wallet
|
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();
|
.unwrap();
|
||||||
|
|
||||||
assert!(psbt.inputs[0].non_witness_utxo.is_none());
|
assert!(psbt.inputs[0].non_witness_utxo.is_none());
|
||||||
@ -1724,7 +1726,7 @@ mod test {
|
|||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
let (psbt, _) = wallet
|
let (psbt, _) = wallet
|
||||||
.create_tx(
|
.create_tx(
|
||||||
TxBuilder::from_addressees(vec![(addr.clone(), 0)])
|
TxBuilder::with_recipients(vec![(addr.clone(), 0)])
|
||||||
.force_non_witness_utxo()
|
.force_non_witness_utxo()
|
||||||
.send_all(),
|
.send_all(),
|
||||||
)
|
)
|
||||||
@ -1740,7 +1742,7 @@ mod test {
|
|||||||
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
let (psbt, mut details) = wallet
|
let (psbt, mut details) = wallet
|
||||||
.create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]))
|
.create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let tx = psbt.extract_tx();
|
let tx = psbt.extract_tx();
|
||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
@ -1757,7 +1759,7 @@ mod test {
|
|||||||
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
let (psbt, mut details) = wallet
|
let (psbt, mut details) = wallet
|
||||||
.create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]))
|
.create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let tx = psbt.extract_tx();
|
let tx = psbt.extract_tx();
|
||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
@ -1775,7 +1777,7 @@ mod test {
|
|||||||
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
let (psbt, mut details) = wallet
|
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();
|
.unwrap();
|
||||||
let tx = psbt.extract_tx();
|
let tx = psbt.extract_tx();
|
||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
@ -1796,7 +1798,7 @@ mod test {
|
|||||||
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
||||||
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
|
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
|
||||||
let (psbt, mut original_details) = wallet
|
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();
|
.unwrap();
|
||||||
let mut tx = psbt.extract_tx();
|
let mut tx = psbt.extract_tx();
|
||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
@ -1858,7 +1860,7 @@ mod test {
|
|||||||
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
|
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
|
||||||
let (psbt, mut original_details) = wallet
|
let (psbt, mut original_details) = wallet
|
||||||
.create_tx(
|
.create_tx(
|
||||||
TxBuilder::from_addressees(vec![(addr.clone(), 0)])
|
TxBuilder::with_recipients(vec![(addr.clone(), 0)])
|
||||||
.send_all()
|
.send_all()
|
||||||
.enable_rbf(),
|
.enable_rbf(),
|
||||||
)
|
)
|
||||||
@ -1912,7 +1914,7 @@ mod test {
|
|||||||
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
|
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
|
||||||
let (psbt, mut original_details) = wallet
|
let (psbt, mut original_details) = wallet
|
||||||
.create_tx(
|
.create_tx(
|
||||||
TxBuilder::from_addressees(vec![(addr.clone(), 0)])
|
TxBuilder::with_recipients(vec![(addr.clone(), 0)])
|
||||||
.utxos(vec![OutPoint {
|
.utxos(vec![OutPoint {
|
||||||
txid: incoming_txid,
|
txid: incoming_txid,
|
||||||
vout: 0,
|
vout: 0,
|
||||||
@ -1959,7 +1961,7 @@ mod test {
|
|||||||
|
|
||||||
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
|
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
|
||||||
let (psbt, mut original_details) = wallet
|
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();
|
.unwrap();
|
||||||
let mut tx = psbt.extract_tx();
|
let mut tx = psbt.extract_tx();
|
||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
@ -2023,7 +2025,7 @@ mod test {
|
|||||||
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
|
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
|
||||||
let (psbt, mut original_details) = wallet
|
let (psbt, mut original_details) = wallet
|
||||||
.create_tx(
|
.create_tx(
|
||||||
TxBuilder::from_addressees(vec![(addr.clone(), 0)])
|
TxBuilder::with_recipients(vec![(addr.clone(), 0)])
|
||||||
.send_all()
|
.send_all()
|
||||||
.add_utxo(OutPoint {
|
.add_utxo(OutPoint {
|
||||||
txid: incoming_txid,
|
txid: incoming_txid,
|
||||||
@ -2099,7 +2101,7 @@ mod test {
|
|||||||
|
|
||||||
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
|
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
|
||||||
let (psbt, mut original_details) = wallet
|
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();
|
.unwrap();
|
||||||
let mut tx = psbt.extract_tx();
|
let mut tx = psbt.extract_tx();
|
||||||
assert_eq!(tx.input.len(), 1);
|
assert_eq!(tx.input.len(), 1);
|
||||||
@ -2159,7 +2161,7 @@ mod test {
|
|||||||
|
|
||||||
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
|
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
|
||||||
let (psbt, mut original_details) = wallet
|
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();
|
.unwrap();
|
||||||
let mut tx = psbt.extract_tx();
|
let mut tx = psbt.extract_tx();
|
||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
@ -2224,7 +2226,7 @@ mod test {
|
|||||||
let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
|
let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
|
||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
let (psbt, _) = wallet
|
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();
|
.unwrap();
|
||||||
|
|
||||||
let (signed_psbt, finalized) = wallet.sign(psbt, None).unwrap();
|
let (signed_psbt, finalized) = wallet.sign(psbt, None).unwrap();
|
||||||
@ -2240,7 +2242,7 @@ mod test {
|
|||||||
get_funded_wallet("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)");
|
get_funded_wallet("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)");
|
||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
let (psbt, _) = wallet
|
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();
|
.unwrap();
|
||||||
|
|
||||||
let (signed_psbt, finalized) = wallet.sign(psbt, None).unwrap();
|
let (signed_psbt, finalized) = wallet.sign(psbt, None).unwrap();
|
||||||
@ -2255,7 +2257,7 @@ mod test {
|
|||||||
let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
|
let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
|
||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
let (mut psbt, _) = wallet
|
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();
|
.unwrap();
|
||||||
|
|
||||||
psbt.inputs[0].hd_keypaths.clear();
|
psbt.inputs[0].hd_keypaths.clear();
|
||||||
|
@ -84,6 +84,14 @@ pub enum SignerError {
|
|||||||
MissingHDKeypath,
|
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
|
/// Trait for signers
|
||||||
pub trait Signer: fmt::Debug {
|
pub trait Signer: fmt::Debug {
|
||||||
fn sign(
|
fn sign(
|
||||||
@ -92,9 +100,7 @@ pub trait Signer: fmt::Debug {
|
|||||||
input_index: Option<usize>,
|
input_index: Option<usize>,
|
||||||
) -> Result<(), SignerError>;
|
) -> Result<(), SignerError>;
|
||||||
|
|
||||||
fn sign_whole_tx(&self) -> bool {
|
fn sign_whole_tx(&self) -> bool;
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
|
fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
|
||||||
None
|
None
|
||||||
@ -128,6 +134,10 @@ impl Signer for DescriptorXKey<ExtendedPrivKey> {
|
|||||||
derived_key.private_key.sign(psbt, Some(input_index))
|
derived_key.private_key.sign(psbt, Some(input_index))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sign_whole_tx(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
|
fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
|
||||||
Some(DescriptorSecretKey::XPrv(self.clone()))
|
Some(DescriptorSecretKey::XPrv(self.clone()))
|
||||||
}
|
}
|
||||||
@ -176,6 +186,10 @@ impl Signer for PrivateKey {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sign_whole_tx(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
|
fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
|
||||||
Some(DescriptorSecretKey::PrivKey(self.clone()))
|
Some(DescriptorSecretKey::PrivKey(self.clone()))
|
||||||
}
|
}
|
||||||
@ -299,7 +313,7 @@ impl<Pk: MiniscriptKey> SignersContainer<Pk> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ComputeSighash {
|
pub(crate) trait ComputeSighash {
|
||||||
fn sighash(
|
fn sighash(
|
||||||
psbt: &psbt::PartiallySignedTransaction,
|
psbt: &psbt::PartiallySignedTransaction,
|
||||||
input_index: usize,
|
input_index: usize,
|
||||||
|
@ -44,9 +44,9 @@ pub fn get_timestamp() -> u64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub struct Instant(SystemInstant);
|
pub(crate) struct Instant(SystemInstant);
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
pub struct Instant(Duration);
|
pub(crate) struct Instant(Duration);
|
||||||
|
|
||||||
impl Instant {
|
impl Instant {
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
@ -28,12 +28,11 @@ use std::default::Default;
|
|||||||
use bitcoin::{Address, OutPoint, SigHashType, Transaction};
|
use bitcoin::{Address, OutPoint, SigHashType, Transaction};
|
||||||
|
|
||||||
use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm};
|
use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm};
|
||||||
use super::utils::FeeRate;
|
use crate::types::{FeeRate, UTXO};
|
||||||
use crate::types::UTXO;
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct TxBuilder<Cs: CoinSelectionAlgorithm> {
|
pub struct TxBuilder<Cs: CoinSelectionAlgorithm> {
|
||||||
pub(crate) addressees: Vec<(Address, u64)>,
|
pub(crate) recipients: Vec<(Address, u64)>,
|
||||||
pub(crate) send_all: bool,
|
pub(crate) send_all: bool,
|
||||||
pub(crate) fee_rate: Option<FeeRate>,
|
pub(crate) fee_rate: Option<FeeRate>,
|
||||||
pub(crate) policy_path: Option<BTreeMap<String, Vec<usize>>>,
|
pub(crate) policy_path: Option<BTreeMap<String, Vec<usize>>>,
|
||||||
@ -54,19 +53,19 @@ impl TxBuilder<DefaultCoinSelectionAlgorithm> {
|
|||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_addressees(addressees: Vec<(Address, u64)>) -> Self {
|
pub fn with_recipients(recipients: Vec<(Address, u64)>) -> Self {
|
||||||
Self::default().set_addressees(addressees)
|
Self::default().set_recipients(recipients)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Cs: CoinSelectionAlgorithm> TxBuilder<Cs> {
|
impl<Cs: CoinSelectionAlgorithm> TxBuilder<Cs> {
|
||||||
pub fn set_addressees(mut self, addressees: Vec<(Address, u64)>) -> Self {
|
pub fn set_recipients(mut self, recipients: Vec<(Address, u64)>) -> Self {
|
||||||
self.addressees = addressees;
|
self.recipients = recipients;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_addressee(mut self, address: Address, amount: u64) -> Self {
|
pub fn add_recipient(mut self, address: Address, amount: u64) -> Self {
|
||||||
self.addressees.push((address, amount));
|
self.recipients.push((address, amount));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,7 +157,7 @@ impl<Cs: CoinSelectionAlgorithm> TxBuilder<Cs> {
|
|||||||
|
|
||||||
pub fn coin_selection<P: CoinSelectionAlgorithm>(self, coin_selection: P) -> TxBuilder<P> {
|
pub fn coin_selection<P: CoinSelectionAlgorithm>(self, coin_selection: P) -> TxBuilder<P> {
|
||||||
TxBuilder {
|
TxBuilder {
|
||||||
addressees: self.addressees,
|
recipients: self.recipients,
|
||||||
send_all: self.send_all,
|
send_all: self.send_all,
|
||||||
fee_rate: self.fee_rate,
|
fee_rate: self.fee_rate,
|
||||||
policy_path: self.policy_path,
|
policy_path: self.policy_path,
|
||||||
@ -190,7 +189,7 @@ impl Default for TxOrdering {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TxOrdering {
|
impl TxOrdering {
|
||||||
pub fn modify_tx(&self, tx: &mut Transaction) {
|
pub fn sort_tx(&self, tx: &mut Transaction) {
|
||||||
match self {
|
match self {
|
||||||
TxOrdering::Untouched => {}
|
TxOrdering::Untouched => {}
|
||||||
TxOrdering::Shuffle => {
|
TxOrdering::Shuffle => {
|
||||||
@ -279,7 +278,7 @@ mod test {
|
|||||||
let original_tx = ordering_test_tx!();
|
let original_tx = ordering_test_tx!();
|
||||||
let mut tx = original_tx.clone();
|
let mut tx = original_tx.clone();
|
||||||
|
|
||||||
TxOrdering::Untouched.modify_tx(&mut tx);
|
TxOrdering::Untouched.sort_tx(&mut tx);
|
||||||
|
|
||||||
assert_eq!(original_tx, tx);
|
assert_eq!(original_tx, tx);
|
||||||
}
|
}
|
||||||
@ -289,7 +288,7 @@ mod test {
|
|||||||
let original_tx = ordering_test_tx!();
|
let original_tx = ordering_test_tx!();
|
||||||
let mut tx = original_tx.clone();
|
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_eq!(original_tx.input, tx.input);
|
||||||
assert_ne!(original_tx.output, tx.output);
|
assert_ne!(original_tx.output, tx.output);
|
||||||
@ -302,7 +301,7 @@ mod test {
|
|||||||
let original_tx = ordering_test_tx!();
|
let original_tx = ordering_test_tx!();
|
||||||
let mut tx = original_tx.clone();
|
let mut tx = original_tx.clone();
|
||||||
|
|
||||||
TxOrdering::BIP69Lexicographic.modify_tx(&mut tx);
|
TxOrdering::BIP69Lexicographic.sort_tx(&mut tx);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
tx.input[0].previous_output,
|
tx.input[0].previous_output,
|
||||||
|
@ -40,34 +40,6 @@ impl IsDust for u64 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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 struct After {
|
||||||
pub current_height: Option<u32>,
|
pub current_height: Option<u32>,
|
||||||
pub assume_height_reached: bool,
|
pub assume_height_reached: bool,
|
||||||
@ -158,7 +130,7 @@ impl<I: Iterator> Iterator for ChunksIterator<I> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use crate::types::FeeRate;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_fee_from_btc_per_kb() {
|
fn test_fee_from_btc_per_kb() {
|
||||||
|
@ -83,7 +83,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt
|
|||||||
use #root_ident::descriptor::ExtendedDescriptor;
|
use #root_ident::descriptor::ExtendedDescriptor;
|
||||||
use #root_ident::database::MemoryDatabase;
|
use #root_ident::database::MemoryDatabase;
|
||||||
use #root_ident::types::ScriptType;
|
use #root_ident::types::ScriptType;
|
||||||
use #root_ident::{Wallet, TxBuilder};
|
use #root_ident::{Wallet, TxBuilder, FeeRate};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@ -307,7 +307,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt
|
|||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000);
|
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();
|
let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
|
||||||
assert!(finalized, "Cannot finalize transaction");
|
assert!(finalized, "Cannot finalize transaction");
|
||||||
let tx = psbt.extract_tx();
|
let tx = psbt.extract_tx();
|
||||||
@ -334,7 +334,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt
|
|||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000);
|
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();
|
let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
|
||||||
assert!(finalized, "Cannot finalize transaction");
|
assert!(finalized, "Cannot finalize transaction");
|
||||||
let sent_txid = wallet.broadcast(psbt.extract_tx()).unwrap();
|
let sent_txid = wallet.broadcast(psbt.extract_tx()).unwrap();
|
||||||
@ -373,7 +373,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt
|
|||||||
|
|
||||||
let mut total_sent = 0;
|
let mut total_sent = 0;
|
||||||
for _ in 0..5 {
|
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();
|
let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
|
||||||
assert!(finalized, "Cannot finalize transaction");
|
assert!(finalized, "Cannot finalize transaction");
|
||||||
wallet.broadcast(psbt.extract_tx()).unwrap();
|
wallet.broadcast(psbt.extract_tx()).unwrap();
|
||||||
@ -405,7 +405,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt
|
|||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000);
|
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();
|
let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
|
||||||
assert!(finalized, "Cannot finalize transaction");
|
assert!(finalized, "Cannot finalize transaction");
|
||||||
wallet.broadcast(psbt.extract_tx()).unwrap();
|
wallet.broadcast(psbt.extract_tx()).unwrap();
|
||||||
@ -437,7 +437,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt
|
|||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000);
|
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();
|
let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
|
||||||
assert!(finalized, "Cannot finalize transaction");
|
assert!(finalized, "Cannot finalize transaction");
|
||||||
wallet.broadcast(psbt.extract_tx()).unwrap();
|
wallet.broadcast(psbt.extract_tx()).unwrap();
|
||||||
@ -470,7 +470,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt
|
|||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 75_000);
|
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();
|
let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
|
||||||
assert!(finalized, "Cannot finalize transaction");
|
assert!(finalized, "Cannot finalize transaction");
|
||||||
wallet.broadcast(psbt.extract_tx()).unwrap();
|
wallet.broadcast(psbt.extract_tx()).unwrap();
|
||||||
@ -501,7 +501,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt
|
|||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 75_000);
|
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();
|
let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
|
||||||
assert!(finalized, "Cannot finalize transaction");
|
assert!(finalized, "Cannot finalize transaction");
|
||||||
wallet.broadcast(psbt.extract_tx()).unwrap();
|
wallet.broadcast(psbt.extract_tx()).unwrap();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user