installer: add wallet export feature at descriptor backup step

This commit is contained in:
pythcoiner 2025-03-05 06:07:51 +01:00
parent b0e560c0b5
commit 68bb742755
No known key found for this signature in database
GPG Key ID: C1048AEEDF303B88
9 changed files with 108 additions and 22 deletions

View File

@ -69,7 +69,7 @@ impl ExportModal {
}
ImportExportType::ImportPsbt => "psbt.psbt".into(),
ImportExportType::ImportDescriptor => "descriptor.descriptor".into(),
ImportExportType::ExportLabels => format!("liana-labels-{date}.csv"),
ImportExportType::ExportLabels => format!("liana-labels-{date}.jsonl"),
ImportExportType::ExportBackup(_) => format!("liana-backup-{date}.json"),
}
}
@ -95,8 +95,12 @@ impl ExportModal {
self.state = ImportExportState::Progress(p);
}
}
Progress::Finished | Progress::Ended => self.state = ImportExportState::Ended,
Progress::Error(e) => self.error = Some(e),
Progress::Finished | Progress::Ended => {
self.state = ImportExportState::Ended;
}
Progress::Error(e) => {
self.error = Some(e);
}
Progress::None => {}
Progress::Psbt(_) => {
if self.import_export_type == ImportExportType::ImportPsbt {

View File

@ -271,13 +271,15 @@ impl State for ImportExportSettingsState {
},
|s| {
Message::View(view::Message::Settings(
view::SettingsMessage::ExportBackup(s),
view::SettingsMessage::ExportBackup(Ok(s)),
))
},
);
}
}
Message::View(view::Message::Settings(view::SettingsMessage::ExportBackup(backup))) => {
Message::View(view::Message::Settings(view::SettingsMessage::ExportBackup(Ok(
backup,
)))) => {
let modal = ExportModal::new(Some(daemon), ImportExportType::ExportBackup(backup));
launch!(self, modal);
}

View File

@ -240,7 +240,7 @@ impl State for WalletSettingsState {
},
|s| {
Message::View(view::Message::Settings(
view::SettingsMessage::ExportBackup(s),
view::SettingsMessage::ExportBackup(Ok(s)),
))
},
)
@ -248,7 +248,9 @@ impl State for WalletSettingsState {
Task::none()
}
}
Message::View(view::Message::Settings(view::SettingsMessage::ExportBackup(backup))) => {
Message::View(view::Message::Settings(view::SettingsMessage::ExportBackup(Ok(
backup,
)))) => {
if self.modal.is_none() {
let modal =
// ExportModal::new(Some(daemon), ImportExportType::ExportBackup(backup));

View File

@ -1,4 +1,4 @@
use crate::{app::menu::Menu, export::ImportExportMessage, node::bitcoind::RpcAuthType};
use crate::{app::menu::Menu, backup, export::ImportExportMessage, node::bitcoind::RpcAuthType};
use liana::miniscript::bitcoin::{bip32::Fingerprint, OutPoint};
pub trait Close {
@ -89,7 +89,7 @@ pub enum SettingsMessage {
ExportTransactions,
ExportLabels,
ExportWallet,
ExportBackup(String),
ExportBackup(Result<String, backup::Error>),
ImportWallet,
AboutSection,
RegisterWallet,

View File

@ -46,19 +46,19 @@ pub struct Backup {
pub proprietary: serde_json::Map<String, serde_json::Value>,
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum Error {
DescriptorMissing,
NotSingleWallet,
Json,
SettingsFromFile,
Daemon(DaemonError),
Daemon(String),
TxTimeMissing,
}
impl From<DaemonError> for Error {
fn from(value: DaemonError) -> Self {
Error::Daemon(value)
Error::Daemon(value.to_string())
}
}

View File

@ -8,6 +8,7 @@ use super::{context, Error};
use crate::{
app::settings::ProviderKey,
app::view::Close,
backup,
download::{DownloadError, Progress},
export::ImportExportMessage,
hw::HardwareWalletMessage,
@ -51,6 +52,8 @@ pub enum Message {
RedeemNextKey,
KeyRedeemed(ProviderKey, Result<(), services::Error>),
AllKeysRedeemed,
BackupWallet,
ExportWallet(Result<String, backup::Error>),
ImportExport(ImportExportMessage),
}
@ -62,7 +65,7 @@ impl Close for Message {
impl From<ImportExportMessage> for Message {
fn from(value: ImportExportMessage) -> Self {
Self::ImportExport(value)
Message::ImportExport(value)
}
}

View File

@ -25,6 +25,7 @@ use crate::{
settings::{self as gui_settings, AuthConfig, Settings, SettingsError, WalletSetting},
wallet::wallet_name,
},
backup,
daemon::DaemonError,
datadir::create_directory,
hw::{HardwareWalletConfig, HardwareWallets},
@ -730,6 +731,7 @@ pub enum Error {
CannotGetAvailablePort(String),
Unexpected(String),
HardwareWallet(async_hwi::Error),
Backup(backup::Error),
}
impl From<jsonrpc::simple_http::Error> for Error {
@ -783,6 +785,7 @@ impl std::fmt::Display for Error {
Self::CannotCreateFile(e) => write!(f, "Failed to create file: {}", e),
Self::Unexpected(e) => write!(f, "Unexpected: {}", e),
Self::HardwareWallet(e) => write!(f, "Hardware Wallet: {}", e),
Self::Backup(e) => write!(f, "Backup: {:?}", e),
}
}
}

View File

@ -16,7 +16,9 @@ use liana_ui::{component::form, widget::Element};
use async_hwi::DeviceKind;
use crate::{
app::{settings::KeySetting, wallet::wallet_name},
app::{settings::KeySetting, state::export::ExportModal, wallet::wallet_name},
backup::{self, Backup},
export::{ImportExportMessage, ImportExportType},
hw::{HardwareWallet, HardwareWallets},
installer::{
message::{self, Message},
@ -292,16 +294,71 @@ pub struct BackupDescriptor {
done: bool,
descriptor: Option<LianaDescriptor>,
keys: HashMap<Fingerprint, KeySetting>,
modal: Option<ExportModal>,
error: Option<Error>,
context: Option<Context>,
}
impl Step for BackupDescriptor {
fn subscription(&self, _hws: &HardwareWallets) -> Subscription<Message> {
if let Some(modal) = &self.modal {
if let Some(sub) = modal.subscription() {
sub.map(|m| Message::ImportExport(ImportExportMessage::Progress(m)))
} else {
Subscription::none()
}
} else {
Subscription::none()
}
}
fn update(&mut self, _hws: &mut HardwareWallets, message: Message) -> Task<Message> {
if let Message::UserActionDone(done) = message {
self.done = done;
match message {
Message::ImportExport(ImportExportMessage::Close) => {
self.modal = None;
}
Message::ImportExport(m) => {
if let Some(modal) = self.modal.as_mut() {
let task: Task<Message> = modal.update(m);
return task;
};
}
Message::BackupWallet => {
if let (None, Some(ctx)) = (&self.modal, self.context.as_ref()) {
let ctx = ctx.clone();
return Task::perform(
async move {
let backup = Backup::from_installer(ctx, true).await?;
serde_json::to_string_pretty(&backup).map_err(|_| backup::Error::Json)
},
Message::ExportWallet,
);
}
}
Message::ExportWallet(str) => {
if self.modal.is_none() {
let str = match str {
Ok(s) => s,
Err(e) => {
tracing::error!("{e:?}");
self.error = Some(Error::Backup(e));
return Task::none();
}
};
let modal = ExportModal::new(None, ImportExportType::ExportBackup(str));
let launch = modal.launch();
self.modal = Some(modal);
return launch;
}
}
Message::UserActionDone(done) => {
self.done = done;
}
_ => {}
}
Task::none()
}
fn load_context(&mut self, ctx: &Context) {
self.context = Some(ctx.clone());
if self.descriptor != ctx.descriptor {
self.descriptor.clone_from(&ctx.descriptor);
self.done = false;
@ -318,13 +375,19 @@ impl Step for BackupDescriptor {
progress: (usize, usize),
email: Option<&'a str>,
) -> Element<Message> {
view::backup_descriptor(
let content = view::backup_descriptor(
progress,
email,
self.descriptor.as_ref().expect("Must be a descriptor"),
&self.keys,
self.error.as_ref(),
self.done,
)
);
if let Some(modal) = &self.modal {
modal.view(content)
} else {
content
}
}
}

View File

@ -689,6 +689,7 @@ pub fn backup_descriptor<'a>(
email: Option<&'a str>,
descriptor: &'a LianaDescriptor,
keys: &'a HashMap<Fingerprint, settings::KeySetting>,
error: Option<&Error>,
done: bool,
) -> Element<'a, Message> {
layout(
@ -724,6 +725,7 @@ pub fn backup_descriptor<'a>(
))
.max_width(1000),
)
.push_maybe(error.map(|e| card::error("Failed to export backup", e.to_string())))
.push(
card::simple(
Column::new()
@ -741,10 +743,17 @@ pub fn backup_descriptor<'a>(
),
)
.push(
Row::new().push(Column::new().width(Length::Fill)).push(
button::secondary(Some(icon::clipboard_icon()), "Copy")
.on_press(Message::Clibpboard(descriptor.to_string())),
),
Row::new()
.push(Space::with_width(Length::Fill))
.push(
button::secondary(Some(icon::wallet_icon()), "Backup")
.on_press(Message::BackupWallet),
)
.push(Space::with_width(10))
.push(
button::secondary(Some(icon::clipboard_icon()), "Copy")
.on_press(Message::Clibpboard(descriptor.to_string())),
),
)
.spacing(10),
)