[wallet] Add a type convert fee units, add Wallet::estimate_fee()
This commit is contained in:
parent
5f80950971
commit
08792b2fcd
@ -11,6 +11,7 @@ use self::utils::{ELSGetHistoryRes, ELSListUnspentRes, ElectrumLikeSync};
|
||||
use super::*;
|
||||
use crate::database::{BatchDatabase, DatabaseUtils};
|
||||
use crate::error::Error;
|
||||
use crate::FeeRate;
|
||||
|
||||
pub struct ElectrumBlockchain(Option<Client>);
|
||||
|
||||
@ -77,6 +78,15 @@ impl OnlineBlockchain for ElectrumBlockchain {
|
||||
.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 {
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::collections::HashSet;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use futures::stream::{self, StreamExt, TryStreamExt};
|
||||
|
||||
@ -18,6 +18,7 @@ use self::utils::{ELSGetHistoryRes, ELSListUnspentRes, ElectrumLikeSync};
|
||||
use super::*;
|
||||
use crate::database::{BatchDatabase, DatabaseUtils};
|
||||
use crate::error::Error;
|
||||
use crate::FeeRate;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UrlClient {
|
||||
@ -99,6 +100,27 @@ impl OnlineBlockchain for EsploraBlockchain {
|
||||
.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 {
|
||||
@ -232,6 +254,17 @@ 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]
|
||||
|
@ -5,6 +5,7 @@ use bitcoin::{Transaction, Txid};
|
||||
|
||||
use crate::database::{BatchDatabase, DatabaseUtils};
|
||||
use crate::error::Error;
|
||||
use crate::FeeRate;
|
||||
|
||||
pub mod utils;
|
||||
|
||||
@ -64,6 +65,7 @@ pub trait OnlineBlockchain: Blockchain {
|
||||
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>);
|
||||
|
@ -13,7 +13,7 @@ use bitcoin::{Address, OutPoint};
|
||||
|
||||
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();
|
||||
@ -331,7 +331,7 @@ where
|
||||
|
||||
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
|
||||
|
@ -42,4 +42,6 @@ pub mod types;
|
||||
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};
|
||||
|
@ -22,8 +22,8 @@ pub mod time;
|
||||
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};
|
||||
@ -142,7 +142,7 @@ where
|
||||
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);
|
||||
}
|
||||
@ -759,6 +759,11 @@ where
|
||||
|
||||
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)]
|
||||
|
@ -3,13 +3,14 @@ use std::collections::BTreeMap;
|
||||
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>>,
|
||||
@ -44,13 +45,8 @@ impl<Cs: CoinSelectionAlgorithm> TxBuilder<Cs> {
|
||||
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
|
||||
}
|
||||
|
||||
@ -93,7 +89,7 @@ impl<Cs: CoinSelectionAlgorithm> TxBuilder<Cs> {
|
||||
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,
|
||||
|
@ -14,6 +14,34 @@ impl IsDust for u64 {
|
||||
}
|
||||
}
|
||||
|
||||
#[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,
|
||||
@ -46,3 +74,26 @@ impl<I: Iterator> Iterator for ChunksIterator<I> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user