gui: display (estimated) feerate

For transactions that have been broadcast, the feerate is calculated
using the transaction's actual size. For PSBTs, the feerate is
calculated using the max satisfaction size for all inputs, whether
they have been signed or not.
This commit is contained in:
jp1ac4 2023-10-09 18:36:02 +01:00
parent 7f314844b1
commit 846431ed53
No known key found for this signature in database
GPG Key ID: A7ACD32423568D7B
7 changed files with 53 additions and 4 deletions

View File

@ -155,7 +155,7 @@ impl State for RecoveryPanel {
.cloned()
.collect();
let sigs = desc.partial_spend_info(&psbt).unwrap();
Ok(SpendTx::new(None, psbt, coins, sigs))
Ok(SpendTx::new(None, psbt, coins, sigs, desc.max_sat_vbytes()))
},
Message::Recovery,
);

View File

@ -432,7 +432,13 @@ impl Step for SaveSpend {
.unwrap();
self.spend = Some(psbt::PsbtState::new(
self.wallet.clone(),
SpendTx::new(None, psbt, draft.inputs.clone(), sigs),
SpendTx::new(
None,
psbt,
draft.inputs.clone(),
sigs,
self.wallet.main_descriptor.max_sat_vbytes(),
),
false,
));
}

View File

@ -210,6 +210,15 @@ pub fn payment_view<'a>(
.align_items(Alignment::Center)
.push(h3("Miner fee: ").style(color::GREY_3))
.push(amount_with_size(&fee_amount, H3_SIZE))
.push(text(" ").size(H3_SIZE))
.push(
text(format!(
"({} sats/vbyte)",
fee_amount.to_sat() / tx.tx.vsize() as u64
))
.size(H4_SIZE)
.style(color::GREY_3),
)
}))
.push(card::simple(
Column::new()

View File

@ -196,7 +196,13 @@ pub fn spend_header<'a>(tx: &SpendTx) -> Element<'a, Message> {
Row::new()
.align_items(Alignment::Center)
.push(h3("Miner fee: ").style(color::GREY_3))
.push(amount_with_size(&tx.fee_amount, H3_SIZE)),
.push(amount_with_size(&tx.fee_amount, H3_SIZE))
.push(text(" ").size(H3_SIZE))
.push(
text(format!("(~{} sats/vbyte)", &tx.min_feerate_vb()))
.size(H4_SIZE)
.style(color::GREY_3),
),
),
)
.into()

View File

@ -171,6 +171,15 @@ pub fn tx_view<'a>(
.align_items(Alignment::Center)
.push(h3("Miner fee: ").style(color::GREY_3))
.push(amount_with_size(&fee_amount, H3_SIZE))
.push(text(" ").size(H3_SIZE))
.push(
text(format!(
"({} sats/vbyte)",
fee_amount.to_sat() / tx.tx.vsize() as u64
))
.size(H4_SIZE)
.style(color::GREY_3),
)
})),
),
)

View File

@ -97,7 +97,13 @@ pub trait Daemon: Debug {
.main
.partial_spend_info(&tx.psbt)
.map_err(|e| DaemonError::Unexpected(e.to_string()))?;
spend_txs.push(model::SpendTx::new(tx.updated_at, tx.psbt, coins, sigs))
spend_txs.push(model::SpendTx::new(
tx.updated_at,
tx.psbt,
coins,
sigs,
info.descriptors.main.max_sat_vbytes(),
))
}
spend_txs.sort_by(|a, b| {
if a.status == b.status {

View File

@ -30,6 +30,9 @@ pub struct SpendTx {
pub change_indexes: Vec<usize>,
pub spend_amount: Amount,
pub fee_amount: Amount,
/// The maximum size difference (in virtual bytes) of
/// an input in this transaction before and after satisfaction.
pub max_sat_vbytes: usize,
pub status: SpendStatus,
pub sigs: PartialSpendInfo,
pub updated_at: Option<u32>,
@ -49,6 +52,7 @@ impl SpendTx {
psbt: Psbt,
coins: Vec<Coin>,
sigs: PartialSpendInfo,
max_sat_vbytes: usize,
) -> Self {
let mut change_indexes = Vec::new();
let (change_amount, spend_amount) = psbt.unsigned_tx.output.iter().enumerate().fold(
@ -87,6 +91,7 @@ impl SpendTx {
change_indexes,
spend_amount,
fee_amount: inputs_amount - spend_amount - change_amount,
max_sat_vbytes,
status,
sigs,
}
@ -122,6 +127,14 @@ impl SpendTx {
pub fn is_self_send(&self) -> bool {
!self.coins.is_empty() && self.spend_amount == Amount::from_sat(0)
}
/// Feerate obtained if all transaction inputs have the maximum satisfaction size.
pub fn min_feerate_vb(&self) -> u64 {
// This assumes all inputs are internal (have same max satisfaction size).
let max_tx_vbytes =
self.psbt.unsigned_tx.vsize() + (self.max_sat_vbytes * self.psbt.inputs.len());
self.fee_amount.to_sat() / max_tx_vbytes as u64
}
}
#[derive(Debug, Clone)]