From 98bacb9f4c88ad63c537b7819a437e4e17bdd0b5 Mon Sep 17 00:00:00 2001 From: pythcoiner Date: Mon, 7 Apr 2025 09:55:27 +0200 Subject: [PATCH] export: add import xpub feature --- liana-gui/src/app/state/export.rs | 12 ++++++++- liana-gui/src/export.rs | 43 ++++++++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/liana-gui/src/app/state/export.rs b/liana-gui/src/app/state/export.rs index 77eae207..046d8c2f 100644 --- a/liana-gui/src/app/state/export.rs +++ b/liana-gui/src/app/state/export.rs @@ -44,6 +44,7 @@ impl ExportModal { ImportExportType::Transactions => "Export Transactions", ImportExportType::ExportPsbt(_) => "Export PSBT", ImportExportType::ExportXpub(_) => "Export Xpub", + ImportExportType::ImportXpub(_) => "Import Xpub", ImportExportType::ExportBackup(_) => "Export Backup", ImportExportType::Descriptor(_) => "Export Descriptor", ImportExportType::ExportProcessBackup(..) | ImportExportType::ExportLabels => { @@ -63,7 +64,7 @@ impl ExportModal { format!("liana-txs-{date}.csv") } ImportExportType::ExportPsbt(_) => "psbt.psbt".into(), - ImportExportType::ExportXpub(_) => "liana.pub".into(), + ImportExportType::ExportXpub(_) | ImportExportType::ImportXpub(_) => "liana.pub".into(), ImportExportType::Descriptor(descriptor) => { let checksum = descriptor .to_string() @@ -129,6 +130,14 @@ impl ExportModal { } // TODO: forward PSBT } + Progress::Xpub(xpub_str) => { + if matches!(self.import_export_type, ImportExportType::ExportXpub(_)) { + self.state = ImportExportState::Ended; + } + return Task::perform(async {}, move |_| { + ImportExportMessage::Xpub(xpub_str.clone()).into() + }); + } Progress::Descriptor(_) => { if self.import_export_type == ImportExportType::ImportDescriptor { self.state = ImportExportState::Ended; @@ -216,6 +225,7 @@ impl ExportModal { } } ImportExportMessage::UpdateAliases(_) => { /* unexpected */ } + ImportExportMessage::Xpub(_) => { /* unexpected */ } } Task::none() } diff --git a/liana-gui/src/export.rs b/liana-gui/src/export.rs index fafc0262..54008e4c 100644 --- a/liana-gui/src/export.rs +++ b/liana-gui/src/export.rs @@ -15,7 +15,10 @@ use async_hwi::bitbox::api::btc::Fingerprint; use chrono::{DateTime, Duration, Utc}; use liana::{ descriptors::LianaDescriptor, - miniscript::bitcoin::{Amount, Network, Psbt, Txid}, + miniscript::{ + bitcoin::{Amount, Network, Psbt, Txid}, + DescriptorPublicKey, + }, }; use lianad::{ bip329::{error::ExportError, Labels}, @@ -80,6 +83,7 @@ pub enum ImportExportMessage { Overwrite, Ignore, UpdateAliases(HashMap), + Xpub(String), } impl From for view::Message { @@ -117,6 +121,8 @@ pub enum Error { Bip329Export(String), BackupImport(String), Backup(backup::Error), + ParseXpub, + XpubNetwork, } impl Display for Error { @@ -136,6 +142,8 @@ impl Display for Error { Error::Bip329Export(e) => write!(f, "Bip329Export: {e}"), Error::BackupImport(e) => write!(f, "BackupImport: {e}"), Error::Backup(e) => write!(f, "Backup: {e}"), + Error::ParseXpub => write!(f, "Fail to parse Xpub from file"), + Error::XpubNetwork => write!(f, "Xpub is for another network"), } } } @@ -155,6 +163,7 @@ pub enum ImportExportType { Descriptor(LianaDescriptor), ExportLabels, ImportPsbt, + ImportXpub(Network), ImportDescriptor, } @@ -170,6 +179,7 @@ impl ImportExportType { | ImportExportType::ExportLabels => "Export successful!", ImportExportType::ImportBackup(_, _) | ImportExportType::ImportPsbt + | ImportExportType::ImportXpub(_) | ImportExportType::WalletFromBackup | ImportExportType::ImportDescriptor => "Import successful", } @@ -231,6 +241,7 @@ pub enum Progress { None, Psbt(Psbt), Descriptor(LianaDescriptor), + Xpub(String), LabelsConflict(Sender), KeyAliasesConflict(Sender), UpdateAliases(HashMap), @@ -284,6 +295,7 @@ impl Export { } ImportExportType::ExportLabels => export_labels(&sender, daemon, path).await, ImportExportType::ImportPsbt => import_psbt(&sender, path).await, + ImportExportType::ImportXpub(network) => import_xpub(&sender, path, network).await, ImportExportType::ImportDescriptor => import_descriptor(&sender, path).await, ImportExportType::ExportBackup(str) => export_string(&sender, path, str).await, ImportExportType::ExportXpub(xpub_str) => export_string(&sender, path, xpub_str).await, @@ -596,6 +608,35 @@ pub async fn import_descriptor( Ok(()) } +pub async fn import_xpub( + sender: &UnboundedSender, + path: PathBuf, + network: Network, +) -> Result<(), Error> { + let mut file = File::open(path)?; + + let mut xpub_str = String::new(); + file.read_to_string(&mut xpub_str)?; + + if let Ok(DescriptorPublicKey::XPub(key)) = DescriptorPublicKey::from_str(&xpub_str) { + let valid = if network == Network::Bitcoin { + key.xkey.network == Network::Bitcoin.into() + } else { + key.xkey.network == Network::Testnet.into() + }; + if valid { + send_progress!(sender, Progress(100.0)); + send_progress!(sender, Xpub(xpub_str)); + } else { + return Err(Error::XpubNetwork); + } + } else { + return Err(Error::ParseXpub); + } + + Ok(()) +} + /// Import a backup in an already existing wallet: /// - Load backup from file /// - check if networks matches