Merge #725: gui: display (estimated) feerate

846431ed53efd2b29aefabe84ed56d054c0d4b45 gui: display (estimated) feerate (jp1ac4)

Pull request description:

  This is to resolve #558.

  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 (see https://github.com/wizardsardine/liana/issues/558#issuecomment-1750128022).

  In both cases, the calculated feerate currently has type `u64`.

  This is an example of a transaction on the Home tab:
  ![image](https://github.com/wizardsardine/liana/assets/121959000/ad81094f-ac16-4dd4-9229-75dbda2ca5b5)

  The same transaction on the Transactions tab:
  ![image](https://github.com/wizardsardine/liana/assets/121959000/576f3f1f-81c0-4f1f-8f9d-fc4e1fdec000)

  On the PSBTs tab, the feerate is an estimate:
  ![image](https://github.com/wizardsardine/liana/assets/121959000/cf7a4d44-fe5e-44a8-b9bb-e429e76540a5)

ACKs for top commit:
  edouardparis:
    ACK 846431ed53efd2b29aefabe84ed56d054c0d4b45
  darosior:
    Github ACK 846431ed53efd2b29aefabe84ed56d054c0d4b45 -- feerate estimate assumption seem fine for now.

Tree-SHA512: 499e798a62bad2f1937ab7aa303bccbdfd5b58c730c0384f1ab58d8e2904b30221c585eb37e11cabb200662920bbdca2b1c5ce886eb187539386da42daa64bbd
This commit is contained in:
Antoine Poinsot 2023-10-17 12:27:32 +02:00
commit e8ba65c501
No known key found for this signature in database
GPG Key ID: E13FC145CD3F4304
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)]