gui: integrate import PSBT from file
This commit is contained in:
parent
9658cfde3f
commit
71684e7039
@ -155,12 +155,6 @@ impl ExportModal {
|
||||
self.error = Some(e.clone());
|
||||
}
|
||||
Progress::None => {}
|
||||
Progress::Psbt(_) => {
|
||||
if matches!(self.import_export_type, ImportExportType::ImportPsbt(_)) {
|
||||
self.state = ImportExportState::Ended;
|
||||
}
|
||||
// TODO: forward PSBT
|
||||
}
|
||||
Progress::Xpub(xpub_str) => {
|
||||
if matches!(self.import_export_type, ImportExportType::ExportXpub(_)) {
|
||||
self.state = ImportExportState::Ended;
|
||||
@ -181,6 +175,7 @@ impl ExportModal {
|
||||
});
|
||||
}
|
||||
Progress::WalletFromBackup(_) => {}
|
||||
Progress::Psbt(_) => {}
|
||||
},
|
||||
ImportExportMessage::TimedOut => {
|
||||
self.stop(ImportExportState::TimedOut);
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use iced::Subscription;
|
||||
@ -13,13 +12,10 @@ use liana::{
|
||||
use lianad::commands::CoinStatus;
|
||||
|
||||
use liana_ui::component::toast;
|
||||
use liana_ui::{
|
||||
component::{form, modal},
|
||||
widget::Element,
|
||||
};
|
||||
use liana_ui::{component::modal, widget::Element};
|
||||
|
||||
use crate::daemon::model::LabelsLoader;
|
||||
use crate::export::{ImportExportMessage, ImportExportType};
|
||||
use crate::export::{ImportExportMessage, ImportExportType, Progress};
|
||||
use crate::{
|
||||
app::{
|
||||
cache::Cache,
|
||||
@ -59,7 +55,6 @@ pub trait Modal {
|
||||
pub enum PsbtModal {
|
||||
Save(SaveModal),
|
||||
Sign(SignModal),
|
||||
Update(UpdateModal),
|
||||
Broadcast(BroadcastModal),
|
||||
Delete(DeleteModal),
|
||||
Export(ExportModal),
|
||||
@ -70,7 +65,6 @@ impl<'a> AsRef<dyn Modal + 'a> for PsbtModal {
|
||||
match &self {
|
||||
Self::Save(a) => a,
|
||||
Self::Sign(a) => a,
|
||||
Self::Update(a) => a,
|
||||
Self::Broadcast(a) => a,
|
||||
Self::Delete(a) => a,
|
||||
Self::Export(a) => a,
|
||||
@ -83,7 +77,6 @@ impl<'a> AsMut<dyn Modal + 'a> for PsbtModal {
|
||||
match self {
|
||||
Self::Save(a) => a,
|
||||
Self::Sign(a) => a,
|
||||
Self::Update(a) => a,
|
||||
Self::Broadcast(a) => a,
|
||||
Self::Delete(a) => a,
|
||||
Self::Export(a) => a,
|
||||
@ -150,6 +143,17 @@ impl PsbtState {
|
||||
return launch;
|
||||
}
|
||||
}
|
||||
Message::View(view::Message::ImportPsbt) => {
|
||||
if self.modal.is_none() {
|
||||
let modal = ExportModal::new(
|
||||
Some(daemon.clone()),
|
||||
ImportExportType::ImportPsbt(Some(self.tx.psbt.unsigned_tx.compute_txid())),
|
||||
);
|
||||
let launch = modal.launch(false);
|
||||
self.modal = Some(PsbtModal::Export(modal));
|
||||
return launch;
|
||||
}
|
||||
}
|
||||
Message::View(view::Message::ImportExport(ImportExportMessage::Close)) => {
|
||||
if matches!(self.modal, Some(PsbtModal::Export(_))) {
|
||||
self.modal = None;
|
||||
@ -188,12 +192,6 @@ impl PsbtState {
|
||||
self.modal = Some(PsbtModal::Sign(modal));
|
||||
return cmd;
|
||||
}
|
||||
Message::View(view::Message::Spend(view::SpendTxMessage::EditPsbt)) => {
|
||||
let modal = UpdateModal::new(self.wallet.clone(), self.tx.psbt.to_string());
|
||||
let cmd = modal.load(daemon);
|
||||
self.modal = Some(PsbtModal::Update(modal));
|
||||
return cmd;
|
||||
}
|
||||
Message::View(view::Message::Spend(view::SpendTxMessage::Broadcast)) => {
|
||||
let outpoints: Vec<_> = self.tx.coins.keys().cloned().collect();
|
||||
return Task::perform(
|
||||
@ -246,6 +244,16 @@ impl PsbtState {
|
||||
self.warning = Some(e);
|
||||
}
|
||||
},
|
||||
Message::Export(ImportExportMessage::Progress(Progress::Psbt(psbt))) => {
|
||||
merge_signatures(&mut self.tx.psbt, &psbt);
|
||||
self.tx.sigs = self
|
||||
.wallet
|
||||
.main_descriptor
|
||||
.partial_spend_info(&self.tx.psbt)
|
||||
// FIXME: we should not unwrap here, it seems importing a PSBT w/ an
|
||||
// non-owned input will trigger a panic
|
||||
.unwrap();
|
||||
}
|
||||
_ => {
|
||||
if let Some(modal) = self.modal.as_mut() {
|
||||
return modal.as_mut().update(daemon.clone(), message, &mut self.tx);
|
||||
@ -658,97 +666,6 @@ async fn sign_psbt(
|
||||
Ok(psbt)
|
||||
}
|
||||
|
||||
pub struct UpdateModal {
|
||||
wallet: Arc<Wallet>,
|
||||
psbt: String,
|
||||
updated: form::Value<String>,
|
||||
processing: bool,
|
||||
error: Option<Error>,
|
||||
success: bool,
|
||||
}
|
||||
|
||||
impl UpdateModal {
|
||||
pub fn new(wallet: Arc<Wallet>, psbt: String) -> Self {
|
||||
Self {
|
||||
wallet,
|
||||
psbt,
|
||||
updated: form::Value::default(),
|
||||
processing: false,
|
||||
error: None,
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Modal for UpdateModal {
|
||||
fn view<'a>(&'a self, content: Element<'a, view::Message>) -> Element<'a, view::Message> {
|
||||
modal::Modal::new(
|
||||
content,
|
||||
if self.success {
|
||||
view::psbt::update_spend_success_view()
|
||||
} else {
|
||||
view::psbt::update_spend_view(
|
||||
self.psbt.clone(),
|
||||
&self.updated,
|
||||
self.error.as_ref(),
|
||||
self.processing,
|
||||
)
|
||||
},
|
||||
)
|
||||
.on_blur(Some(view::Message::Spend(view::SpendTxMessage::Cancel)))
|
||||
.into()
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
daemon: Arc<dyn Daemon + Sync + Send>,
|
||||
message: Message,
|
||||
tx: &mut SpendTx,
|
||||
) -> Task<Message> {
|
||||
match message {
|
||||
Message::Updated(res) => {
|
||||
self.processing = false;
|
||||
match res {
|
||||
Ok(()) => {
|
||||
self.success = true;
|
||||
self.error = None;
|
||||
let psbt = Psbt::from_str(&self.updated.value).expect("Already checked");
|
||||
merge_signatures(&mut tx.psbt, &psbt);
|
||||
tx.sigs = self
|
||||
.wallet
|
||||
.main_descriptor
|
||||
.partial_spend_info(&tx.psbt)
|
||||
.unwrap();
|
||||
}
|
||||
Err(e) => self.error = e.into(),
|
||||
}
|
||||
}
|
||||
Message::View(view::Message::ImportSpend(view::ImportSpendMessage::PsbtEdited(s))) => {
|
||||
self.updated.value = s;
|
||||
if let Ok(psbt) = Psbt::from_str(&self.updated.value) {
|
||||
self.updated.valid =
|
||||
tx.psbt.unsigned_tx.compute_txid() == psbt.unsigned_tx.compute_txid();
|
||||
} else {
|
||||
self.updated.valid = false;
|
||||
}
|
||||
}
|
||||
Message::View(view::Message::ImportSpend(view::ImportSpendMessage::Confirm)) => {
|
||||
self.processing = true;
|
||||
self.error = None;
|
||||
if let Ok(updated) = Psbt::from_str(&self.updated.value) {
|
||||
return Task::perform(
|
||||
async move { daemon.update_spend_tx(&updated).await.map_err(|e| e.into()) },
|
||||
Message::Updated,
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@ -1,18 +1,14 @@
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use iced::{Subscription, Task};
|
||||
|
||||
use liana::miniscript::bitcoin::psbt::Psbt;
|
||||
use liana_ui::{
|
||||
component::{form, modal},
|
||||
widget::Element,
|
||||
};
|
||||
use liana_ui::widget::Element;
|
||||
|
||||
use super::{psbt, State};
|
||||
use super::{export::ExportModal, psbt, State};
|
||||
use crate::{
|
||||
app::{cache::Cache, error::Error, menu::Menu, message::Message, view, wallet::Wallet},
|
||||
daemon::{model::SpendTx, Daemon},
|
||||
export::{ImportExportMessage, ImportExportType},
|
||||
};
|
||||
|
||||
pub struct PsbtsPanel {
|
||||
@ -20,7 +16,7 @@ pub struct PsbtsPanel {
|
||||
selected_tx: Option<psbt::PsbtState>,
|
||||
spend_txs: Vec<SpendTx>,
|
||||
warning: Option<Error>,
|
||||
import_tx: Option<ImportPsbtModal>,
|
||||
modal: Option<ExportModal>,
|
||||
}
|
||||
|
||||
impl PsbtsPanel {
|
||||
@ -30,7 +26,7 @@ impl PsbtsPanel {
|
||||
spend_txs: Vec::new(),
|
||||
warning: None,
|
||||
selected_tx: None,
|
||||
import_tx: None,
|
||||
modal: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,7 +34,7 @@ impl PsbtsPanel {
|
||||
let psbt_state = psbt::PsbtState::new(self.wallet.clone(), spend_tx, true);
|
||||
self.selected_tx = Some(psbt_state);
|
||||
self.warning = None;
|
||||
self.import_tx = None;
|
||||
self.modal = None;
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,14 +49,8 @@ impl State for PsbtsPanel {
|
||||
self.warning.as_ref(),
|
||||
view::psbts::psbts_view(&self.spend_txs),
|
||||
);
|
||||
if let Some(import_tx) = &self.import_tx {
|
||||
modal::Modal::new(list_view, import_tx.view())
|
||||
.on_blur(if import_tx.processing {
|
||||
None
|
||||
} else {
|
||||
Some(view::Message::Close)
|
||||
})
|
||||
.into()
|
||||
if let Some(modal) = &self.modal {
|
||||
modal.view(list_view)
|
||||
} else {
|
||||
list_view
|
||||
}
|
||||
@ -99,9 +89,32 @@ impl State for PsbtsPanel {
|
||||
}
|
||||
}
|
||||
},
|
||||
Message::View(view::Message::ImportSpend(view::ImportSpendMessage::Import)) => {
|
||||
if self.import_tx.is_none() {
|
||||
self.import_tx = Some(ImportPsbtModal::new());
|
||||
Message::View(view::Message::ImportPsbt) => {
|
||||
if let Some(tx) = &mut self.selected_tx {
|
||||
return tx.update(daemon, cache, message);
|
||||
} else if self.modal.is_none() {
|
||||
let modal =
|
||||
ExportModal::new(Some(daemon.clone()), ImportExportType::ImportPsbt(None));
|
||||
let launch = modal.launch(false);
|
||||
self.modal = Some(modal);
|
||||
return launch;
|
||||
}
|
||||
}
|
||||
Message::View(view::Message::ImportExport(ImportExportMessage::Close)) => {
|
||||
if let Some(tx) = &mut self.selected_tx {
|
||||
return tx.update(daemon, cache, message);
|
||||
} else if self.modal.is_some() {
|
||||
self.modal = None;
|
||||
return Task::perform(async {}, |_| Message::View(view::Message::Reload));
|
||||
}
|
||||
}
|
||||
Message::View(view::Message::ImportExport(m)) => {
|
||||
let m = m.clone();
|
||||
if let Some(tx) = &mut self.selected_tx {
|
||||
let message = Message::View(view::Message::ImportExport(m));
|
||||
return tx.update(daemon, cache, message);
|
||||
} else if let Some(modal) = self.modal.as_mut() {
|
||||
return modal.update(m.clone());
|
||||
}
|
||||
}
|
||||
Message::View(view::Message::Select(i)) => {
|
||||
@ -116,10 +129,6 @@ impl State for PsbtsPanel {
|
||||
if let Some(tx) = &mut self.selected_tx {
|
||||
return tx.update(daemon, cache, message);
|
||||
}
|
||||
|
||||
if let Some(import_tx) = &mut self.import_tx {
|
||||
return import_tx.update(daemon, cache, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
@ -128,6 +137,17 @@ impl State for PsbtsPanel {
|
||||
fn subscription(&self) -> Subscription<Message> {
|
||||
if let Some(psbt) = &self.selected_tx {
|
||||
psbt.subscription()
|
||||
} else if let Some(modal) = &self.modal {
|
||||
modal
|
||||
.subscription()
|
||||
.map(|s| {
|
||||
s.map(|m| {
|
||||
Message::View(view::Message::ImportExport(ImportExportMessage::Progress(
|
||||
m,
|
||||
)))
|
||||
})
|
||||
})
|
||||
.unwrap_or(Subscription::none())
|
||||
} else {
|
||||
Subscription::none()
|
||||
}
|
||||
@ -140,7 +160,7 @@ impl State for PsbtsPanel {
|
||||
) -> Task<Message> {
|
||||
self.wallet = wallet;
|
||||
self.selected_tx = None;
|
||||
self.import_tx = None;
|
||||
self.modal = None;
|
||||
let daemon = daemon.clone();
|
||||
Task::perform(
|
||||
async move {
|
||||
@ -159,74 +179,3 @@ impl From<PsbtsPanel> for Box<dyn State> {
|
||||
Box::new(s)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ImportPsbtModal {
|
||||
imported: form::Value<String>,
|
||||
processing: bool,
|
||||
error: Option<Error>,
|
||||
success: bool,
|
||||
}
|
||||
|
||||
impl ImportPsbtModal {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
imported: form::Value::default(),
|
||||
processing: false,
|
||||
error: None,
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ImportPsbtModal {
|
||||
fn view<'a>(&self) -> Element<'a, view::Message> {
|
||||
if self.success {
|
||||
view::psbts::import_psbt_success_view()
|
||||
} else {
|
||||
view::psbts::import_psbt_view(&self.imported, self.error.as_ref(), self.processing)
|
||||
}
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
daemon: Arc<dyn Daemon + Sync + Send>,
|
||||
_cache: &Cache,
|
||||
message: Message,
|
||||
) -> Task<Message> {
|
||||
match message {
|
||||
Message::Updated(res) => {
|
||||
self.processing = false;
|
||||
match res {
|
||||
Ok(()) => {
|
||||
self.success = true;
|
||||
self.error = None;
|
||||
}
|
||||
Err(e) => self.error = e.into(),
|
||||
}
|
||||
}
|
||||
Message::View(view::Message::ImportSpend(view::ImportSpendMessage::PsbtEdited(s))) => {
|
||||
self.imported.value = s;
|
||||
self.imported.valid = Psbt::from_str(&self.imported.value).ok().is_some();
|
||||
}
|
||||
Message::View(view::Message::ImportSpend(view::ImportSpendMessage::Confirm)) => {
|
||||
if self.imported.valid {
|
||||
self.processing = true;
|
||||
self.error = None;
|
||||
let imported = Psbt::from_str(&self.imported.value).expect("Already checked");
|
||||
return Task::perform(
|
||||
async move {
|
||||
daemon
|
||||
.update_spend_tx(&imported)
|
||||
.await
|
||||
.map_err(|e| e.into())
|
||||
},
|
||||
Message::Updated,
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,6 +26,7 @@ pub enum Message {
|
||||
ImportExport(ImportExportMessage),
|
||||
HideRescanWarning,
|
||||
ExportPsbt,
|
||||
ImportPsbt,
|
||||
}
|
||||
|
||||
impl Close for Message {
|
||||
|
||||
@ -346,10 +346,10 @@ pub fn spend_overview_view<'a>(
|
||||
)
|
||||
.push(
|
||||
button::secondary(
|
||||
Some(icon::import_icon()),
|
||||
"Update",
|
||||
Some(icon::restore_icon()),
|
||||
"Import",
|
||||
)
|
||||
.on_press(Message::Spend(SpendTxMessage::EditPsbt)),
|
||||
.on_press(Message::ImportPsbt),
|
||||
),
|
||||
)
|
||||
.align_y(Alignment::Center),
|
||||
|
||||
@ -68,8 +68,8 @@ pub fn psbts_view(spend_txs: &[SpendTx]) -> Element<'_, Message> {
|
||||
.spacing(10)
|
||||
.push(Container::new(h3("PSBTs")).width(Length::Fill))
|
||||
.push(
|
||||
button::secondary(Some(icon::import_icon()), "Import")
|
||||
.on_press(Message::ImportSpend(ImportSpendMessage::Import)),
|
||||
button::secondary(Some(icon::restore_icon()), "Import")
|
||||
.on_press(Message::ImportPsbt),
|
||||
)
|
||||
.push(
|
||||
button::secondary(Some(icon::plus_icon()), "New")
|
||||
|
||||
@ -605,9 +605,9 @@ pub async fn import_psbt(
|
||||
}
|
||||
|
||||
daemon.update_spend_tx(&psbt).await?;
|
||||
send_progress!(sender, Psbt(psbt));
|
||||
|
||||
send_progress!(sender, Progress(100.0));
|
||||
send_progress!(sender, Psbt(psbt));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user