use super::*;
use crate::database::{BatchDatabase, DatabaseUtils};
use crate::error::Error;
+use crate::FeeRate;
pub struct ElectrumBlockchain(Option<Client>);
.block_headers_subscribe()
.map(|data| data.height)?)
}
+
+ fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
+ Ok(FeeRate::from_btc_per_kvb(
+ self.0
+ .as_ref()
+ .ok_or(Error::OfflineClient)?
+ .estimate_fee(target)? as f32,
+ ))
+ }
}
impl ElectrumLikeSync for Client {
-use std::collections::HashSet;
+use std::collections::{HashMap, HashSet};
use futures::stream::{self, StreamExt, TryStreamExt};
use super::*;
use crate::database::{BatchDatabase, DatabaseUtils};
use crate::error::Error;
+use crate::FeeRate;
#[derive(Debug)]
pub struct UrlClient {
.ok_or(Error::OfflineClient)?
._get_height())?)
}
+
+ fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
+ let estimates = await_or_block!(self
+ .0
+ .as_ref()
+ .ok_or(Error::OfflineClient)?
+ ._get_fee_estimates())?;
+
+ let fee_val = estimates
+ .into_iter()
+ .map(|(k, v)| Ok::<_, std::num::ParseIntError>((k.parse::<usize>()?, v)))
+ .collect::<Result<Vec<_>, _>>()
+ .map_err(|e| Error::Generic(e.to_string()))?
+ .into_iter()
+ .take_while(|(k, _)| k <= &target)
+ .map(|(_, v)| v)
+ .last()
+ .unwrap_or(1.0);
+
+ Ok(FeeRate::from_sat_per_vb(fee_val as f32))
+ }
}
impl UrlClient {
})
.collect())
}
+
+ async fn _get_fee_estimates(&self) -> Result<HashMap<String, f64>, EsploraError> {
+ Ok(self
+ .client
+ .get(&format!("{}/api/fee-estimates", self.url,))
+ .send()
+ .await?
+ .error_for_status()?
+ .json::<HashMap<String, f64>>()
+ .await?)
+ }
}
#[maybe_async]
use crate::database::{BatchDatabase, DatabaseUtils};
use crate::error::Error;
+use crate::FeeRate;
pub mod utils;
fn broadcast(&self, tx: &Transaction) -> Result<(), Error>;
fn get_height(&self) -> Result<usize, Error>;
+ fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error>;
}
pub type ProgressData = (f32, Option<String>);
use crate::error::Error;
use crate::types::ScriptType;
-use crate::{TxBuilder, Wallet};
+use crate::{FeeRate, TxBuilder, Wallet};
fn parse_addressee(s: &str) -> Result<(Address, u64), String> {
let parts: Vec<_> = s.split(":").collect();
if let Some(fee_rate) = sub_matches.value_of("fee_rate") {
let fee_rate = f32::from_str(fee_rate).map_err(|s| Error::Generic(s.to_string()))?;
- tx_builder = tx_builder.fee_rate(fee_rate);
+ tx_builder = tx_builder.fee_rate(FeeRate::from_sat_per_vb(fee_rate));
}
if let Some(utxos) = sub_matches.values_of("utxos") {
let utxos = utxos
pub mod wallet;
pub use descriptor::ExtendedDescriptor;
-pub use wallet::{OfflineWallet, TxBuilder, Wallet};
+pub use wallet::tx_builder::TxBuilder;
+pub use wallet::utils::FeeRate;
+pub use wallet::{OfflineWallet, Wallet};
pub mod tx_builder;
pub mod utils;
-pub use tx_builder::TxBuilder;
-use utils::IsDust;
+use tx_builder::TxBuilder;
+use utils::{FeeRate, IsDust};
use crate::blockchain::{noop_progress, Blockchain, OfflineBlockchain, OnlineBlockchain};
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
output: vec![],
};
- let fee_rate = builder.fee_perkb.unwrap_or(1e3) * 100_000.0;
+ let fee_rate = builder.fee_rate.unwrap_or_default().as_sat_vb();
if builder.send_all && builder.addressees.len() != 1 {
return Err(Error::SendAllMultipleOutputs);
}
Ok(tx.txid())
}
+
+ #[maybe_async]
+ pub fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
+ Ok(maybe_await!(self.client.estimate_fee(target))?)
+ }
}
#[cfg(test)]
use bitcoin::{Address, OutPoint, SigHashType};
use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm};
+use super::utils::FeeRate;
// TODO: add a flag to ignore change outputs (make them unspendable)
#[derive(Debug, Default)]
pub struct TxBuilder<Cs: CoinSelectionAlgorithm> {
pub(crate) addressees: Vec<(Address, u64)>,
pub(crate) send_all: bool,
- pub(crate) fee_perkb: Option<f32>,
+ pub(crate) fee_rate: Option<FeeRate>,
pub(crate) policy_path: Option<BTreeMap<String, Vec<usize>>>,
pub(crate) utxos: Option<Vec<OutPoint>>,
pub(crate) unspendable: Option<Vec<OutPoint>>,
self
}
- pub fn fee_rate(mut self, satoshi_per_vbyte: f32) -> Self {
- self.fee_perkb = Some(satoshi_per_vbyte * 1e3);
- self
- }
-
- pub fn fee_rate_perkb(mut self, satoshi_per_kb: f32) -> Self {
- self.fee_perkb = Some(satoshi_per_kb);
+ pub fn fee_rate(mut self, fee_rate: FeeRate) -> Self {
+ self.fee_rate = Some(fee_rate);
self
}
TxBuilder {
addressees: self.addressees,
send_all: self.send_all,
- fee_perkb: self.fee_perkb,
+ fee_rate: self.fee_rate,
policy_path: self.policy_path,
utxos: self.utxos,
unspendable: self.unspendable,
}
}
+#[derive(Debug, Copy, Clone)]
+// 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 ChunksIterator<I: Iterator> {
iter: I,
size: usize,
Some(v)
}
}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn test_fee_from_btc_per_kb() {
+ let fee = FeeRate::from_btc_per_kvb(1e-5);
+ assert!((fee.as_sat_vb() - 1.0).abs() < 0.0001);
+ }
+
+ #[test]
+ fn test_fee_from_sats_vbyte() {
+ let fee = FeeRate::from_sat_per_vb(1.0);
+ assert!((fee.as_sat_vb() - 1.0).abs() < 0.0001);
+ }
+
+ #[test]
+ fn test_fee_default_min_relay_fee() {
+ let fee = FeeRate::default_min_relay_fee();
+ assert!((fee.as_sat_vb() - 1.0).abs() < 0.0001);
+ }
+}