Merge #1644: Add Import/Export Xpub feature
0b2ebd3aaad945afdf8b4dea4931179626663b89 export: typo 'Fail to' => 'Failed to' (pythcoiner) 8dd16fb586b4edc87d602b0a1d6ad396fe5340b3 export: trim string read from files (pythcoiner) 541c63419b1a39d0159ca87492360037e2119fbb installer: integrate import xpub feature (pythcoiner) 98bacb9f4c88ad63c537b7819a437e4e17bdd0b5 export: add import xpub feature (pythcoiner) d7e388707b3368e12b73b578524b8e0c667ddb92 installer: integrate export xpub feature (pythcoiner) 986a982d8e7148b51bf20e4913502bfa1ce1ffe6 export: add export xpub feature (pythcoiner) Pull request description: - [x] Add export feature to `export` - [x] Installer: Export xpub to a file instead copying to clipboard  - [x] Add import feature to `export` - [x] Installer: Add import Xpub feature at descriptor creation step before importing key:  after importing key:  closes #1633 ACKs for top commit: edouardparis: ACK 0b2ebd3aaad945afdf8b4dea4931179626663b89 Tree-SHA512: 44540efd25525a6489b27b1a928bac1c94e06720c5ba82e1eeada09abdb967db3f0ec8f2e404274818d1720efc508e19c8d9a5002f1e047c0335c8b8844ef7e1
This commit is contained in:
commit
896407aba3
@ -43,6 +43,8 @@ impl ExportModal {
|
||||
match self.import_export_type {
|
||||
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 => {
|
||||
@ -62,6 +64,7 @@ impl ExportModal {
|
||||
format!("liana-txs-{date}.csv")
|
||||
}
|
||||
ImportExportType::ExportPsbt(_) => "psbt.psbt".into(),
|
||||
ImportExportType::ExportXpub(_) | ImportExportType::ImportXpub(_) => "liana.pub".into(),
|
||||
ImportExportType::Descriptor(descriptor) => {
|
||||
let checksum = descriptor
|
||||
.to_string()
|
||||
@ -127,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;
|
||||
@ -214,6 +225,7 @@ impl ExportModal {
|
||||
}
|
||||
}
|
||||
ImportExportMessage::UpdateAliases(_) => { /* unexpected */ }
|
||||
ImportExportMessage::Xpub(_) => { /* unexpected */ }
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ impl From<&Error> for WarningMessage {
|
||||
Error::Desc(e) => WarningMessage(format!("Descriptor analysis error: '{}'.", e)),
|
||||
Error::Spend(e) => WarningMessage(format!("Spend creation error: '{}'.", e)),
|
||||
Error::ImportExport(e) => WarningMessage(format!("{e}")),
|
||||
Error::RestoreBackup(e) => WarningMessage(format!("Fail to restore backup: {e}")),
|
||||
Error::RestoreBackup(e) => WarningMessage(format!("Failed to restore backup: {e}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<Fingerprint, settings::KeySetting>),
|
||||
Xpub(String),
|
||||
}
|
||||
|
||||
impl From<ImportExportMessage> 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, "Failed to parse Xpub from file"),
|
||||
Error::XpubNetwork => write!(f, "Xpub is for another network"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -144,6 +152,7 @@ impl Display for Error {
|
||||
pub enum ImportExportType {
|
||||
Transactions,
|
||||
ExportPsbt(String),
|
||||
ExportXpub(String),
|
||||
ExportBackup(String),
|
||||
ExportProcessBackup(PathBuf, Network, Arc<Config>, Arc<Wallet>),
|
||||
ImportBackup(
|
||||
@ -154,6 +163,7 @@ pub enum ImportExportType {
|
||||
Descriptor(LianaDescriptor),
|
||||
ExportLabels,
|
||||
ImportPsbt,
|
||||
ImportXpub(Network),
|
||||
ImportDescriptor,
|
||||
}
|
||||
|
||||
@ -165,9 +175,11 @@ impl ImportExportType {
|
||||
| ImportExportType::ExportBackup(_)
|
||||
| ImportExportType::Descriptor(_)
|
||||
| ImportExportType::ExportProcessBackup(..)
|
||||
| ImportExportType::ExportXpub(_)
|
||||
| ImportExportType::ExportLabels => "Export successful!",
|
||||
ImportExportType::ImportBackup(_, _)
|
||||
| ImportExportType::ImportPsbt
|
||||
| ImportExportType::ImportXpub(_)
|
||||
| ImportExportType::WalletFromBackup
|
||||
| ImportExportType::ImportDescriptor => "Import successful",
|
||||
}
|
||||
@ -229,6 +241,7 @@ pub enum Progress {
|
||||
None,
|
||||
Psbt(Psbt),
|
||||
Descriptor(LianaDescriptor),
|
||||
Xpub(String),
|
||||
LabelsConflict(Sender<bool>),
|
||||
KeyAliasesConflict(Sender<bool>),
|
||||
UpdateAliases(HashMap<Fingerprint, settings::KeySetting>),
|
||||
@ -282,8 +295,10 @@ 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,
|
||||
ImportExportType::ExportProcessBackup(datadir, network, config, wallet) => {
|
||||
app_backup_export(
|
||||
datadir,
|
||||
@ -556,10 +571,10 @@ pub async fn export_descriptor(
|
||||
pub async fn export_string(
|
||||
sender: &UnboundedSender<Progress>,
|
||||
path: PathBuf,
|
||||
psbt: String,
|
||||
str: String,
|
||||
) -> Result<(), Error> {
|
||||
let mut file = open_file_write(&path).await?;
|
||||
file.write_all(psbt.as_bytes())?;
|
||||
file.write_all(str.as_bytes())?;
|
||||
send_progress!(sender, Progress(100.0));
|
||||
send_progress!(sender, Ended);
|
||||
Ok(())
|
||||
@ -570,6 +585,7 @@ pub async fn import_psbt(sender: &UnboundedSender<Progress>, path: PathBuf) -> R
|
||||
|
||||
let mut psbt_str = String::new();
|
||||
file.read_to_string(&mut psbt_str)?;
|
||||
psbt_str = psbt_str.trim().to_string();
|
||||
|
||||
let psbt = Psbt::from_str(&psbt_str).map_err(|_| Error::ParsePsbt)?;
|
||||
|
||||
@ -586,13 +602,45 @@ pub async fn import_descriptor(
|
||||
|
||||
let mut descr_str = String::new();
|
||||
file.read_to_string(&mut descr_str)?;
|
||||
let descriptor = LianaDescriptor::from_str(&descr_str).map_err(|_| Error::ParseDescriptor)?;
|
||||
let descr_str = descr_str.trim();
|
||||
|
||||
let descriptor = LianaDescriptor::from_str(descr_str).map_err(|_| Error::ParseDescriptor)?;
|
||||
|
||||
send_progress!(sender, Progress(100.0));
|
||||
send_progress!(sender, Descriptor(descriptor));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn import_xpub(
|
||||
sender: &UnboundedSender<Progress>,
|
||||
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)?;
|
||||
let xpub_str = xpub_str.trim().to_string();
|
||||
|
||||
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
|
||||
@ -623,6 +671,7 @@ pub async fn import_backup(
|
||||
|
||||
let mut backup_str = String::new();
|
||||
file.read_to_string(&mut backup_str)?;
|
||||
backup_str = backup_str.trim().to_string();
|
||||
|
||||
let backup: Result<Backup, _> = serde_json::from_str(&backup_str);
|
||||
let backup = match backup {
|
||||
@ -687,7 +736,7 @@ pub async fn import_backup(
|
||||
let db_labels = match daemon.get_labels_bip329(0, u32::MAX).await {
|
||||
Ok(l) => l,
|
||||
Err(_) => {
|
||||
return Err(Error::BackupImport("Fail to dump DB labels".into()));
|
||||
return Err(Error::BackupImport("Failed to dump DB labels".into()));
|
||||
}
|
||||
};
|
||||
|
||||
@ -710,7 +759,7 @@ pub async fn import_backup(
|
||||
write_labels = match ack_receiver.recv().await {
|
||||
Some(b) => b,
|
||||
None => {
|
||||
return Err(Error::BackupImport("Fail to receive labels ACK".into()));
|
||||
return Err(Error::BackupImport("Failed to receive labels ACK".into()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -724,11 +773,11 @@ pub async fn import_backup(
|
||||
Some(c) => match &c.data_dir {
|
||||
Some(dd) => dd,
|
||||
None => {
|
||||
return Err(Error::BackupImport("Fail to get Daemon config".into()));
|
||||
return Err(Error::BackupImport("Failed to get Daemon config".into()));
|
||||
}
|
||||
},
|
||||
None => {
|
||||
return Err(Error::BackupImport("Fail to get Daemon config".into()));
|
||||
return Err(Error::BackupImport("Failed to get Daemon config".into()));
|
||||
}
|
||||
};
|
||||
|
||||
@ -738,7 +787,7 @@ pub async fn import_backup(
|
||||
let settings = match Settings::from_file(datadir.to_path_buf(), network) {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
return Err(Error::BackupImport("Fail to get App Settings".into()));
|
||||
return Err(Error::BackupImport("Failed to get App Settings".into()));
|
||||
}
|
||||
};
|
||||
|
||||
@ -776,7 +825,7 @@ pub async fn import_backup(
|
||||
write_aliases = match ack_receiver.recv().await {
|
||||
Some(a) => a,
|
||||
None => {
|
||||
return Err(Error::BackupImport("Fail to receive aliases ACK".into()));
|
||||
return Err(Error::BackupImport("Failed to receive aliases ACK".into()));
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -797,7 +846,7 @@ pub async fn import_backup(
|
||||
|
||||
if daemon.update_deriv_indexes(receive, change).await.is_err() {
|
||||
return Err(Error::BackupImport(
|
||||
"Fail to update derivation indexes".into(),
|
||||
"Failed to update derivation indexes".into(),
|
||||
));
|
||||
}
|
||||
|
||||
@ -809,7 +858,7 @@ pub async fn import_backup(
|
||||
psbts.push(p);
|
||||
}
|
||||
Err(_) => {
|
||||
return Err(Error::BackupImport("Fail to parse PSBT".into()));
|
||||
return Err(Error::BackupImport("Failed to parse PSBT".into()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -817,7 +866,7 @@ pub async fn import_backup(
|
||||
// import PSBTs
|
||||
for psbt in psbts {
|
||||
if daemon.update_spend_tx(&psbt).await.is_err() {
|
||||
return Err(Error::BackupImport("Fail to store PSBT".into()));
|
||||
return Err(Error::BackupImport("Failed to store PSBT".into()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -834,7 +883,7 @@ pub async fn import_backup(
|
||||
})
|
||||
.collect();
|
||||
if daemon.update_labels(&labels).await.is_err() {
|
||||
return Err(Error::BackupImport("Fail to import labels".into()));
|
||||
return Err(Error::BackupImport("Failed to import labels".into()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -855,7 +904,7 @@ pub async fn import_backup(
|
||||
settings.wallets.get_mut(0).expect("already checked").keys =
|
||||
settings_aliases.clone().into_values().collect();
|
||||
if settings.to_file(datadir.to_path_buf(), network).is_err() {
|
||||
return Err(Error::BackupImport("Fail to import keys aliases".into()));
|
||||
return Err(Error::BackupImport("Failed to import keys aliases".into()));
|
||||
} else {
|
||||
// Update wallet state
|
||||
send_progress!(sender, UpdateAliases(settings_aliases));
|
||||
@ -927,6 +976,7 @@ pub async fn wallet_from_backup(
|
||||
|
||||
let mut backup_str = String::new();
|
||||
file.read_to_string(&mut backup_str)?;
|
||||
backup_str = backup_str.trim().to_string();
|
||||
|
||||
let backup: Result<Backup, _> = serde_json::from_str(&backup_str);
|
||||
let backup = match backup {
|
||||
@ -1087,7 +1137,7 @@ pub async fn import_backup_at_launch(
|
||||
// import PSBTs
|
||||
for psbt in psbts {
|
||||
if let Err(e) = daemon.update_spend_tx(&psbt).await {
|
||||
tracing::error!("Fail to restore PSBT: {e}")
|
||||
tracing::error!("Failed to restore PSBT: {e}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -56,6 +56,7 @@ pub enum Message {
|
||||
AllKeysRedeemed,
|
||||
BackupWallet,
|
||||
ExportWallet(Result<String, backup::Error>),
|
||||
ExportXpub(String),
|
||||
ImportExport(ImportExportMessage),
|
||||
ImportBackup,
|
||||
WalletFromBackup((HashMap<Fingerprint, settings::KeySetting>, Backup)),
|
||||
@ -179,6 +180,7 @@ pub enum ImportKeyModal {
|
||||
TokenEdited(String),
|
||||
ConfirmToken,
|
||||
SelectKey(usize),
|
||||
ImportXpub(Network),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
@ -14,6 +14,8 @@ use liana::miniscript::{
|
||||
|
||||
use liana_ui::{component::form, widget::Element};
|
||||
|
||||
use crate::app::state::export::ExportModal;
|
||||
use crate::export::{ImportExportMessage, ImportExportType};
|
||||
use crate::{
|
||||
app::settings::ProviderKey,
|
||||
hw::{HardwareWallet, HardwareWallets},
|
||||
@ -86,6 +88,7 @@ pub struct EditXpubModal {
|
||||
hot_signer: Arc<Mutex<Signer>>,
|
||||
hot_signer_fingerprint: Fingerprint,
|
||||
chosen_signer: Option<Key>,
|
||||
modal: Option<ExportModal>,
|
||||
}
|
||||
|
||||
impl EditXpubModal {
|
||||
@ -135,6 +138,7 @@ impl EditXpubModal {
|
||||
hot_signer_fingerprint,
|
||||
hot_signer,
|
||||
duplicate_master_fg: false,
|
||||
modal: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -243,6 +247,28 @@ impl super::DescriptorEditModal for EditXpubModal {
|
||||
.unwrap_or_default();
|
||||
self.form_name.valid = true;
|
||||
}
|
||||
Message::ImportExport(import_msg) => match import_msg {
|
||||
ImportExportMessage::Close => {
|
||||
if self.modal.is_some() {
|
||||
self.modal = None;
|
||||
}
|
||||
}
|
||||
ImportExportMessage::Xpub(xpub_str) => {
|
||||
if self.modal.is_some() {
|
||||
self.modal = None;
|
||||
return Task::perform(async move { xpub_str }, |xpub_str| {
|
||||
Message::DefineDescriptor(message::DefineDescriptor::KeyModal(
|
||||
message::ImportKeyModal::XPubEdited(xpub_str),
|
||||
))
|
||||
});
|
||||
}
|
||||
}
|
||||
m => {
|
||||
if let Some(modal) = self.modal.as_mut() {
|
||||
return modal.update(m);
|
||||
}
|
||||
}
|
||||
},
|
||||
Message::DefineDescriptor(message::DefineDescriptor::KeyModal(msg)) => match msg {
|
||||
message::ImportKeyModal::FetchedKey(res) => {
|
||||
self.processing = false;
|
||||
@ -323,6 +349,14 @@ impl super::DescriptorEditModal for EditXpubModal {
|
||||
self.form_token.valid = s.is_empty() || self.form_token_warning.is_none();
|
||||
self.form_token.value = s;
|
||||
}
|
||||
message::ImportKeyModal::ImportXpub(network) => {
|
||||
if self.modal.is_none() {
|
||||
let modal = ExportModal::new(None, ImportExportType::ImportXpub(network));
|
||||
let launch = modal.launch(false);
|
||||
self.modal = Some(modal);
|
||||
return launch;
|
||||
}
|
||||
}
|
||||
message::ImportKeyModal::XPubEdited(s) => {
|
||||
self.chosen_signer = None;
|
||||
if let Ok(DescriptorPublicKey::XPub(key)) = DescriptorPublicKey::from_str(&s) {
|
||||
@ -416,7 +450,14 @@ impl super::DescriptorEditModal for EditXpubModal {
|
||||
}
|
||||
|
||||
fn subscription(&self, hws: &HardwareWallets) -> Subscription<Message> {
|
||||
hws.refresh().map(Message::HardwareWallets)
|
||||
let hw = hws.refresh().map(Message::HardwareWallets);
|
||||
if let Some(modal) = self.modal.as_ref() {
|
||||
if let Some(sub) = modal.subscription() {
|
||||
let import = sub.map(|m| Message::ImportExport(ImportExportMessage::Progress(m)));
|
||||
return Subscription::batch(vec![hw, import]);
|
||||
}
|
||||
}
|
||||
hw
|
||||
}
|
||||
|
||||
fn view<'a>(&'a self, hws: &'a HardwareWallets) -> Element<'a, Message> {
|
||||
@ -436,7 +477,7 @@ impl super::DescriptorEditModal for EditXpubModal {
|
||||
})
|
||||
.collect();
|
||||
let chosen_signer = self.chosen_signer.as_ref().map(|s| s.fingerprint);
|
||||
view::editor::edit_key_modal(
|
||||
let content = view::editor::edit_key_modal(
|
||||
"Set your key",
|
||||
self.network,
|
||||
self.path_kind,
|
||||
@ -512,7 +553,12 @@ impl super::DescriptorEditModal for EditXpubModal {
|
||||
self.form_token_warning.as_ref(),
|
||||
self.form_key_source_kind.as_ref(),
|
||||
self.duplicate_master_fg,
|
||||
)
|
||||
);
|
||||
if let Some(modal) = &self.modal {
|
||||
modal.view(content)
|
||||
} else {
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -9,6 +9,8 @@ use liana::miniscript::bitcoin::{
|
||||
use liana_ui::widget::Element;
|
||||
|
||||
use crate::{
|
||||
app::state::export::ExportModal,
|
||||
export::{ImportExportMessage, ImportExportType},
|
||||
hw::{HardwareWallet, HardwareWallets},
|
||||
installer::{
|
||||
message::Message,
|
||||
@ -70,6 +72,7 @@ pub struct ShareXpubs {
|
||||
network: Network,
|
||||
hw_xpubs: Vec<HardwareWalletXpubs>,
|
||||
xpubs_signer: SignerXpubs,
|
||||
modal: Option<ExportModal>,
|
||||
}
|
||||
|
||||
impl ShareXpubs {
|
||||
@ -78,6 +81,7 @@ impl ShareXpubs {
|
||||
network,
|
||||
hw_xpubs: Vec::new(),
|
||||
xpubs_signer: SignerXpubs::new(signer),
|
||||
modal: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -102,6 +106,24 @@ impl Step for ShareXpubs {
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::ExportXpub(xpub_str) => {
|
||||
if self.modal.is_none() {
|
||||
let modal = ExportModal::new(None, ImportExportType::ExportXpub(xpub_str));
|
||||
let launch = modal.launch(true);
|
||||
self.modal = Some(modal);
|
||||
return launch;
|
||||
}
|
||||
}
|
||||
Message::ImportExport(ImportExportMessage::Close) => {
|
||||
if self.modal.is_some() {
|
||||
self.modal = None;
|
||||
}
|
||||
}
|
||||
Message::ImportExport(msg) => {
|
||||
if let Some(modal) = self.modal.as_mut() {
|
||||
return modal.update(msg);
|
||||
}
|
||||
}
|
||||
Message::UseHotSigner => {
|
||||
self.xpubs_signer.select(self.network);
|
||||
}
|
||||
@ -150,7 +172,14 @@ impl Step for ShareXpubs {
|
||||
}
|
||||
|
||||
fn subscription(&self, hws: &HardwareWallets) -> Subscription<Message> {
|
||||
hws.refresh().map(Message::HardwareWallets)
|
||||
let hw = hws.refresh().map(Message::HardwareWallets);
|
||||
if let Some(modal) = self.modal.as_ref() {
|
||||
if let Some(sub) = modal.subscription() {
|
||||
let export = sub.map(|m| Message::ImportExport(ImportExportMessage::Progress(m)));
|
||||
return Subscription::batch(vec![hw, export]);
|
||||
}
|
||||
}
|
||||
hw
|
||||
}
|
||||
|
||||
fn apply(&mut self, ctx: &mut Context) -> bool {
|
||||
@ -166,7 +195,7 @@ impl Step for ShareXpubs {
|
||||
_progress: (usize, usize),
|
||||
email: Option<&'a str>,
|
||||
) -> Element<Message> {
|
||||
view::share_xpubs(
|
||||
let content = view::share_xpubs(
|
||||
email,
|
||||
hws.list
|
||||
.iter()
|
||||
@ -190,7 +219,13 @@ impl Step for ShareXpubs {
|
||||
})
|
||||
.collect(),
|
||||
self.xpubs_signer.view(),
|
||||
)
|
||||
);
|
||||
|
||||
if let Some(modal) = &self.modal {
|
||||
modal.view(content)
|
||||
} else {
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -352,6 +352,7 @@ pub fn edit_key_modal<'a>(
|
||||
form_key_source_kind: Option<&KeySourceKind>,
|
||||
duplicate_master_fg: bool,
|
||||
) -> Element<'a, Message> {
|
||||
let xpub_valid = form_xpub.valid && !form_xpub.value.is_empty();
|
||||
let content = Column::new()
|
||||
.padding(25)
|
||||
.push_maybe(error.map(|e| card::error("Failed to import xpub", e.to_string())))
|
||||
@ -392,8 +393,22 @@ pub fn edit_key_modal<'a>(
|
||||
.push(
|
||||
Row::new()
|
||||
.align_y(Alignment::Center)
|
||||
.push(p1_regular("Enter an extended public key:").width(Length::Fill))
|
||||
.push(image::success_mark_icon().width(Length::Fixed(50.0)))
|
||||
.push(p1_regular("Enter/import an extended public key:").width(Length::Fill))
|
||||
.push_maybe(if !xpub_valid{
|
||||
Some(
|
||||
button::primary(Some(icon::restore_icon()), "Import")
|
||||
.on_press(
|
||||
Message::DefineDescriptor(
|
||||
message::DefineDescriptor::KeyModal(
|
||||
message::ImportKeyModal::ImportXpub(network),),)
|
||||
))
|
||||
} else { None }
|
||||
)
|
||||
.push_maybe(
|
||||
if xpub_valid {
|
||||
Some(image::success_mark_icon().width(Length::Fixed(50.0)))
|
||||
} else {None}
|
||||
)
|
||||
)
|
||||
.push(
|
||||
Row::new()
|
||||
@ -422,7 +437,7 @@ pub fn edit_key_modal<'a>(
|
||||
.align_y(Alignment::Center)
|
||||
.spacing(10)
|
||||
.push(icon::import_icon())
|
||||
.push(p1_regular("Enter an extended public key"))
|
||||
.push(p1_regular("Enter/import an extended public key"))
|
||||
)
|
||||
.padding(20)
|
||||
.width(Length::Fill)
|
||||
|
||||
@ -446,8 +446,8 @@ pub fn signer_xpubs<'a>(
|
||||
)
|
||||
.push(
|
||||
Container::new(
|
||||
button::secondary(Some(icon::clipboard_icon()), "Copy")
|
||||
.on_press(Message::Clibpboard(xpub.clone()))
|
||||
button::primary(Some(icon::backup_icon()), "Export")
|
||||
.on_press(Message::ExportXpub(xpub.clone()))
|
||||
.width(Length::Shrink),
|
||||
)
|
||||
.padding(10),
|
||||
@ -539,8 +539,8 @@ pub fn hardware_wallet_xpubs<'a>(
|
||||
)
|
||||
.push(
|
||||
Container::new(
|
||||
button::secondary(Some(icon::clipboard_icon()), "Copy")
|
||||
.on_press(Message::Clibpboard(xpub.clone()))
|
||||
button::primary(Some(icon::backup_icon()), "Export")
|
||||
.on_press(Message::ExportXpub(xpub.clone()))
|
||||
.width(Length::Shrink),
|
||||
)
|
||||
.padding(10),
|
||||
|
||||
@ -405,7 +405,7 @@ impl GUI {
|
||||
command.map(|msg| Message::Run(Box::new(msg)))
|
||||
}
|
||||
loader::Message::App(Err(e), _) => {
|
||||
tracing::error!("Fail to import backup: {e}");
|
||||
tracing::error!("Failed to import backup: {e}");
|
||||
Task::none()
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user