Repository: wizardsardine/liana
Pull request #313 (2023-02-03 18:22:53)

gui: fix hws list reloading
I wanted the refresh button to add more harware wallet to the list without erasing the previous list
in order to not cut the existing communication channels.

But with the new display of "unsupported wallet" this behaviour can corrupt the list. An "unsupported wallet" can become "supported" because user switch app or unlock.

It is preferable to reset the full list and drop the objects after a refresh.


Patch:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 983aded9..f8d39d05 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -42,7 +42,7 @@ jobs:
         if: matrix.os == 'windows-latest'
         run: cargo test --verbose --no-default-features
       - name: Test on Rust ${{ matrix.toolchain }} (non Windows)
-        # See issue https://github.com/wizardsardine/liana/issues/69
+        # See issue https://github.com/revault/liana/issues/69
         if: matrix.os != 'windows-latest' && (matrix.os != 'macOS-latest' || matrix.toolchain != '1.48')
         run: cargo test --verbose --color always -- --nocapture
 
diff --git a/CHANGELOG.md b/CHANGELOG.md
deleted file mode 100644
index ca0574cb..00000000
--- a/CHANGELOG.md
+++ /dev/null
@@ -1,32 +0,0 @@
-# Liana daemon and GUI release notes
-
-## 0.2
-
-The second release of Liana brings various fixes as well as the possibility to use a multisig in
-either, or both, of the spending paths.
-
-### Features
-
-- Multisig: we now support descriptors with multiple keys both in the primary (non-timelocked)
-  spending path and the recovery (timelocked) path.
-
-#### GUI-specific
-
-- You can now import and update Spend transaction drafts as PSBTs to collaboratively create and sign
-  transactions.
-- When creating a new descriptor you can now set an alias for each key. Those will be displayed when
-  inspecting a transaction's signatories.
-- Amounts are now displayed with the sats in bold for better redability.
-
-### Fixes
-
-- We now remove the fixed interpreter and rpath set by GUIX reproducible builds in the `liana-cli`
-  ELF binary.
-- We now check the `bitcoind` version before trying to import a Miniscript descriptor.
-- We now discard unconfirmed incoming payments that were dropped from our mempool.
-
-#### GUI-specific
-
-- Various wording fixes on the UI.
-- Amounts are now updated when moving between steps in the Spend creation flow.
-- Coins are now sorted by age when displayed as a list.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 228df938..1d6a12a0 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -19,7 +19,7 @@ Github issue before going forward with the implementation.
 ## Looking for contributions
 
 If you are looking for first time contributions, you can `git grep` for `FIXME`s and `TODO`s
-as well as checking out the [good first issues](https://github.com/wizardsardine/liana/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
+as well as checking out the [good first issues](https://github.com/revault/liana/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
 on the issue tracker.
 
 
diff --git a/Cargo.lock b/Cargo.lock
index 94585c46..3402f40b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -205,7 +205,7 @@ dependencies = [
 
 [[package]]
 name = "liana"
-version = "0.2.0"
+version = "0.1.0"
 dependencies = [
  "backtrace",
  "base64",
diff --git a/Cargo.toml b/Cargo.toml
index c76325a7..f02f8cee 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,9 +1,9 @@
 [package]
 name = "liana"
-version = "0.2.0"
+version = "0.1.0"
 authors = ["Antoine Poinsot <darosior@protonmail.com>"]
 edition = "2018"
-repository = "https://github.com/wizardsardine/liana"
+repository = "https://github.com/revault/liana"
 license-file = "LICENCE"
 keywords = ["bitcoin", "wallet", "safe", "script", "miniscript", "inheritance", "recovery"]
 description = "Liana wallet daemon"
diff --git a/README.md b/README.md
index 182ae651..76e84b32 100644
--- a/README.md
+++ b/README.md
@@ -42,7 +42,7 @@ your machine yet, you can download it [there](https://bitcoincore.org/en/downloa
 The recommended installation method for regular users is to download an executable software release. If you prefer to
 build the project from source, see [`doc/BUILD.md`](doc/BUILD.md) instead.
 
-Head to the [release page](https://github.com/wizardsardine/liana/releases) and download the right
+Head to the [release page](https://github.com/revault/liana/releases) and download the right
 executable for your platform. If you are not sure what is the "right" executable for your platform,
 choose `liana-0.1.exe` if you are on Windows, `liana-0.1.dmg` if you are on MacOS and
 `liana-0.1-x86_64-linux-gnu.tar.gz` if you are on Linux.
@@ -113,7 +113,7 @@ application: `2.1.0`. This is the minimum supported version, as it's the first o
 support for Miniscript.
 
 If you are using the GUI, it should be intuitive what menu to use depending on your intention. If it
-is not, bug report are very welcome so [feel free to report it](https://github.com/wizardsardine/liana/issues)! :)
+is not, bug report are very welcome so [feel free to report it](https://github.com/revault/liana/issues)! :)
 
 If you are using the daemon, you can use the `liana-cli` binary to send commands to it. It will need
 the path to the same configuration as the daemon. You can find a full documentation of the JSONRPC
@@ -157,7 +157,7 @@ sweep PSBT to the requested address with the specified feerate, filled with all
 
 ## About the software project
 
-Liana is an open source project. It is [hosted at Github](https://github.com/wizardsardine/liana).
+Liana is an open source project. It is [hosted at Github](https://github.com/revault/liana).
 
 Contributions are very welcome. For guidelines, see [CONTRIBUTING.md](CONTRIBUTING.md).
 
diff --git a/contrib/release/release.sh b/contrib/release/release.sh
index 3fa47c5b..7fb5b84e 100755
--- a/contrib/release/release.sh
+++ b/contrib/release/release.sh
@@ -7,7 +7,7 @@
 
 set -ex
 
-VERSION="${VERSION:-"0.2"}"
+VERSION="${VERSION:-"0.1"}"
 LIANA_PREFIX="liana-$VERSION"
 LINUX_DIR_NAME="$LIANA_PREFIX-x86_64-linux-gnu"
 LINUX_ARCHIVE="$LINUX_DIR_NAME.tar.gz"
diff --git a/contrib/reproducible/docker/cargo_config.toml b/contrib/reproducible/docker/cargo_config.toml
index 07998080..366c3a00 100644
--- a/contrib/reproducible/docker/cargo_config.toml
+++ b/contrib/reproducible/docker/cargo_config.toml
@@ -9,8 +9,8 @@ git = "https://github.com/darosior/rust-miniscript"
 branch = "multipath_descriptors_on_8.0"
 replace-with = "vendored_sources"
 
-[source."https://github.com/wizardsardine/liana"]
-git = "https://github.com/wizardsardine/liana"
+[source."https://github.com/revault/liana"]
+git = "https://github.com/revault/liana"
 branch = "master"
 replace-with = "vendored_sources"
 
diff --git a/contrib/reproducible/docker/macos.Dockerfile b/contrib/reproducible/docker/macos.Dockerfile
index 5113c2b9..b5dd0073 100644
--- a/contrib/reproducible/docker/macos.Dockerfile
+++ b/contrib/reproducible/docker/macos.Dockerfile
@@ -9,8 +9,8 @@ RUN apt update && apt install -y \
                     make=4.3-4.1 \
                     libssl-dev=1.1.1n-0+deb11u3 \
                     liblzma-dev=5.2.5-2.1~deb11u1 \
-                    libxml2=2.9.10+dfsg-6.7+deb11u3 \
-                    libxml2-dev=2.9.10+dfsg-6.7+deb11u3 \
+                    libxml2=2.9.10+dfsg-6.7+deb11u2 \
+                    libxml2-dev=2.9.10+dfsg-6.7+deb11u2 \
                     cmake=3.18.4-2+deb11u1 \
                     git=1:2.30.2-1 \
                     patch=2.7.6-7 \
@@ -21,7 +21,7 @@ RUN apt update && apt install -y \
                     libbz2-dev=1.0.8-4 \
                     xz-utils=5.2.5-2.1~deb11u1 \
                     bzip2=1.0.8-4 \
-                    curl=7.74.0-1.3+deb11u5
+                    curl=7.74.0-1.3+deb11u3
 
 # Download the cargo binary and compiled stdlib from the distributed releases to make sure to build with
 # the very same toolchain. We use 1.65.0 because it is unfortunately the MSRV of the GUI.
diff --git a/contrib/reproducible/docker/windows.Dockerfile b/contrib/reproducible/docker/windows.Dockerfile
index 1ea7336a..b658dc3e 100644
--- a/contrib/reproducible/docker/windows.Dockerfile
+++ b/contrib/reproducible/docker/windows.Dockerfile
@@ -6,7 +6,7 @@ WORKDIR /liana
 # out of our way to pin the whole tree of deps. Instead invest time in getting Guix cross-compilation.
 RUN apt update && apt install -y \
                     gcc-mingw-w64-x86-64=10.2.1-6+24.2 \
-                    curl=7.74.0-1.3+deb11u5 \
+                    curl=7.74.0-1.3+deb11u3 \
                     gcc=4:10.2.1-1
 
 # Download the cargo binary and compiled stdlib from the distributed releases to make sure to build with
diff --git a/contrib/reproducible/guix/build.sh b/contrib/reproducible/guix/build.sh
index 8969f82f..d8d6535d 100755
--- a/contrib/reproducible/guix/build.sh
+++ b/contrib/reproducible/guix/build.sh
@@ -18,8 +18,8 @@ git = "https://github.com/darosior/rust-miniscript"
 branch = "multipath_descriptors_on_9.0"
 replace-with = "vendored_sources"
 
-[source."https://github.com/wizardsardine/liana"]
-git = "https://github.com/wizardsardine/liana"
+[source."https://github.com/revault/liana"]
+git = "https://github.com/revault/liana"
 branch = "master"
 replace-with = "vendored_sources"
 EOF
diff --git a/doc/TRY.md b/doc/TRY.md
index a0c055ba..1e27d61a 100644
--- a/doc/TRY.md
+++ b/doc/TRY.md
@@ -126,7 +126,7 @@ Keep the Specter simulator open and move on to the next step.
 
 ## Step 3: start Liana
 
-Head to the [release page](https://github.com/wizardsardine/liana/releases) and download the right
+Head to the [release page](https://github.com/revault/liana/releases) and download the right
 executable for your platform. If you are not sure what is the "right" executable for your platform,
 choose `liana-0.1.exe` if you are on Windows, `liana-0.1.dmg` if you are on MacOS and
 `liana-0.1-x86_64-linux-gnu.tar.gz` if you are on Linux.
diff --git a/doc/signing_devices.md b/doc/signing_devices.md
index ea54b733..5908a18f 100644
--- a/doc/signing_devices.md
+++ b/doc/signing_devices.md
@@ -30,11 +30,9 @@ panel.
 In order to be able to install the latest Bitcoin application on mainnet, one more step is required.
 Go to "settings" (top-right corner), then "Experimental features", enable "My Ledger provider" and
 insert "4" in the field. You may then go to "My Ledger" and install the 2.1.0 Bitcoin application
-for mainnet. *Do not* download any other app from the provider 4. **Make sure to disable** the "My
-Ledger provider" setting once you installed the Bitcoin app.
+for mainnet.
 
 It's worth noting that although you need to tweak with the "Experimental features" settings, the
 Bitcoin application is not experimental anymore. It was just downgraded in Ledger live from released
 to experimental in order to prevent people from upgrading by mistake and not be able to keep using
-applications not compatible with the new interface. It is however not safe to use it for installing
-any other application.
+applications not compatible with the new interface
diff --git a/gui/Cargo.lock b/gui/Cargo.lock
index 574b8a8a..e58bc908 100644
--- a/gui/Cargo.lock
+++ b/gui/Cargo.lock
@@ -1639,8 +1639,8 @@ dependencies = [
 
 [[package]]
 name = "liana"
-version = "0.2.0"
-source = "git+https://github.com/wizardsardine/liana?branch=master#8cc6cb1f86df3ee47caff848e331dc9fbc925aae"
+version = "0.1.0"
+source = "git+https://github.com/wizardsardine/liana?branch=master#bd6ecaeb285f780ea94e38e28b2d0860f1b45c97"
 dependencies = [
  "backtrace",
  "base64",
@@ -1658,7 +1658,7 @@ dependencies = [
 
 [[package]]
 name = "liana_gui"
-version = "0.2.0"
+version = "0.1.0"
 dependencies = [
  "async-hwi",
  "backtrace",
diff --git a/gui/Cargo.toml b/gui/Cargo.toml
index 4763d2ab..cb79fc76 100644
--- a/gui/Cargo.toml
+++ b/gui/Cargo.toml
@@ -1,9 +1,9 @@
 [package]
 name = "liana_gui"
-version = "0.2.0"
+version = "0.1.0"
 readme = "README.md"
 description = "Liana GUI"
-repository = "https://github.com/wizardsardine/liana"
+repository = "https://github.com/revault/liana"
 license = "BSD-3-Clause"
 authors = ["Edouard Paris <m@edouard.paris>"]
 edition = "2018"
diff --git a/gui/src/app/state/spend/detail.rs b/gui/src/app/state/spend/detail.rs
index eacb53c4..4c6f45aa 100644
--- a/gui/src/app/state/spend/detail.rs
+++ b/gui/src/app/state/spend/detail.rs
@@ -345,6 +345,9 @@ impl Action for SignAction {
                 }
             }
             Message::View(view::Message::Reload) => {
+                self.hws = Vec::new();
+                self.chosen_hw = None;
+                self.error = None;
                 return self.load(wallet, daemon);
             }
             _ => {}
diff --git a/gui/src/installer/step/descriptor.rs b/gui/src/installer/step/descriptor.rs
index 23eabb88..1dc8c5d5 100644
--- a/gui/src/installer/step/descriptor.rs
+++ b/gui/src/installer/step/descriptor.rs
@@ -14,6 +14,8 @@ use liana::{
     },
 };
 
+use async_hwi::DeviceKind;
+
 use crate::{
     app::settings::KeySetting,
     hw::{list_hardware_wallets, HardwareWallet},
@@ -635,6 +637,7 @@ impl DescriptorKeyModal for EditXpubModal {
                 self.hws = hws;
             }
             Message::Reload => {
+                self.hws = Vec::new();
                 return self.load();
             }
             Message::DefineDescriptor(message::DefineDescriptor::HWXpubImported(res)) => {
@@ -873,7 +876,8 @@ impl Step for ParticipateXpub {
             Message::ConnectedHardwareWallets(hws) => {
                 for hw in hws {
                     if let Some(xpub_hw) = self.xpubs_hw.iter_mut().find(|h| {
-                        h.hw.fingerprint() == hw.fingerprint() && h.hw.kind() == hw.kind()
+                        h.hw.kind() == hw.kind()
+                            && (h.hw.fingerprint() == hw.fingerprint() || !h.hw.is_supported())
                     }) {
                         xpub_hw.hw = hw;
                     } else {
@@ -1026,7 +1030,9 @@ pub struct RegisterDescriptor {
     descriptor: Option<MultipathDescriptor>,
     processing: bool,
     chosen_hw: Option<usize>,
-    hws: Vec<(HardwareWallet, Option<[u8; 32]>, bool)>,
+    hws: Vec<HardwareWallet>,
+    hmacs: Vec<(Fingerprint, DeviceKind, Option<[u8; 32]>)>,
+    registered: HashSet<Fingerprint>,
     error: Option<Error>,
 }
 
@@ -1037,17 +1043,13 @@ impl Step for RegisterDescriptor {
     fn update(&mut self, message: Message) -> Command<Message> {
         match message {
             Message::Select(i) => {
-                if let Some((
-                    HardwareWallet::Supported {
-                        device,
-                        fingerprint,
-                        ..
-                    },
-                    hmac,
-                    _,
-                )) = self.hws.get(i)
+                if let Some(HardwareWallet::Supported {
+                    device,
+                    fingerprint,
+                    ..
+                }) = self.hws.get(i)
                 {
-                    if hmac.is_none() {
+                    if !self.registered.contains(fingerprint) {
                         let descriptor = self.descriptor.as_ref().unwrap().to_string();
                         self.chosen_hw = Some(i);
                         self.processing = true;
@@ -1066,26 +1068,21 @@ impl Step for RegisterDescriptor {
                     Ok((fingerprint, hmac)) => {
                         if let Some(hw_h) = self
                             .hws
-                            .iter_mut()
-                            .find(|hw_h| hw_h.0.fingerprint() == Some(fingerprint))
+                            .iter()
+                            .find(|hw_h| hw_h.fingerprint() == Some(fingerprint))
                         {
-                            hw_h.1 = hmac;
-                            hw_h.2 = true;
+                            self.registered.insert(fingerprint);
+                            self.hmacs.push((fingerprint, *hw_h.kind(), hmac));
                         }
                     }
                     Err(e) => self.error = Some(e),
                 }
             }
             Message::ConnectedHardwareWallets(hws) => {
-                for hw in hws {
-                    if !self.hws.iter().any(|(h, _, _)| {
-                        h.fingerprint() == hw.fingerprint() && h.kind() == hw.kind()
-                    }) {
-                        self.hws.push((hw, None, false));
-                    }
-                }
+                self.hws = hws;
             }
             Message::Reload => {
+                self.hws = Vec::new();
                 return self.load();
             }
             _ => {}
@@ -1093,11 +1090,8 @@ impl Step for RegisterDescriptor {
         Command::none()
     }
     fn apply(&mut self, ctx: &mut Context) -> bool {
-        for (hw, token, registered) in &self.hws {
-            if *registered {
-                ctx.hws
-                    .push((*hw.kind(), hw.fingerprint().unwrap(), *token));
-            }
+        for (fingerprint, kind, token) in &self.hmacs {
+            ctx.hws.push((*kind, *fingerprint, *token));
         }
         true
     }
@@ -1113,6 +1107,7 @@ impl Step for RegisterDescriptor {
             progress,
             desc.to_string(),
             &self.hws,
+            &self.registered,
             self.error.as_ref(),
             self.processing,
             self.chosen_hw,
diff --git a/gui/src/installer/view.rs b/gui/src/installer/view.rs
index bedaaf07..b991c028 100644
--- a/gui/src/installer/view.rs
+++ b/gui/src/installer/view.rs
@@ -3,6 +3,8 @@ use iced::widget::{
 };
 use iced::{alignment, Alignment, Element, Length};
 
+use std::collections::HashSet;
+
 use liana::miniscript::bitcoin;
 
 use crate::{
@@ -606,7 +608,8 @@ pub fn participate_xpub(
 pub fn register_descriptor<'a>(
     progress: (usize, usize),
     descriptor: String,
-    hws: &'a [(HardwareWallet, Option<[u8; 32]>, bool)],
+    hws: &'a [HardwareWallet],
+    registered: &HashSet<bitcoin::util::bip32::Fingerprint>,
     error: Option<&Error>,
     processing: bool,
     chosen_hw: Option<usize>,
@@ -654,10 +657,12 @@ pub fn register_descriptor<'a>(
                             .fold(Column::new().spacing(10), |col, (i, hw)| {
                                 col.push(hw_list_view(
                                     i,
-                                    &hw.0,
+                                    hw,
                                     Some(i) == chosen_hw,
                                     processing,
-                                    hw.2,
+                                    hw.fingerprint()
+                                        .map(|fg| registered.contains(&fg))
+                                        .unwrap_or(false),
                                 ))
                             }),
                     )
diff --git a/gui/src/lib.rs b/gui/src/lib.rs
index 082e6287..3b60c731 100644
--- a/gui/src/lib.rs
+++ b/gui/src/lib.rs
@@ -11,6 +11,6 @@ use liana::Version;
 
 pub const VERSION: Version = Version {
     major: 0,
-    minor: 2,
+    minor: 1,
     patch: 0,
 };
diff --git a/src/bitcoin/d/mod.rs b/src/bitcoin/d/mod.rs
index 1558ed60..ad4fd082 100644
--- a/src/bitcoin/d/mod.rs
+++ b/src/bitcoin/d/mod.rs
@@ -10,13 +10,7 @@ use crate::{
 use utils::{block_before_date, roundup_progress};
 
 use std::{
-    cmp,
-    collections::{HashMap, HashSet},
-    convert::TryInto,
-    fs, io,
-    str::FromStr,
-    thread,
-    time::Duration,
+    cmp, collections::HashSet, convert::TryInto, fs, io, str::FromStr, thread, time::Duration,
 };
 
 use jsonrpc::{
@@ -971,22 +965,6 @@ impl BitcoinD {
             |h| self.get_block_stats(h),
         )
     }
-
-    /// Whether this transaction is in the mempool.
-    pub fn is_in_mempool(&self, txid: &bitcoin::Txid) -> bool {
-        match self
-            .make_fallible_node_request("getmempoolentry", &params!(Json::String(txid.to_string())))
-        {
-            Ok(_) => true,
-            Err(BitcoindError::Server(jsonrpc::Error::Rpc(jsonrpc::error::RpcError {
-                code: -5,
-                ..
-            }))) => false,
-            Err(e) => {
-                panic!("Unexpected error returned by bitcoind {}", e);
-            }
-        }
-    }
 }
 
 /// An entry in the 'listdescriptors' result.
@@ -1149,33 +1127,3 @@ pub struct BlockStats {
     pub time: u32,
     pub median_time_past: u32,
 }
-
-/// Make cached calls to bitcoind's `gettransaction`. It's useful for instance when coins have been
-/// created or spent in a single transaction.
-pub struct CachedTxGetter<'a> {
-    bitcoind: &'a BitcoinD,
-    cache: HashMap<bitcoin::Txid, GetTxRes>,
-}
-
-impl<'a> CachedTxGetter<'a> {
-    pub fn new(bitcoind: &'a BitcoinD) -> Self {
-        Self {
-            bitcoind,
-            cache: HashMap::new(),
-        }
-    }
-
-    /// Query a transaction. Tries to get it from the cache and falls back to calling
-    /// `gettransaction` on bitcoind. If both fail, returns None.
-    pub fn get_transaction(&mut self, txid: &bitcoin::Txid) -> Option<GetTxRes> {
-        // TODO: work around the borrow checker to avoid having to clone.
-        if let Some(res) = self.cache.get(txid) {
-            Some(res.clone())
-        } else if let Some(res) = self.bitcoind.get_transaction(txid) {
-            self.cache.insert(*txid, res);
-            self.cache.get(txid).cloned()
-        } else {
-            None
-        }
-    }
-}
diff --git a/src/bitcoin/mod.rs b/src/bitcoin/mod.rs
index ecb96fc3..2646dbee 100644
--- a/src/bitcoin/mod.rs
+++ b/src/bitcoin/mod.rs
@@ -5,11 +5,11 @@ pub mod d;
 pub mod poller;
 
 use crate::{
-    bitcoin::d::{BitcoindError, CachedTxGetter, LSBlockEntry},
+    bitcoin::d::{BitcoindError, LSBlockEntry},
     descriptors,
 };
 
-use std::{fmt, sync};
+use std::{collections::HashMap, fmt, sync};
 
 use miniscript::bitcoin;
 
@@ -58,12 +58,11 @@ pub trait BitcoinInterface: Send {
         descs: &[descriptors::InheritanceDescriptor],
     ) -> Vec<UTxO>;
 
-    /// Get all coins that were confirmed, and at what height and time. Along with "expired"
-    /// unconfirmed coins (for instance whose creating transaction may have been replaced).
+    /// Get all coins that were confirmed, and at what height and time.
     fn confirmed_coins(
         &self,
         outpoints: &[bitcoin::OutPoint],
-    ) -> (Vec<(bitcoin::OutPoint, i32, u32)>, Vec<bitcoin::OutPoint>);
+    ) -> Vec<(bitcoin::OutPoint, i32, u32)>;
 
     /// Get all coins that are being spent, and the spending txid.
     fn spending_coins(
@@ -167,34 +166,21 @@ impl BitcoinInterface for d::BitcoinD {
     fn confirmed_coins(
         &self,
         outpoints: &[bitcoin::OutPoint],
-    ) -> (Vec<(bitcoin::OutPoint, i32, u32)>, Vec<bitcoin::OutPoint>) {
-        // The confirmed and expired coins to be returned.
+    ) -> Vec<(bitcoin::OutPoint, i32, u32)> {
         let mut confirmed = Vec::with_capacity(outpoints.len());
-        let mut expired = Vec::new();
-        // Cached calls to `gettransaction`.
-        let mut tx_getter = CachedTxGetter::new(self);
 
         for op in outpoints {
-            let res = if let Some(res) = tx_getter.get_transaction(&op.txid) {
-                res
+            // TODO: batch those calls to gettransaction
+            if let Some(res) = self.get_transaction(&op.txid) {
+                if let Some(block) = res.block {
+                    confirmed.push((*op, block.height, block.time));
+                }
             } else {
                 log::error!("Transaction not in wallet for coin '{}'.", op);
-                continue;
-            };
-
-            // If the transaction was confirmed, mark the coin as such.
-            if let Some(block) = res.block {
-                confirmed.push((*op, block.height, block.time));
-                continue;
-            }
-
-            // If the transaction was dropped from the mempool, discard the coin.
-            if !self.is_in_mempool(&op.txid) {
-                expired.push(*op);
             }
         }
 
-        (confirmed, expired)
+        confirmed
     }
 
     fn spending_coins(
@@ -227,34 +213,48 @@ impl BitcoinInterface for d::BitcoinD {
         &self,
         outpoints: &[(bitcoin::OutPoint, bitcoin::Txid)],
     ) -> Vec<(bitcoin::OutPoint, bitcoin::Txid, Block)> {
-        // Spend coins to be returned.
         let mut spent = Vec::with_capacity(outpoints.len());
-        // Cached calls to `gettransaction`.
-        let mut tx_getter = CachedTxGetter::new(self);
 
+        let mut cache: HashMap<bitcoin::Txid, Option<d::GetTxRes>> = HashMap::new();
         for (op, txid) in outpoints {
-            let res = if let Some(res) = tx_getter.get_transaction(txid) {
-                res
-            } else {
-                log::error!("Could not get tx {} spending coin {}.", txid, op);
-                continue;
+            let tx: Option<&d::GetTxRes> = match cache.get(txid) {
+                Some(tx) => tx.as_ref(),
+                None => {
+                    let tx = self.get_transaction(txid);
+                    cache.insert(*txid, tx);
+                    cache.get(txid).unwrap().as_ref()
+                }
             };
 
-            // If the transaction was confirmed, mark it as such.
-            if let Some(block) = res.block {
-                spent.push((*op, *txid, block));
-                continue;
-            }
-
-            // If a conflicting transaction was confirmed instead, replace the txid of the
-            // spender for this coin with it and mark it as confirmed.
-            for txid in &res.conflicting_txs {
-                if let Some(tx) = tx_getter.get_transaction(txid) {
-                    if let Some(block) = tx.block {
-                        spent.push((*op, *txid, block))
+            // There is an immutable borrow on the cache, these txs will be added once it is
+            // dropped.
+            let mut txs_to_cache: Vec<(bitcoin::Txid, Option<d::GetTxRes>)> = Vec::new();
+
+            if let Some(tx) = tx {
+                if let Some(block) = tx.block {
+                    spent.push((*op, *txid, block));
+                } else if !tx.conflicting_txs.is_empty() {
+                    for txid in &tx.conflicting_txs {
+                        let tx: Option<&d::GetTxRes> = match cache.get(txid) {
+                            Some(tx) => tx.as_ref(),
+                            None => {
+                                let tx = self.get_transaction(txid);
+                                txs_to_cache.push((*txid, tx));
+                                txs_to_cache.last().unwrap().1.as_ref()
+                            }
+                        };
+                        if let Some(tx) = tx {
+                            if let Some(block) = tx.block {
+                                spent.push((*op, *txid, block))
+                            }
+                        }
                     }
                 }
             }
+
+            for (txid, res) in txs_to_cache {
+                cache.insert(txid, res);
+            }
         }
 
         spent
@@ -347,7 +347,7 @@ impl BitcoinInterface for sync::Arc<sync::Mutex<dyn BitcoinInterface + 'static>>
     fn confirmed_coins(
         &self,
         outpoints: &[bitcoin::OutPoint],
-    ) -> (Vec<(bitcoin::OutPoint, i32, u32)>, Vec<bitcoin::OutPoint>) {
+    ) -> Vec<(bitcoin::OutPoint, i32, u32)> {
         self.lock().unwrap().confirmed_coins(outpoints)
     }
 
diff --git a/src/bitcoin/poller/looper.rs b/src/bitcoin/poller/looper.rs
index 2718be48..807965b3 100644
--- a/src/bitcoin/poller/looper.rs
+++ b/src/bitcoin/poller/looper.rs
@@ -15,7 +15,6 @@ use miniscript::bitcoin::{self, secp256k1};
 struct UpdatedCoins {
     pub received: Vec<Coin>,
     pub confirmed: Vec<(bitcoin::OutPoint, i32, u32)>,
-    pub expired: Vec<bitcoin::OutPoint>,
     pub spending: Vec<(bitcoin::OutPoint, bitcoin::Txid)>,
     pub spent: Vec<(bitcoin::OutPoint, bitcoin::Txid, i32, u32)>,
 }
@@ -92,9 +91,8 @@ fn update_coins(
             }
         })
         .collect();
-    let (confirmed, expired) = bit.confirmed_coins(&to_be_confirmed);
+    let confirmed = bit.confirmed_coins(&to_be_confirmed);
     log::debug!("Newly confirmed coins: {:?}", confirmed);
-    log::debug!("Expired coins: {:?}", expired);
 
     // We need to take the newly received ones into account as well, as they may have been
     // spent within the previous tip and the current one, and we may not poll this chunk of the
@@ -133,7 +131,6 @@ fn update_coins(
     UpdatedCoins {
         received,
         confirmed,
-        expired,
         spending,
         spent,
     }
@@ -222,7 +219,6 @@ fn updates(
     // Having the tip in database means that, as far as the chain is concerned, we've got all
     // updates up to this block. But not more.
     db_conn.new_unspent_coins(&updated_coins.received);
-    db_conn.remove_coins(&updated_coins.expired);
     db_conn.confirm_coins(&updated_coins.confirmed);
     db_conn.spend_coins(&updated_coins.spending);
     db_conn.confirm_spend(&updated_coins.spent);
diff --git a/src/database/mod.rs b/src/database/mod.rs
index 04953c36..6662a365 100644
--- a/src/database/mod.rs
+++ b/src/database/mod.rs
@@ -89,9 +89,6 @@ pub trait DatabaseConnection {
     /// Store new UTxOs. Coins must not already be in database.
     fn new_unspent_coins(&mut self, coins: &[Coin]);
 
-    /// Remove some UTxOs from the database.
-    fn remove_coins(&mut self, coins: &[bitcoin::OutPoint]);
-
     /// Mark a set of coins as being confirmed at a specified height and block time.
     fn confirm_coins(&mut self, outpoints: &[(bitcoin::OutPoint, i32, u32)]);
 
@@ -199,10 +196,6 @@ impl DatabaseConnection for SqliteConn {
         self.new_unspent_coins(coins)
     }
 
-    fn remove_coins(&mut self, outpoints: &[bitcoin::OutPoint]) {
-        self.remove_coins(outpoints)
-    }
-
     fn confirm_coins<'a>(&mut self, outpoints: &[(bitcoin::OutPoint, i32, u32)]) {
         self.confirm_coins(outpoints)
     }
diff --git a/src/database/sqlite/mod.rs b/src/database/sqlite/mod.rs
index 17663a13..d12064b8 100644
--- a/src/database/sqlite/mod.rs
+++ b/src/database/sqlite/mod.rs
@@ -363,21 +363,6 @@ impl SqliteConn {
         .expect("Database must be available")
     }
 
-    /// Remove a set of coins from the database.
-    pub fn remove_coins(&mut self, outpoints: &[bitcoin::OutPoint]) {
-        db_exec(&mut self.conn, |db_tx| {
-            for outpoint in outpoints {
-                db_tx.execute(
-                    "DELETE FROM coins WHERE txid = ?1 AND vout = ?2",
-                    rusqlite::params![outpoint.txid.to_vec(), outpoint.vout,],
-                )?;
-            }
-
-            Ok(())
-        })
-        .expect("Database must be available")
-    }
-
     /// Mark a set of coins as confirmed.
     pub fn confirm_coins<'a>(
         &mut self,
@@ -720,13 +705,6 @@ mod tests {
             conn.new_unspent_coins(&[coin_a]);
             assert_eq!(conn.coins(CoinType::All)[0].outpoint, coin_a.outpoint);
 
-            // We can also remove it. Say the unconfirmed tx that created it got replaced.
-            conn.remove_coins(&[coin_a.outpoint]);
-            assert!(conn.coins(CoinType::All).is_empty());
-
-            // Add it back for the rest of the test.
-            conn.new_unspent_coins(&[coin_a]);
-
             // We can query it by its outpoint
             let coins = conn.db_coins(&[coin_a.outpoint]);
             assert_eq!(coins.len(), 1);
diff --git a/src/descriptors.rs b/src/descriptors.rs
index c7df1566..fa038c0d 100644
--- a/src/descriptors.rs
+++ b/src/descriptors.rs
@@ -58,7 +58,7 @@ impl std::fmt::Display for LianaDescError {
             Self::InvalidKey(key) => {
                 write!(
                     f,
-                    "Invalid key '{}'. Need a wildcard ('ranged') xpub with an origin and a multipath for (and only for) deriving change addresses. That is, an xpub of the form '[aaff0099]xpub.../<0;1>/*'.",
+                    "Invalid key '{}'. Need a wildcard ('ranged') xpub with a multipath for (and only for) deriving change addresses. That is, an xpub of the form 'xpub.../<0;1>/*'.",
                     key
                     )
             }
diff --git a/src/lib.rs b/src/lib.rs
index e4c823bb..48997ada 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -78,7 +78,7 @@ impl fmt::Display for Version {
 
 pub const VERSION: Version = Version {
     major: 0,
-    minor: 2,
+    minor: 1,
     patch: 0,
 };
 


Test patch:
diff --git a/src/testutils.rs b/src/testutils.rs
index dcdb1018..b07cdda7 100644
--- a/src/testutils.rs
+++ b/src/testutils.rs
@@ -65,11 +65,8 @@ impl BitcoinInterface for DummyBitcoind {
         Vec::new()
     }
 
-    fn confirmed_coins(
-        &self,
-        _: &[bitcoin::OutPoint],
-    ) -> (Vec<(bitcoin::OutPoint, i32, u32)>, Vec<bitcoin::OutPoint>) {
-        (Vec::new(), Vec::new())
+    fn confirmed_coins(&self, _: &[bitcoin::OutPoint]) -> Vec<(bitcoin::OutPoint, i32, u32)> {
+        Vec::new()
     }
 
     fn spending_coins(&self, _: &[bitcoin::OutPoint]) -> Vec<(bitcoin::OutPoint, bitcoin::Txid)> {
@@ -223,12 +220,6 @@ impl DatabaseConnection for DummyDatabase {
         }
     }
 
-    fn remove_coins(&mut self, outpoints: &[bitcoin::OutPoint]) {
-        for op in outpoints {
-            self.db.write().unwrap().coins.remove(op);
-        }
-    }
-
     fn confirm_coins<'a>(&mut self, outpoints: &[(bitcoin::OutPoint, i32, u32)]) {
         for (op, height, time) in outpoints {
             let mut db = self.db.write().unwrap();
diff --git a/tests/test_chain.py b/tests/test_chain.py
index 21623aef..cd63cb5a 100644
--- a/tests/test_chain.py
+++ b/tests/test_chain.py
@@ -1,10 +1,14 @@
+import time
+
 from fixtures import *
 from test_framework.utils import wait_for, get_txid, spend_coins
 
 
 def get_coin(lianad, outpoint_or_txid):
     return next(
-        c for c in lianad.rpc.listcoins()["coins"] if outpoint_or_txid in c["outpoint"]
+        c
+        for c in lianad.rpc.listcoins()["coins"]
+        if outpoint_or_txid in c["outpoint"]
     )
 
 
@@ -76,10 +80,13 @@ def test_reorg_exclusion(lianad, bitcoind):
     bitcoind.simple_reorg(initial_height, shift=-1)
     wait_for(lambda: lianad.rpc.getinfo()["block_height"] == current_height + 1)
 
-    # For a too deep reorg bitcoind doesn't update the mempool. The deposit transactions were
-    # dropped. And we discard the unconfirmed coins whose deposit tx isn't part of our mempool
-    # anymore: the coins must have been marked as unconfirmed and subsequently discarded.
-    wait_for(lambda: len(lianad.rpc.listcoins()["coins"]) == 0)
+    # They must all be marked as unconfirmed.
+    new_coin_a = get_coin(lianad, coin_a["outpoint"])
+    assert new_coin_a["block_height"] is None
+    new_coin_b = get_coin(lianad, coin_b["outpoint"])
+    assert new_coin_b["block_height"] is None
+    new_coin_c = get_coin(lianad, coin_c["outpoint"])
+    assert new_coin_c["block_height"] is None
 
     # And if we now confirm everything, they'll be marked as such. The one that was 'spending'
     # will now be spent (its spending transaction will be confirmed) and the one that was spent
@@ -250,50 +257,3 @@ def test_rescan_edge_cases(lianad, bitcoind):
     wait_for(lambda: lianad.rpc.getinfo()["rescan_progress"] is None)
     assert len(sorted_coins()) == len(coins_before)
     assert all(c["outpoint"] in outpoints_before for c in list_coins())
-
-
-def test_deposit_replacement(lianad, bitcoind):
-    """Test we discard an unconfirmed deposit that was replaced."""
-    # Get some more coins.
-    bitcoind.generate_block(1)
-
-    # Create a new unconfirmed deposit.
-    addr = lianad.rpc.getnewaddress()["address"]
-    txid = bitcoind.rpc.sendtoaddress(addr, 1)
-
-    # Create a transaction conflicting with the deposit that pays more fee.
-    deposit_tx = bitcoind.rpc.gettransaction(txid, False, True)["decoded"]
-    bitcoind.rpc.lockunspent(
-        False,
-        [
-            {"txid": deposit_tx["txid"], "vout": i}
-            for i in range(len(deposit_tx["vout"]))
-        ],
-    )
-    res = bitcoind.rpc.walletcreatefundedpsbt(
-        [
-            {"txid": txin["txid"], "vout": txin["vout"], "sequence": 0xFF_FF_FF_FD}
-            for txin in deposit_tx["vin"]
-        ],
-        [
-            {bitcoind.rpc.getnewaddress(): txout["value"]}
-            for txout in deposit_tx["vout"]
-        ],
-        0,
-        {"fee_rate": 10, "add_inputs": True},
-    )
-    res = bitcoind.rpc.walletprocesspsbt(res["psbt"])
-    assert res["complete"]
-    conflicting_tx = bitcoind.rpc.finalizepsbt(res["psbt"])["hex"]
-
-    # Make sure we registered the unconfirmed coin. Then RBF the deposit tx.
-    wait_for(lambda: len(lianad.rpc.listcoins()["coins"]) == 1)
-    txid = bitcoind.rpc.sendrawtransaction(conflicting_tx)
-
-    # We must forget about the deposit.
-    wait_for(lambda: len(lianad.rpc.listcoins()["coins"]) == 0)
-
-    # Send a new one, it'll be detected.
-    addr = lianad.rpc.getnewaddress()["address"]
-    bitcoind.rpc.sendtoaddress(addr, 2)
-    wait_for(lambda: len(lianad.rpc.listcoins()["coins"]) == 1)
diff --git a/tests/test_rpc.py b/tests/test_rpc.py
index 66d2103c..83f035c6 100644
--- a/tests/test_rpc.py
+++ b/tests/test_rpc.py
@@ -20,7 +20,7 @@ from test_framework.utils import (
 
 def test_getinfo(lianad):
     res = lianad.rpc.getinfo()
-    assert res["version"] == "0.2.0"
+    assert res["version"] == "0.1"
     assert res["network"] == "regtest"
     wait_for(lambda: lianad.rpc.getinfo()["block_height"] == 101)
     res = lianad.rpc.getinfo()