Add update psbt action to spend tx panel

This commit is contained in:
edouard 2023-01-05 18:00:45 +01:00
parent c4ccbf0113
commit 9921bcadd5
3 changed files with 208 additions and 9 deletions

View File

@ -1,7 +1,10 @@
use std::sync::Arc;
use iced::{Command, Element};
use liana::miniscript::bitcoin::util::{bip32::Fingerprint, psbt::Psbt};
use liana::miniscript::bitcoin::{
consensus,
util::{bip32::Fingerprint, psbt::Psbt},
};
use crate::{
app::{
@ -12,7 +15,7 @@ use crate::{
Daemon,
},
hw::{list_hardware_wallets, HardwareWallet},
ui::component::modal,
ui::component::{form, modal},
};
trait Action {
@ -79,6 +82,12 @@ impl SpendTxState {
self.action = Some(Box::new(action));
return cmd;
}
view::SpendTxMessage::EditPsbt => {
let action = UpdateAction::new(self.tx.psbt.to_string());
let cmd = action.load(daemon);
self.action = Some(Box::new(action));
return cmd;
}
view::SpendTxMessage::Broadcast => {
self.action = Some(Box::new(BroadcastAction::default()));
}
@ -349,3 +358,110 @@ async fn sign_psbt(
hw.sign_tx(&mut psbt).await.map_err(Error::from)?;
Ok((psbt, fingerprint))
}
pub struct UpdateAction {
psbt: String,
updated: form::Value<String>,
processing: bool,
error: Option<Error>,
success: bool,
}
impl UpdateAction {
pub fn new(psbt: String) -> Self {
Self {
psbt,
updated: form::Value::default(),
processing: false,
error: None,
success: false,
}
}
}
impl Action for UpdateAction {
fn view(&self) -> Element<view::Message> {
if self.success {
view::spend::detail::update_spend_success_view()
} else {
view::spend::detail::update_spend_view(
self.psbt.clone(),
&self.updated,
self.error.as_ref(),
self.processing,
)
}
}
fn update(
&mut self,
daemon: Arc<dyn Daemon + Sync + Send>,
_cache: &Cache,
message: Message,
tx: &mut SpendTx,
) -> Command<Message> {
match message {
Message::Updated(res) => {
self.processing = false;
match res {
Ok(()) => {
self.success = true;
self.error = None;
let psbt = consensus::encode::deserialize::<Psbt>(
&base64::decode(&self.updated.value).unwrap(),
)
.expect("Already checked");
for (i, input) in tx.psbt.inputs.iter_mut().enumerate() {
if tx
.psbt
.unsigned_tx
.input
.get(i)
.map(|tx_in| tx_in.previous_output)
!= psbt
.unsigned_tx
.input
.get(i)
.map(|tx_in| tx_in.previous_output)
{
continue;
}
if let Some(updated_input) = psbt.inputs.get(i) {
input
.partial_sigs
.extend(updated_input.partial_sigs.clone().into_iter());
}
}
}
Err(e) => self.error = e.into(),
}
}
Message::View(view::Message::ImportSpend(view::ImportSpendMessage::PsbtEdited(s))) => {
self.updated.value = s;
if let Some(psbt) = base64::decode(&self.updated.value)
.ok()
.and_then(|bytes| consensus::encode::deserialize::<Psbt>(&bytes).ok())
{
self.updated.valid = tx.psbt.unsigned_tx.txid() == psbt.unsigned_tx.txid();
}
}
Message::View(view::Message::ImportSpend(view::ImportSpendMessage::Confirm)) => {
if self.updated.valid {
self.processing = true;
self.error = None;
let updated: Psbt = consensus::encode::deserialize(
&base64::decode(&self.updated.value).expect("Already checked"),
)
.unwrap();
return Command::perform(
async move { daemon.update_spend_tx(&updated).map_err(|e| e.into()) },
Message::Updated,
);
}
}
_ => {}
}
Command::none()
}
}

View File

@ -41,6 +41,8 @@ pub enum SpendTxMessage {
Confirm,
Cancel,
SelectHardwareWallet(usize),
EditPsbt,
PsbtEdited(String),
Next,
}

View File

@ -1,5 +1,5 @@
use iced::{
widget::{Button, Column, Container, Row, Scrollable},
widget::{Button, Column, Container, Row, Scrollable, Space},
Alignment, Element, Length,
};
@ -13,10 +13,11 @@ use crate::{
daemon::model::{Coin, SpendStatus, SpendTx},
hw::HardwareWallet,
ui::{
color,
component::{
badge, button, card,
collapse::Collapse,
container, separation,
container, form, separation,
text::{text, Text},
},
icon,
@ -266,18 +267,35 @@ fn spend_overview_view<'a>(tx: &SpendTx) -> Element<'a, Message> {
.push(separation().width(Length::Fill))
.push(
Column::new()
.spacing(10)
.push(
Row::new()
.push(text("Tx ID:").bold().width(Length::Fill))
.push(text(format!("{}", tx.psbt.unsigned_tx.txid())).small())
.push(text(tx.psbt.unsigned_tx.txid().to_string()).small())
.push(
Button::new(icon::clipboard_icon())
.on_press(Message::Clipboard(
tx.psbt.unsigned_tx.txid().to_string(),
))
.style(button::Style::TransparentBorder.into()),
)
.align_items(Alignment::Center),
)
.push(
Row::new()
.push(text("Psbt:").bold().width(Length::Fill))
.align_items(Alignment::Center)
.push(text("PSBT:").bold().width(Length::Fill))
.push(
button::transparent(Some(icon::clipboard_icon()), "Copy")
.on_press(Message::Clipboard(tx.psbt.to_string())),
Row::new()
.spacing(5)
.push(
button::border(Some(icon::clipboard_icon()), "Copy")
.on_press(Message::Clipboard(tx.psbt.to_string())),
)
.push(
button::border(Some(icon::import_icon()), "Update")
.on_press(Message::Spend(SpendTxMessage::EditPsbt)),
),
)
.align_items(Alignment::Center),
),
@ -539,7 +557,7 @@ pub fn sign_action<'a>(
Column::new()
.push(
Column::new()
.spacing(20)
.spacing(15)
.width(Length::Fill)
.push("Please connect a hardware wallet")
.push(button::border(None, "Refresh").on_press(Message::Reload))
@ -547,9 +565,72 @@ pub fn sign_action<'a>(
)
.width(Length::Fill)
})
.spacing(20)
.width(Length::Fill)
.align_items(Alignment::Center),
)
.width(Length::Units(500))
.into()
}
pub fn update_spend_view<'a>(
psbt: String,
updated: &form::Value<String>,
error: Option<&Error>,
processing: bool,
) -> Element<'a, Message> {
Column::new()
.push(warn(error))
.push(card::simple(
Column::new()
.spacing(20)
.push(
Row::new()
.push(text("PSBT:").bold().width(Length::Fill))
.push(
button::border(Some(icon::clipboard_icon()), "Copy")
.on_press(Message::Clipboard(psbt)),
)
.align_items(Alignment::Center),
)
.push(separation().width(Length::Fill))
.push(
Column::new()
.spacing(10)
.push(text("Insert updated PSBT:").bold())
.push(
form::Form::new("PSBT", updated, move |msg| {
Message::ImportSpend(ImportSpendMessage::PsbtEdited(msg))
})
.warning("Please enter the correct base64 encoded PSBT")
.size(20)
.padding(10),
)
.push(Row::new().push(Space::with_width(Length::Fill)).push(
if updated.valid && !updated.value.is_empty() && !processing {
button::primary(None, "Update")
.on_press(Message::ImportSpend(ImportSpendMessage::Confirm))
} else if processing {
button::primary(None, "Processing...")
} else {
button::primary(None, "Update")
},
)),
),
))
.max_width(400)
.into()
}
pub fn update_spend_success_view<'a>() -> Element<'a, Message> {
Column::new()
.push(
card::simple(Container::new(
text("Spend transaction is updated").style(color::SUCCESS),
))
.padding(50),
)
.width(Length::Units(400))
.align_items(Alignment::Center)
.into()
}