From 846431ed53efd2b29aefabe84ed56d054c0d4b45 Mon Sep 17 00:00:00 2001 From: jp1ac4 <121959000+jp1ac4@users.noreply.github.com> Date: Mon, 9 Oct 2023 18:36:02 +0100 Subject: [PATCH] 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. --- gui/src/app/state/recovery.rs | 2 +- gui/src/app/state/spend/step.rs | 8 +++++++- gui/src/app/view/home.rs | 9 +++++++++ gui/src/app/view/psbt.rs | 8 +++++++- gui/src/app/view/transactions.rs | 9 +++++++++ gui/src/daemon/mod.rs | 8 +++++++- gui/src/daemon/model.rs | 13 +++++++++++++ 7 files changed, 53 insertions(+), 4 deletions(-) diff --git a/gui/src/app/state/recovery.rs b/gui/src/app/state/recovery.rs index 5daecece..dbe904e9 100644 --- a/gui/src/app/state/recovery.rs +++ b/gui/src/app/state/recovery.rs @@ -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, ); diff --git a/gui/src/app/state/spend/step.rs b/gui/src/app/state/spend/step.rs index 00d226a2..dcbe53e1 100644 --- a/gui/src/app/state/spend/step.rs +++ b/gui/src/app/state/spend/step.rs @@ -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, )); } diff --git a/gui/src/app/view/home.rs b/gui/src/app/view/home.rs index 7e59e74e..0a1a28cd 100644 --- a/gui/src/app/view/home.rs +++ b/gui/src/app/view/home.rs @@ -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() diff --git a/gui/src/app/view/psbt.rs b/gui/src/app/view/psbt.rs index f314ab36..587d17e8 100644 --- a/gui/src/app/view/psbt.rs +++ b/gui/src/app/view/psbt.rs @@ -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() diff --git a/gui/src/app/view/transactions.rs b/gui/src/app/view/transactions.rs index 127cb194..20491080 100644 --- a/gui/src/app/view/transactions.rs +++ b/gui/src/app/view/transactions.rs @@ -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), + ) })), ), ) diff --git a/gui/src/daemon/mod.rs b/gui/src/daemon/mod.rs index 8b3a9426..1c4e975f 100644 --- a/gui/src/daemon/mod.rs +++ b/gui/src/daemon/mod.rs @@ -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 { diff --git a/gui/src/daemon/model.rs b/gui/src/daemon/model.rs index 2f6b53d1..b36d9e45 100644 --- a/gui/src/daemon/model.rs +++ b/gui/src/daemon/model.rs @@ -30,6 +30,9 @@ pub struct SpendTx { pub change_indexes: Vec, 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, @@ -49,6 +52,7 @@ impl SpendTx { psbt: Psbt, coins: Vec, 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)]