Repository: wizardsardine/liana (rust)

Issue:
Include restore via backup file using Liana Connect in the add an existing Liana wallet flow
A user can currently just enter the descriptor, we should let them have the possibility to upload a backup file:

![Image](https://github.com/user-attachments/assets/2eb61fc1-740e-4225-b2c7-f93d4eeb74aa)

The section should become:
```

**Load a wallet from backup file / descriptor**
Upload the wallet backup file or paste its descriptor to add it

"Import descriptor" button

or

"Descriptor"
(paste area)
```

This work is important since we won't have the backend switch in the settings but we will indicate the user how to restore easily the wallet with a new backend/node option. Using the file is much simpler, easy to understand and we should standardize it for all backend options.

Pull request:
Add an option for add a descriptor from file/backup for liana-connect
This PR closes #1751:
 - add the option to import a descriptor from a file.
 - add a spacer at the bottom of the screen.
 - add the possibility to import also a descriptor from a backup file.



Patch:
diff --git a/flake.nix b/flake.nix
index 0e37cef0..4c43dd4b 100644
--- a/flake.nix
+++ b/flake.nix
@@ -140,34 +140,23 @@
             ${aarch64-apple-darwin}/aarch64-apple-darwin/liana-gui
         '';
 
-        # Common build inputs for all shells
-        commonBuildInputs = with pkgs; [
-          expat
-          fontconfig
-          freetype
-          freetype.dev
-          libGL
-          pkg-config
-          udev
-          wayland
-          libxkbcommon
-          xorg.libX11
-          xorg.libXcursor
-          xorg.libXi
-          xorg.libXrandr
-        ];
-
-        # Minimal shell without Rust toolchain
-        minimalShell = pkgs.mkShell rec {
-          buildInputs = commonBuildInputs;
-
-          LD_LIBRARY_PATH =
-            builtins.foldl' (a: b: "${a}:${b}/lib") "${pkgs.vulkan-loader}/lib" buildInputs;
-        };
-
-        # Full development shell with Rust toolchain
         devShell = pkgs.mkShell rec {
-          buildInputs = commonBuildInputs ++ [ toolchain ];
+          buildInputs = with pkgs; [
+            expat
+            fontconfig
+            freetype
+            freetype.dev
+            libGL
+            pkg-config
+            udev
+            wayland
+            libxkbcommon
+            xorg.libX11
+            xorg.libXcursor
+            xorg.libXi
+            xorg.libXrandr
+            toolchain
+          ];
 
           LD_LIBRARY_PATH =
             builtins.foldl' (a: b: "${a}:${b}/lib") "${pkgs.vulkan-loader}/lib" buildInputs;
@@ -202,7 +191,7 @@
 
 
         devShells = {
-          minimal = minimalShell;
+          dev = devShell;
           release = releaseShell;
           default = devShell;
         };
diff --git a/liana-gui/src/backup.rs b/liana-gui/src/backup.rs
index cbfcb901..bb3b09d8 100644
--- a/liana-gui/src/backup.rs
+++ b/liana-gui/src/backup.rs
@@ -1,7 +1,10 @@
 use chrono::{Duration, Utc};
-use liana::miniscript::{
-    self,
-    bitcoin::{bip32::Fingerprint, Network, Txid},
+use liana::{
+    descriptors::LianaDescriptor,
+    miniscript::{
+        self,
+        bitcoin::{bip32::Fingerprint, Network, Txid},
+    },
 };
 use lianad::{
     bip329,
@@ -111,6 +114,23 @@ impl Debug for Backup {
 }
 
 impl Backup {
+    /// Create a Backup from a descriptor
+    ///
+    /// # Arguments
+    /// * `descriptor` - the descriptor
+    /// * `network` - the network
+    pub fn from_descriptor(descriptor: LianaDescriptor, network: Network) -> Self {
+        let account = Account::new(descriptor.to_string());
+        Backup {
+            name: None,
+            alias: None,
+            accounts: vec![account],
+            network,
+            date: None,
+            proprietary: Default::default(),
+            version: default_version(),
+        }
+    }
     /// Create a Backup from the Installer context
     ///
     /// # Arguments
diff --git a/liana-gui/src/export.rs b/liana-gui/src/export.rs
index 88a910f9..fd4e74ee 100644
--- a/liana-gui/src/export.rs
+++ b/liana-gui/src/export.rs
@@ -246,7 +246,7 @@ pub enum Progress {
     WalletFromBackup(
         (
             LianaDescriptor,
-            Network,
+            Option<Network>,
             HashMap<Fingerprint, settings::KeySetting>,
             Backup,
         ),
@@ -637,7 +637,19 @@ pub async fn import_descriptor(
     file.read_to_string(&mut descr_str)?;
     let descr_str = descr_str.trim();
 
-    let descriptor = LianaDescriptor::from_str(descr_str).map_err(|_| Error::ParseDescriptor)?;
+    let descriptor = if let Ok(d) = LianaDescriptor::from_str(descr_str) {
+        Some(d)
+    } else {
+        let backup: Result<Backup, _> = serde_json::from_str(descr_str);
+        backup.ok().and_then(|b| {
+            if b.accounts.len() == 1 {
+                LianaDescriptor::from_str(&b.accounts[0].descriptor).ok()
+            } else {
+                None
+            }
+        })
+    };
+    let descriptor = descriptor.ok_or(Error::ParseDescriptor)?;
 
     send_progress!(sender, Progress(100.0));
     send_progress!(sender, Descriptor(descriptor));
@@ -1033,13 +1045,33 @@ pub async fn wallet_from_backup(
 
     let backup: Result<Backup, _> = serde_json::from_str(&backup_str);
     let backup = match backup {
-        Ok(psbt) => psbt,
+        Ok(b) => b,
         Err(e) => {
-            return Err(Error::BackupImport(format!("{:?}", e)));
+            // try to import as bare descriptor
+            let descr = LianaDescriptor::from_str(&backup_str);
+            match descr {
+                Ok(descr) => {
+                    let network = if descr.all_xpubs_net_is(Network::Bitcoin) {
+                        Network::Bitcoin
+                    } else {
+                        Network::Signet
+                    };
+                    Backup::from_descriptor(descr, network)
+                }
+                Err(e2) => {
+                    return Err(Error::BackupImport(format!(
+                        "A backup or descriptor file is expected: {e}, {e2}"
+                    )));
+                }
+            }
         }
     };
 
-    let network = backup.network;
+    let network = if backup.network == Network::Bitcoin {
+        Some(backup.network)
+    } else {
+        None
+    };
 
     let account = match backup.accounts.len() {
         0 => {
@@ -1274,8 +1306,44 @@ pub async fn app_backup_export(
 
 #[cfg(test)]
 mod tests {
+    use std::env;
+
     use super::*;
 
+    #[tokio::test]
+    async fn test_import_descriptor_from_file() {
+        let (sender, mut receiver) = unbounded_channel();
+        let path = env::current_dir()
+            .unwrap()
+            .join("test_assets")
+            .join("liana-jz5sm0xn.txt");
+        println!("path: {}", path.display());
+        import_descriptor(&sender, path).await.unwrap();
+        let _msg = receiver.try_recv().unwrap();
+        assert!(matches!(Progress::Progress(100.0), _msg));
+        let raw_descriptor = "wsh(or_d(pk([8a550171/48'/1'/0'/2']tpubDFnCs5ZaCqopaNhgLCiXAwbkaBdcnuMt1VFoPsRpUrpidyvzG67MYjkfxw6HnTBhHqeU3xw2ioNBVcWY3jXwGhSyppEQvtn38GsL7RH1eef/<0;1>/*),and_v(v:pkh([8a550171/48'/1'/0'/2']tpubDFnCs5ZaCqopaNhgLCiXAwbkaBdcnuMt1VFoPsRpUrpidyvzG67MYjkfxw6HnTBhHqeU3xw2ioNBVcWY3jXwGhSyppEQvtn38GsL7RH1eef/<2;3>/*),older(52596))))#jz5sm0xn";
+        let descr = LianaDescriptor::from_str(raw_descriptor).unwrap();
+        let _msg = receiver.try_recv().unwrap();
+        assert!(matches!(Progress::Descriptor(descr), _msg));
+    }
+
+    #[tokio::test]
+    async fn test_import_descriptor_from_backup_file() {
+        let (sender, mut receiver) = unbounded_channel();
+        let path = env::current_dir()
+            .unwrap()
+            .join("test_assets")
+            .join("liana-backup-2025-06-23T13-23-54.json");
+        println!("path: {}", path.display());
+        import_descriptor(&sender, path).await.unwrap();
+        let _msg = receiver.try_recv().unwrap();
+        assert!(matches!(Progress::Progress(100.0), _msg));
+        let raw_descriptor = "wsh(or_d(pk([8a550171/48'/1'/0'/2']tpubDFnCs5ZaCqopaNhgLCiXAwbkaBdcnuMt1VFoPsRpUrpidyvzG67MYjkfxw6HnTBhHqeU3xw2ioNBVcWY3jXwGhSyppEQvtn38GsL7RH1eef/<0;1>/*),and_v(v:pkh([8a550171/48'/1'/0'/2']tpubDFnCs5ZaCqopaNhgLCiXAwbkaBdcnuMt1VFoPsRpUrpidyvzG67MYjkfxw6HnTBhHqeU3xw2ioNBVcWY3jXwGhSyppEQvtn38GsL7RH1eef/<2;3>/*),older(52596))))#jz5sm0xn";
+        let descr = LianaDescriptor::from_str(raw_descriptor).unwrap();
+        let _msg = receiver.try_recv().unwrap();
+        assert!(matches!(Progress::Descriptor(descr), _msg));
+    }
+
     #[test]
     fn test_parse_coldcard_xpub() {
         let raw = r#"
diff --git a/liana-gui/src/installer/message.rs b/liana-gui/src/installer/message.rs
index 693da779..c5453259 100644
--- a/liana-gui/src/installer/message.rs
+++ b/liana-gui/src/installer/message.rs
@@ -117,6 +117,8 @@ pub enum ImportRemoteWallet {
     InvitationFetched(Result<api::WalletInvitation, Error>),
     AcceptInvitation,
     InvitationAccepted(Result<api::Wallet, Error>),
+    ImportDescriptorFromFile,
+    ImportExport(ImportExportMessage),
 }
 
 #[derive(Debug, Clone)]
diff --git a/liana-gui/src/installer/step/backend.rs b/liana-gui/src/installer/step/backend.rs
index c5379953..78153328 100644
--- a/liana-gui/src/installer/step/backend.rs
+++ b/liana-gui/src/installer/step/backend.rs
@@ -1,13 +1,15 @@
 use std::str::FromStr;
 
-use iced::Task;
+use iced::{Subscription, Task};
 
 use liana::{descriptors::LianaDescriptor, miniscript::bitcoin::Network};
 use liana_ui::{component::form, widget::Element};
 
 use crate::{
+    app::state::export::ExportModal,
     daemon::DaemonError,
     dir::NetworkDirectory,
+    export::{ImportExportMessage, ImportExportType, Progress},
     hw::HardwareWallets,
     installer::{
         context::{self, Context, RemoteBackend},
@@ -448,6 +450,7 @@ pub struct ImportRemoteWallet {
     error: Option<String>,
     backend: context::RemoteBackend,
     wallets: Vec<api::Wallet>,
+    modal: Option<ExportModal>,
     // wallet alias is stored here to be applied to context
     // and be modified in a following step
     wallet_alias: Option<String>,
@@ -464,6 +467,7 @@ impl ImportRemoteWallet {
             error: None,
             backend: context::RemoteBackend::Undefined,
             wallets: Vec::new(),
+            modal: None,
             wallet_alias: None,
         }
     }
@@ -502,6 +506,37 @@ impl Step for ImportRemoteWallet {
     // Verification of the values is happening when the user click on Next button.
     fn update(&mut self, _hws: &mut HardwareWallets, message: Message) -> Task<Message> {
         match message {
+            Message::ImportRemoteWallet(message::ImportRemoteWallet::ImportDescriptorFromFile) => {
+                let modal = ExportModal::new(None, ImportExportType::ImportDescriptor);
+                let launch = modal.launch(false);
+                self.modal = Some(modal);
+                return launch;
+            }
+            Message::ImportExport(ImportExportMessage::Path(p)) => {
+                if let Some(modal) = self.modal.as_mut() {
+                    return modal.update(ImportExportMessage::Path(p));
+                }
+            }
+            Message::ImportExport(ImportExportMessage::Close) => self.modal = None,
+            Message::ImportRemoteWallet(message::ImportRemoteWallet::ImportExport(m)) => match m {
+                ImportExportMessage::Close => self.modal = None,
+                ImportExportMessage::Progress(Progress::Descriptor(d)) => {
+                    self.modal = None;
+                    return Task::batch([
+                        Task::done(Message::ImportRemoteWallet(
+                            message::ImportRemoteWallet::ImportDescriptor(d.to_string()),
+                        )),
+                        Task::done(Message::ImportRemoteWallet(
+                            message::ImportRemoteWallet::ConfirmDescriptor,
+                        )),
+                    ]);
+                }
+                m => {
+                    if let Some(modal) = self.modal.as_mut() {
+                        return modal.update(m);
+                    }
+                }
+            },
             Message::ImportRemoteWallet(message::ImportRemoteWallet::ImportDescriptor(desc)) => {
                 self.imported_descriptor.value = desc;
                 if !self.imported_descriptor.value.is_empty() {
@@ -643,6 +678,19 @@ impl Step for ImportRemoteWallet {
         Task::none()
     }
 
+    fn subscription(&self, _hws: &HardwareWallets) -> iced::Subscription<Message> {
+        if let Some(modal) = &self.modal {
+            if let Some(sub) = modal.subscription() {
+                return sub.map(|m| {
+                    Message::ImportRemoteWallet(message::ImportRemoteWallet::ImportExport(
+                        ImportExportMessage::Progress(m),
+                    ))
+                });
+            }
+        }
+        Subscription::none()
+    }
+
     fn apply(&mut self, ctx: &mut Context) -> bool {
         // Set to true in order to force the registration process to be shown to user.
         ctx.hw_is_used = true;
@@ -662,7 +710,7 @@ impl Step for ImportRemoteWallet {
         progress: (usize, usize),
         email: Option<&'a str>,
     ) -> Element<Message> {
-        view::import_wallet_or_descriptor(
+        let content = view::import_wallet_or_descriptor(
             progress,
             email,
             &self.invitation_token,
@@ -675,7 +723,12 @@ impl Step for ImportRemoteWallet {
                 .iter()
                 .map(|w| (&w.name, w.metadata.wallet_alias.as_ref()))
                 .collect(),
-        )
+        );
+        if let Some(modal) = &self.modal {
+            modal.view(content)
+        } else {
+            content
+        }
     }
 }
 
diff --git a/liana-gui/src/installer/step/descriptor/mod.rs b/liana-gui/src/installer/step/descriptor/mod.rs
index 223576a1..cea59a7b 100644
--- a/liana-gui/src/installer/step/descriptor/mod.rs
+++ b/liana-gui/src/installer/step/descriptor/mod.rs
@@ -120,12 +120,26 @@ impl Step for ImportDescriptor {
             }
             Message::ImportExport(ImportExportMessage::Progress(Progress::WalletFromBackup(r))) => {
                 let (descriptor, network, aliases, backup) = r;
-                if self.network == network {
-                    self.imported_backup = Some(backup);
-                    self.imported_descriptor.value = descriptor.to_string();
-                    self.imported_aliases = Some(aliases);
+                if let Some(n) = network {
+                    if self.network == n {
+                        self.imported_backup = Some(backup);
+                        self.imported_descriptor.value = descriptor.to_string();
+                        self.imported_aliases = Some(aliases);
+                    } else {
+                        self.error =
+                            Some("Backup network do not match the selected network!".into());
+                    }
                 } else {
-                    self.error = Some("Backup network do not match the selected network!".into());
+                    // The backup have been inferred from a bare descriptor, we check whether
+                    // the descriptor match any test network
+                    if self.network != Network::Bitcoin {
+                        self.imported_backup = Some(backup);
+                        self.imported_descriptor.value = descriptor.to_string();
+                        self.imported_aliases = Some(aliases);
+                    } else {
+                        self.error =
+                            Some("Backup network do not match the selected network!".into());
+                    }
                 }
             }
             Message::ImportExport(m) => {
diff --git a/liana-gui/src/installer/view/mod.rs b/liana-gui/src/installer/view/mod.rs
index c325982a..b17a596f 100644
--- a/liana-gui/src/installer/view/mod.rs
+++ b/liana-gui/src/installer/view/mod.rs
@@ -233,6 +233,12 @@ pub fn import_wallet_or_descriptor<'a>(
                                     .size(text::P1_SIZE)
                                     .padding(10),
                                 )
+                                .push(text("or").bold())
+                                .push(button::primary(None, "Import descriptor").on_press(
+                                    Message::ImportRemoteWallet(
+                                        message::ImportRemoteWallet::ImportDescriptorFromFile,
+                                    ),
+                                ))
                                 .spacing(10),
                         )
                         .push(
@@ -268,7 +274,8 @@ pub fn import_wallet_or_descriptor<'a>(
             .push_maybe(error.map(|e| card::error("Something wrong happened", e.to_string())))
             .push(card_wallets)
             .push(card::simple(col_invitation_token).padding(0))
-            .push(card::simple(col_descriptor).padding(0)),
+            .push(card::simple(col_descriptor).padding(0))
+            .push(Space::with_height(10)),
         true,
         Some(Message::Previous),
     )


Test patch:
diff --git a/liana-gui/test_assets/liana-backup-2025-06-23T13-23-54.json b/liana-gui/test_assets/liana-backup-2025-06-23T13-23-54.json
new file mode 100644
index 00000000..e90e4c21
--- /dev/null
+++ b/liana-gui/test_assets/liana-backup-2025-06-23T13-23-54.json
@@ -0,0 +1,69 @@
+{
+  "name": "Liana-jz5sm0xn",
+  "alias": "My Liana Regtest wallet",
+  "accounts": [
+    {
+      "name": "Liana-jz5sm0xn",
+      "descriptor": "wsh(or_d(pk([8a550171/48'/1'/0'/2']tpubDFnCs5ZaCqopaNhgLCiXAwbkaBdcnuMt1VFoPsRpUrpidyvzG67MYjkfxw6HnTBhHqeU3xw2ioNBVcWY3jXwGhSyppEQvtn38GsL7RH1eef/<0;1>/*),and_v(v:pkh([8a550171/48'/1'/0'/2']tpubDFnCs5ZaCqopaNhgLCiXAwbkaBdcnuMt1VFoPsRpUrpidyvzG67MYjkfxw6HnTBhHqeU3xw2ioNBVcWY3jXwGhSyppEQvtn38GsL7RH1eef/<2;3>/*),older(52596))))#jz5sm0xn",
+      "receive_index": 1,
+      "change_index": 2,
+      "timestamp": 1748013169,
+      "keys": {
+        "8a550171": {
+          "key": "8a550171",
+          "alias": "a"
+        }
+      },
+      "labels": [],
+      "transactions": [
+        "020000000001042e410675d4d1b009e257ca11345502e5c1f00709590db95e2ac9fe1a11044d4d0000000000fdffffff2503d8712de399f674baab16b844a54870bd2142650c609edb3d1cd60bcbf77b0000000000fdffffff4e093e78a81157ebcb11780bfe010fc0752231015d0c2a8114630ac7c6c159c50000000000fdffffffb5e6c596c3659f7af56ebaa9e8ae618c5ac7f338033172e9475f1933f04e348a0000000000fdffffff02d263060000000000225120e49d1997805dbb7290369fb95f94451b0ca3e738119b9c6b6e5a70a33384913100e1f50500000000220020fd22758b64471a67371158c546ab5918732659085eb21c9af6591cb811e56c660247304402203c379630bf2b827abf013ef8ed6a37a18dfc98fef28199a208bc19bc68b441f40220248456a96302579d0739ce8dbc63550835961a811198859aaa250c8611e46466012103df49913c09de6d937101041d2e413a09e5991ba3ef31534d88054b319d2d74510247304402202a8e2ffcaa86fef2fc40caf21d0d59b4801ad553701b4466c2872e5ac3ffb23f02200f10f5493543f8c4fe37627a7ad6bfa93b01a22873983656003ababe9d6c4ca60121031337b42040d5f2700ba46ae7cf70dc172c2b4f695313a8ed0fe62ea843278e6c0247304402204ee9ae50ff1bbde05832236fdea9f3709a4d9fa87014cf86a63e9333641c737c02206394e7124539adf560f76ddbb87ec4614d9a9e70ac995c88d1b2b6e614ab4e430121034547cd6c5fe44edbc9af9193df5a9a7fc0055c3f47b968e92ae30d6630a6cd08024730440220135820533d687ed853e92f7e52ef73de755f13ace2db60f5f85a07681bd62e2a022015290d93eb8b0d53f17c2851f2fb53ba2859ee15aacb0a188041f46f249101100121029f4c9445023494227a966609268d0db59bf82eb21433188470bbdcdd63f5508caf5d0000"
+      ],
+      "psbts": [
+        "cHNidP8BAF4CAAAAAcb4e2jUBqxxBTgMZFk6JUCes6Q3cvgpYJ2uJDUOBbudAQAAAAD9////AX3g9QUAAAAAIgAgCkktj1GAtl5qSGMFiltgrcw2ozjyaWF4XBktdHaUItuyXQAAAAEA/bICAgAAAAABBC5BBnXU0bAJ4lfKETRVAuXB8AcJWQ25XirJ/hoRBE1NAAAAAAD9////JQPYcS3jmfZ0uqsWuESlSHC9IUJlDGCe2z0c1gvL93sAAAAAAP3///9OCT54qBFX68sReAv+AQ/AdSIxAV0MKoEUYwrHxsFZxQAAAAAA/f///7XmxZbDZZ969W66qeiuYYxax/M4AzFy6UdfGTPwTjSKAAAAAAD9////AtJjBgAAAAAAIlEg5J0Zl4Bdu3KQNp+5X5RFGwyj5zgRm5xrblpwozOEkTEA4fUFAAAAACIAIP0idYtkRxpnNxFYxUarWRhzJlkIXrIcmvZZHLgR5WxmAkcwRAIgPDeWML8rgnq/AT747Wo3oY38mP7ygZmiCLwZvGi0QfQCICSEVqljAledBznOjbxjVQg1lhqBEZiFmqolDIYR5GRmASED30mRPAnebZNxAQQdLkE6CeWZG6PvMVNNiAVLMZ0tdFECRzBEAiAqji/8qob+8vxAyvIdDVm0gBrVU3AbRGbChy5aw/+yPwIgDxD1STVD+MT+N2J6eta/qTsBoihzmDZWADq6vp1sTKYBIQMTN7QgQNXycAukaufPcNwXLCtPaVMTqO0P5i6oQyeObAJHMEQCIE7prlD/G73gWDIjb96p83CaTZ+ocBTPhqY+kzNkHHN8AiBjlOcSRTmt9WD3bdu4fsRhTZqecKyZXIjRsrbmFKtOQwEhA0VHzWxf5E7bya+Rk99amn/ABVw/R7lo6SrjDWYwps0IAkcwRAIgE1ggUz1ofthT6S9+Uu9z3nVfE6zi22D1+FoHaBvWLioCIBUpDZPriw1T8XwoUfL7U7ooWe4VqssKGIBB9G8kkQEQASECn0yURQI0lCJ6lmYJJo0NtZv4LrIUMxiEcLvc3WP1UIyvXQAAAQErAOH1BQAAAAAiACD9InWLZEcaZzcRWMVGq1kYcyZZCF6yHJr2WRy4EeVsZiICA2vrvUCtFrmnsdsUyMDb2kT4uowEfiQ5D/1l+cWZ1vDWRzBEAiAaM9aLqBTG5C4tK9p9c28Jz439Emr7XPoxUYyJllYLMAIgFA6/yH55PAeZedfY2h6at7dB/k/wPf8M3+y0IEcQgKIBIgIDio+/H1qygPy0Phj9olT5VctStZGyB6qVDiVdlfiIWTpHMEQCIGUetlDH7qqci2rSxbHnvoqcs1pEJHgaLbRAnMPjffv1AiBHhT5DcOQhVhn0vtXO8Ij5ii3BafsK9VX2SN2l9H1/9QEBBUQhA2vrvUCtFrmnsdsUyMDb2kT4uowEfiQ5D/1l+cWZ1vDWrHNkdqkUXG6thIRwLnqHwJdmKl7XWxiKxDyIrQN0zQCyaCIGA2vrvUCtFrmnsdsUyMDb2kT4uowEfiQ5D/1l+cWZ1vDWHIpVAXEwAACAAQAAgAAAAIACAACAAAAAAAEAAAAiBgOKj78fWrKA/LQ+GP2iVPlVy1K1kbIHqpUOJV2V+IhZOhyKVQFxMAAAgAEAAIAAAACAAgAAgAIAAAABAAAAACICA1BIFqUUMoNtx4WVqTmwuL2iEpyL6ZLAflqiZpCHcVveHIpVAXEwAACAAQAAgAAAAIACAACAAwAAAAIAAAAiAgO+QA+pMN6tM8/CHiT8Rt32H2jEOY2nZpV2rV/QYKGJEhyKVQFxMAAAgAEAAIAAAACAAgAAgAEAAAACAAAAAA=="
+      ],
+      "coins": {
+        "9dbb050e3524ae9d6029f87237a4b39e40253a59640c380571ac06d4687bf8c6:1": {
+          "amount": 100000000,
+          "outpoint": "9dbb050e3524ae9d6029f87237a4b39e40253a59640c380571ac06d4687bf8c6:1",
+          "address": "bcrt1ql538tzmygudxwdc3trz5d26erpejvkggt6epexhktywtsy09d3nqzqf3d4",
+          "block_height": 23984,
+          "account": 0,
+          "derivation_index": 1,
+          "is_coinbase": null,
+          "is_from_self": false
+        }
+      },
+      "chain_tip": {
+        "block_height": 24332,
+        "block_hash": null
+      },
+      "proprietary": {
+        "config": {
+          "debug": null,
+          "log_level": null,
+          "start_internal_bitcoind": false
+        },
+        "liana_version": "11.0",
+        "settings": {
+          "alias": "My Liana Regtest wallet",
+          "descriptor_checksum": "jz5sm0xn",
+          "hardware_wallets": [],
+          "keys": [
+            {
+              "master_fingerprint": "8a550171",
+              "name": "a",
+              "provider_key": null
+            }
+          ],
+          "name": "Liana-jz5sm0xn",
+          "pinned_at": 1748013169,
+          "remote_backend_auth": null,
+          "start_internal_bitcoind": false
+        }
+      }
+    }
+  ],
+  "network": "regtest",
+  "date": 1750677837,
+  "version": 0
+}
\ No newline at end of file
diff --git a/liana-gui/test_assets/liana-jz5sm0xn.txt b/liana-gui/test_assets/liana-jz5sm0xn.txt
new file mode 100644
index 00000000..2838db61
--- /dev/null
+++ b/liana-gui/test_assets/liana-jz5sm0xn.txt
@@ -0,0 +1,1 @@
+wsh(or_d(pk([8a550171/48'/1'/0'/2']tpubDFnCs5ZaCqopaNhgLCiXAwbkaBdcnuMt1VFoPsRpUrpidyvzG67MYjkfxw6HnTBhHqeU3xw2ioNBVcWY3jXwGhSyppEQvtn38GsL7RH1eef/<0;1>/*),and_v(v:pkh([8a550171/48'/1'/0'/2']tpubDFnCs5ZaCqopaNhgLCiXAwbkaBdcnuMt1VFoPsRpUrpidyvzG67MYjkfxw6HnTBhHqeU3xw2ioNBVcWY3jXwGhSyppEQvtn38GsL7RH1eef/<2;3>/*),older(52596))))#jz5sm0xn
\ No newline at end of file