diff --git a/src/app/context.rs b/src/app/context.rs index 35ed92ea..500ef62a 100644 --- a/src/app/context.rs +++ b/src/app/context.rs @@ -1,12 +1,15 @@ +use std::future::Future; +use std::pin::Pin; +use std::sync::Arc; + +use bitcoin::util::psbt::PartiallySignedTransaction as Psbt; + use super::menu::Menu; use crate::{app::config, conversion::Converter, daemon::Daemon, revault::Role}; use revaultd::config::Config as DaemonConfig; use revaultd::revault_tx::miniscript::DescriptorPublicKey; use revault_hwi::{app::revault::RevaultHWI, HWIError}; -use std::future::Future; -use std::pin::Pin; -use std::sync::Arc; pub type HardwareWallet = Box, HWIError>> + Send + Sync>; @@ -82,6 +85,34 @@ impl Context { }) .collect() } + + pub fn user_signed(&self, psbt: &Psbt) -> bool { + let man_fp = &self + .config + .daemon + .manager_config + .as_ref() + .map(|key| key.xpub.fingerprint()); + let stk_fp = &self + .config + .daemon + .stakeholder_config + .as_ref() + .map(|key| key.xpub.fingerprint()); + if let Some(input) = psbt.inputs.first() { + input.partial_sigs.keys().any(|key| { + input + .bip32_derivation + .get(key) + .map(|(fingerprint, _)| { + Some(*fingerprint) == *man_fp || Some(*fingerprint) == *stk_fp + }) + .unwrap_or(false) + }) + } else { + false + } + } } pub struct ConfigContext { diff --git a/src/app/state/spend_transaction.rs b/src/app/state/spend_transaction.rs index 4a12b948..49118a33 100644 --- a/src/app/state/spend_transaction.rs +++ b/src/app/state/spend_transaction.rs @@ -1,7 +1,4 @@ -use bitcoin::util::{ - bip32::{ExtendedPubKey, Fingerprint}, - psbt::PartiallySignedTransaction as Psbt, -}; +use bitcoin::util::{bip32::Fingerprint, psbt::PartiallySignedTransaction as Psbt}; use std::convert::From; use bitcoin::OutPoint; @@ -52,12 +49,7 @@ impl SpendTransactionState { change_index: None, action: SpendTransactionAction::new( ctx.managers_threshold, - &ctx.config - .daemon - .manager_config - .as_ref() - .expect("User is a manager") - .xpub, + ctx.user_signed(&psbt), &ctx.managers_xpubs(), &psbt, ), @@ -142,6 +134,7 @@ impl State for SpendTransactionState { self.action.view(ctx, &self.psbt), self.warning.as_ref(), show_delete_button, + ctx.user_signed(&self.psbt), ) } @@ -185,7 +178,7 @@ pub enum SpendTransactionAction { impl SpendTransactionAction { fn new( managers_threshold: usize, - user_manager_xpub: &ExtendedPubKey, + user_signed: bool, managers_xpubs: &Vec, psbt: &Psbt, ) -> Self { @@ -198,13 +191,7 @@ impl SpendTransactionAction { warning: None, view: SpendTransactionBroadcastView::new(), }; - } else if input.partial_sigs.keys().any(|key| { - input - .bip32_derivation - .get(key) - .map(|(fingerprint, _)| user_manager_xpub.fingerprint() == *fingerprint) - .unwrap_or(false) - }) { + } else if user_signed { return Self::SharePsbt { psbt_input: form::Value::default(), processing: false, @@ -277,12 +264,7 @@ impl SpendTransactionAction { SpendTxMessage::UnselectDelete => { *self = Self::new( ctx.managers_threshold, - &ctx.config - .daemon - .manager_config - .as_ref() - .expect("User is a manager") - .xpub, + ctx.user_signed(psbt), &ctx.managers_xpubs(), psbt, ); @@ -320,12 +302,7 @@ impl SpendTransactionAction { *psbt = signer.target.spend_tx.clone(); *self = Self::new( ctx.managers_threshold, - &ctx.config - .daemon - .manager_config - .as_ref() - .expect("User is a manager") - .xpub, + true, &ctx.managers_xpubs(), psbt, ); @@ -599,7 +576,7 @@ mod tests { let mut psbt = Psbt::from_str("cHNidP8BALQCAAAAAc1946BSKWX5trghNlBq/IIYScLPYqr9Bqs2LfqOYuqcAAAAAAAIAAAAA+BAAAAAAAAAIgAgCOQxrx6W/t0dSZikMBNYG2Yyam/3LIoVrAy6e8ZDUAyA8PoCAAAAACIAIMuwqNTx88KHHtIR0EeURzEu9pUmbnUxd22KzYKi25A2CBH6AgAAAAAiACB18mkXdMgWd4MYRrAoIgDiiLLFlxC1j3Qxg9SSVQfbxQAAAAAAAQEruFn1BQAAAAAiACBI6M9l6zams92tyCK/4gbWyNfJMJzgoOv34L0X7GTovAEDBAEAAAABBWEhAgKTOrEDfq0KpKeFjG1J1nBeH7O8X2awCRive58A7NUmrFGHZHapFHKpXyKvmhuuuFL5qVJy+MIdmPJkiKxrdqkUtsmtuJyMk3Jsg+KhtdlHidd7lWGIrGyTUodnWLJoIgYCApM6sQN+rQqkp4WMbUnWcF4fs7xfZrAJGK97nwDs1SYIJR1gCQAAAAAAIgICUHL04HZXilyJ1B118e1Smr+S8c1qtja46Le7DzMCaUMI+93szQAAAAAAACICAlgt7b9E9GVk5djNsGdTbWDr40zR0YAc/1G7+desKJtDCNZ9f+kAAAAAIgIDRwTey1W1qoj/0e9dBjZiSMExThllURNv8U6ri7pKSQ4IcqlfIgAAAAAA").unwrap(); let user_manager_xpub = ExtendedPubKey::from_str("xpub6CZFHPW1GiB8YgV7zGpeQDB6mMHZYPQyUaHrM1nMvKMgLxwok4xCtnzjuxQ3p1LHJUkz5i1Y7bRy5fmGrdg8UBVb39XdXNtWWd2wTsNd7T9").unwrap(); - let action = SpendTransactionAction::new(2, &user_manager_xpub, &Vec::new(), &psbt); + let action = SpendTransactionAction::new(2, false, &Vec::new(), &psbt); assert!(matches!(action, SpendTransactionAction::Sign { .. })); psbt.inputs[0].partial_sigs.insert( @@ -610,13 +587,13 @@ mod tests { "304402202f5eec50f34929e4bd8f6b7e81426795b0cd3608a4dad53ffab3e7af38ab627a02204ff61d9df2432ff3272c17d9baee1ec6b6dcb72b198be7f4ef843d5d47010a0401".as_bytes().to_vec(), ); - let action = SpendTransactionAction::new(2, &user_manager_xpub, &Vec::new(), &psbt); + let action = SpendTransactionAction::new(2, true, &Vec::new(), &psbt); assert!(matches!(action, SpendTransactionAction::SharePsbt { .. })); - let action = SpendTransactionAction::new(1, &user_manager_xpub, &Vec::new(), &psbt); + let action = SpendTransactionAction::new(1, true, &Vec::new(), &psbt); assert!(matches!(action, SpendTransactionAction::Broadcast { .. })); - let action = SpendTransactionAction::new(0, &user_manager_xpub, &Vec::new(), &psbt); + let action = SpendTransactionAction::new(0, true, &Vec::new(), &psbt); assert!(matches!(action, SpendTransactionAction::Broadcast { .. })); } } diff --git a/src/app/view/manager.rs b/src/app/view/manager.rs index 1058c8ef..ea584256 100644 --- a/src/app/view/manager.rs +++ b/src/app/view/manager.rs @@ -829,9 +829,9 @@ pub fn spend_tx_with_feerate_view<'a, T: 'a>( ), ) .push( - Row::new() - .push(col_input.width(Length::FillPortion(1))) - .push(right_column.width(Length::FillPortion(1))) + Column::new() + .push(col_input.width(Length::Fill)) + .push(right_column.width(Length::Fill)) .spacing(20), ) .spacing(20), diff --git a/src/app/view/spend_transaction.rs b/src/app/view/spend_transaction.rs index a9af1b68..142dff03 100644 --- a/src/app/view/spend_transaction.rs +++ b/src/app/view/spend_transaction.rs @@ -1,12 +1,17 @@ use bitcoin::{util::psbt::PartiallySignedTransaction as Psbt, Amount}; -use iced::{scrollable, Align, Column, Container, Element, Length, Row, Toggler}; +use iced::{ + scrollable, tooltip, Align, Checkbox, Column, Container, Element, Length, Row, Tooltip, +}; use revaultd::revault_tx::transactions::RevaultTransaction; use revault_ui::{ color, - component::{badge, button, card, form, scroll, text::Text, ContainerBackgroundStyle}, + component::{ + badge, button, card, form, scroll, separation, text::Text, ContainerBackgroundStyle, + TooltipStyle, + }, icon, }; @@ -47,19 +52,8 @@ impl SpendTransactionView { action: Element<'a, Message>, warning: Option<&Error>, show_delete_button: bool, + user_signed: bool, ) -> Element<'a, Message> { - let col = Column::new() - .push(spend_tx_with_feerate_view( - ctx, - spent_vaults, - psbt, - change_index, - cpfp_index, - None, - )) - .push(action) - .spacing(20); - let row = if show_delete_button { Row::new().push( Container::new( @@ -100,28 +94,6 @@ impl SpendTransactionView { vaults_amount - spend_amount - change_amount }; - let mut col_header = Column::new().push( - Text::new(&format!( - "txid: {}", - psbt.global.unsigned_tx.txid().to_string() - )) - .small() - .bold(), - ); - if psbt.inputs.len() > 0 { - col_header = col_header.push( - Row::new() - .push(Text::new(&format!( - "Total number of signatures: {} / {}", - psbt.inputs[0].partial_sigs.len(), - ctx.managers_threshold, - ))) - .push(icon::key_icon()) - .align_items(Align::Center) - .spacing(5), - ) - } - Container::new(scroll( &mut self.scroll, Container::new( @@ -137,66 +109,125 @@ impl SpendTransactionView { ) .align_items(Align::Center), ) - .push(card::white(Container::new( - Row::new() + .push( + Column::new() .push( - Container::new( - Row::new() - .push(badge::pending_spent_tx()) - .push(col_header) - .spacing(20), - ) - .width(Length::Fill), + Row::new() + .push(badge::pending_spent_tx()) + .push(Text::new("Spend").bold()) + .spacing(5) + .align_items(Align::Center), ) .push( - Container::new( - Column::new() - .push( - Row::new() - .push( - Text::new(&format!( - "{}", - ctx.converter.converts(Amount::from_sat( - spend_amount - )), - )) - .bold(), - ) - .push( - Text::new(&format!(" {}", ctx.converter.unit)) - .small(), - ) - .align_items(Align::Center), - ) - .push( - Row::new() - .push( - Text::new(&format!( - "Fees: {}", - ctx.converter - .converts(Amount::from_sat(fees)), - )) - .small(), - ) - .push( - Text::new(&format!(" {}", ctx.converter.unit)) - .small(), - ) - .align_items(Align::Center), - ) - .align_items(Align::End), - ) - .width(Length::Shrink), + Column::new() + .push( + Text::new(&format!( + "- {} {}", + ctx.converter.converts(Amount::from_sat(spend_amount)), + ctx.converter.unit, + )) + .bold() + .size(50), + ) + .push(Container::new(Text::new(&format!( + "Fee: {} {}", + ctx.converter.converts(Amount::from_sat(fees)), + ctx.converter.unit, + )))) + .align_items(Align::Center), ) + .push(card::white( + Column::new() + .push(Container::new( + Row::new() + .push( + Container::new( + Row::new() + .push(Container::new( + icon::key_icon() + .size(30) + .width(Length::Fill), + )) + .push( + Column::new() + .push( + Text::new( + "Number of signatures:", + ) + .bold(), + ) + .push(Text::new(&format!( + "{}", + psbt.inputs[0] + .partial_sigs + .len(), + ))), + ) + .align_items(Align::Center) + .spacing(20), + ) + .align_x(Align::Center) + .width(Length::FillPortion(1)), + ) + .push( + Container::new(if user_signed { + Row::new() + .push(Container::new( + Text::from( + icon::done_icon() + .size(30) + .width(Length::Fill), + ) + .success(), + )) + .push(Text::new("You signed").success()) + .align_items(Align::Center) + .spacing(20) + } else { + Row::new() + .push(Container::new(Text::from( + icon::cross_icon() + .size(30) + .width(Length::Fill), + ))) + .push(Text::new("You did not sign")) + .align_items(Align::Center) + .spacing(20) + }) + .align_x(Align::Center) + .width(Length::FillPortion(1)), + ) + .align_items(Align::Center) + .spacing(20), + )) + .push(separation().width(Length::Fill)) + .push( + Row::new() + .push(Text::new("Tx ID:").bold().width(Length::Fill)) + .push( + Text::new(&format!( + "{}", + psbt.global.unsigned_tx.txid() + )) + .small(), + ), + ) + .spacing(20), + )) + .push(action) + .push(spend_tx_with_feerate_view( + ctx, + spent_vaults, + psbt, + change_index, + cpfp_index, + None, + )) .spacing(20) + .max_width(800) .align_items(Align::Center), - ))) - .push( - Container::new(col) - .width(Length::Fill) - .align_x(Align::Center), ) - .spacing(20), + .align_items(Align::Center), ), )) .style(ContainerBackgroundStyle) @@ -262,16 +293,6 @@ impl SpendTransactionSharePsbtView { } Container::new( Column::new() - .push( - card::success( - Row::new() - .push(Text::from(icon::done_icon()).size(20).success()) - .push(Text::new("You signed").success()) - .spacing(20), - ) - .width(Length::Fill) - .align_x(Align::Center), - ) .push(card::white(Container::new( col_action .push(Text::new("Enter PSBT:")) @@ -434,42 +455,42 @@ impl SpendTransactionBroadcastView { } else { col_action = col_action .push(Text::new("Transaction is fully signed")) - .push(Toggler::new( - with_priority, - String::from("with priority"), - |priority| Message::SpendTx(SpendTxMessage::WithPriority(priority)), - )) + .push( + Row::new() + .push(Checkbox::new( + with_priority, + String::from("Set high priority"), + |priority| Message::SpendTx(SpendTxMessage::WithPriority(priority)), + )) + .push( + Tooltip::new( + icon::tooltip_icon().size(15), + "try to feebump the transactions in the background if it does not confirm.", + tooltip::Position::Right, + ) + .gap(5) + .size(20) + .padding(10) + .style(TooltipStyle), + ) + .spacing(5), + ) .push( button::important( &mut self.confirm_button, button::button_content(None, "Broadcast"), ) + .width(Length::Units(200)) .on_press(Message::SpendTx(SpendTxMessage::Broadcast)), ); } - Container::new( - Column::new() - .push( - card::success( - Row::new() - .push(Text::from(icon::done_icon()).size(20).success()) - .push(Text::new("You signed").success()) - .spacing(20), - ) - .width(Length::Fill) - .align_x(Align::Center), - ) - .push( - card::white(Container::new( - col_action.align_items(Align::Center).spacing(20), - )) - .width(Length::Fill) - .align_x(Align::Center) - .padding(20), - ) - .spacing(20), - ) + card::white(Container::new( + col_action.align_items(Align::Center).spacing(20), + )) + .width(Length::Fill) + .align_x(Align::Center) + .padding(20) .into() } }