diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py
index 6598f1a..2f8b432 100644
--- a/basicswap/basicswap.py
+++ b/basicswap/basicswap.py
@@ -599,7 +599,12 @@ class BasicSwap(BaseApp):
         }
 
         # Passthrough settings
-        for setting_name in ("wallet_name", "mweb_wallet_name"):
+        for setting_name in (
+            "use_descriptors",
+            "wallet_name",
+            "watch_wallet_name",
+            "mweb_wallet_name",
+        ):
             if setting_name in chain_client_settings:
                 self.coin_clients[coin][setting_name] = chain_client_settings[
                     setting_name
diff --git a/basicswap/bin/prepare.py b/basicswap/bin/prepare.py
index a01f9a1..1c11aff 100755
--- a/basicswap/bin/prepare.py
+++ b/basicswap/bin/prepare.py
@@ -1884,6 +1884,10 @@ def initialise_wallets(
 
                         if c in (Coins.BTC, Coins.LTC, Coins.DOGE, Coins.DASH):
                             # wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors
+
+                            use_descriptors = coin_settings.get(
+                                "use_descriptors", False
+                            )
                             swap_client.callcoinrpc(
                                 c,
                                 "createwallet",
@@ -1893,9 +1897,22 @@ def initialise_wallets(
                                     True,
                                     WALLET_ENCRYPTION_PWD,
                                     False,
-                                    False,
+                                    use_descriptors,
                                 ],
                             )
+                            if use_descriptors:
+                                swap_client.callcoinrpc(
+                                    c,
+                                    "createwallet",
+                                    [
+                                        coin_settings["watch_wallet_name"],
+                                        True,
+                                        True,
+                                        "",
+                                        False,
+                                        use_descriptors,
+                                    ],
+                                )
                             swap_client.ci(c).unlockWallet(WALLET_ENCRYPTION_PWD)
                         else:
                             swap_client.callcoinrpc(
@@ -2538,6 +2555,17 @@ def main():
         if set_name != default_name:
             coin_settings["wallet_name"] = set_name
 
+        ticker: str = coin_params["ticker"]
+        if toBool(os.getenv(ticker + "_USE_DESCRIPTORS", False)):
+
+            if coin_id not in (Coins.BTC,):
+                raise ValueError(f"Descriptor wallet unavailable for {coin_name}")
+
+            coin_settings["use_descriptors"] = True
+            coin_settings["watch_wallet_name"] = getWalletName(
+                coin_params, "bsx_watch", prefix_override=f"{ticker}_WATCH"
+            )
+
     if PART_RPC_USER != "":
         chainclients["particl"]["rpcuser"] = PART_RPC_USER
         chainclients["particl"]["rpcpassword"] = PART_RPC_PWD
diff --git a/basicswap/chainparams.py b/basicswap/chainparams.py
index 07b2376..188e7d5 100644
--- a/basicswap/chainparams.py
+++ b/basicswap/chainparams.py
@@ -91,6 +91,8 @@ chainparams = {
             "bip44": 0,
             "min_amount": 100000,
             "max_amount": 10000000 * COIN,
+            "ext_public_key_prefix": 0x0488B21E,
+            "ext_secret_key_prefix": 0x0488ADE4,
         },
         "testnet": {
             "rpcport": 18332,
@@ -102,6 +104,8 @@ chainparams = {
             "min_amount": 100000,
             "max_amount": 10000000 * COIN,
             "name": "testnet3",
+            "ext_public_key_prefix": 0x043587CF,
+            "ext_secret_key_prefix": 0x04358394,
         },
         "regtest": {
             "rpcport": 18443,
@@ -112,6 +116,8 @@ chainparams = {
             "bip44": 1,
             "min_amount": 100000,
             "max_amount": 10000000 * COIN,
+            "ext_public_key_prefix": 0x043587CF,
+            "ext_secret_key_prefix": 0x04358394,
         },
     },
     Coins.LTC: {
diff --git a/basicswap/contrib/test_framework/descriptors.py b/basicswap/contrib/test_framework/descriptors.py
new file mode 100644
index 0000000..46b4057
--- /dev/null
+++ b/basicswap/contrib/test_framework/descriptors.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python3
+# Copyright (c) 2019 Pieter Wuille
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+"""Utility functions related to output descriptors"""
+
+import re
+
+INPUT_CHARSET = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ "
+CHECKSUM_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
+GENERATOR = [0xf5dee51989, 0xa9fdca3312, 0x1bab10e32d, 0x3706b1677a, 0x644d626ffd]
+
+def descsum_polymod(symbols):
+    """Internal function that computes the descriptor checksum."""
+    chk = 1
+    for value in symbols:
+        top = chk >> 35
+        chk = (chk & 0x7ffffffff) << 5 ^ value
+        for i in range(5):
+            chk ^= GENERATOR[i] if ((top >> i) & 1) else 0
+    return chk
+
+def descsum_expand(s):
+    """Internal function that does the character to symbol expansion"""
+    groups = []
+    symbols = []
+    for c in s:
+        if not c in INPUT_CHARSET:
+            return None
+        v = INPUT_CHARSET.find(c)
+        symbols.append(v & 31)
+        groups.append(v >> 5)
+        if len(groups) == 3:
+            symbols.append(groups[0] * 9 + groups[1] * 3 + groups[2])
+            groups = []
+    if len(groups) == 1:
+        symbols.append(groups[0])
+    elif len(groups) == 2:
+        symbols.append(groups[0] * 3 + groups[1])
+    return symbols
+
+def descsum_create(s):
+    """Add a checksum to a descriptor without"""
+    symbols = descsum_expand(s) + [0, 0, 0, 0, 0, 0, 0, 0]
+    checksum = descsum_polymod(symbols) ^ 1
+    return s + '#' + ''.join(CHECKSUM_CHARSET[(checksum >> (5 * (7 - i))) & 31] for i in range(8))
+
+def descsum_check(s, require=True):
+    """Verify that the checksum is correct in a descriptor"""
+    if not '#' in s:
+        return not require
+    if s[-9] != '#':
+        return False
+    if not all(x in CHECKSUM_CHARSET for x in s[-8:]):
+        return False
+    symbols = descsum_expand(s[:-9]) + [CHECKSUM_CHARSET.find(x) for x in s[-8:]]
+    return descsum_polymod(symbols) == 1
+
+def drop_origins(s):
+    '''Drop the key origins from a descriptor'''
+    desc = re.sub(r'\[.+?\]', '', s)
+    if '#' in s:
+        desc = desc[:desc.index('#')]
+    return descsum_create(desc)
diff --git a/basicswap/interface/btc.py b/basicswap/interface/btc.py
index 3041f18..1f9b25b 100644
--- a/basicswap/interface/btc.py
+++ b/basicswap/interface/btc.py
@@ -18,12 +18,7 @@ from basicswap.basicswap_util import (
     getVoutByAddress,
     getVoutByScriptPubKey,
 )
-from basicswap.contrib.test_framework import (
-    segwit_addr,
-)
-from basicswap.interface.base import (
-    Secp256k1Interface,
-)
+from basicswap.interface.base import Secp256k1Interface
 from basicswap.util import (
     ensure,
     b2h,
@@ -35,6 +30,7 @@ from basicswap.util.ecc import (
     pointToCPK,
     CPKToPoint,
 )
+from basicswap.util.extkey import ExtKeyPair
 from basicswap.util.script import (
     decodeScriptNum,
     getCompactSizeLen,
@@ -44,6 +40,7 @@ from basicswap.util.script import (
 from basicswap.util.address import (
     toWIF,
     b58encode,
+    b58decode,
     decodeWif,
     decodeAddress,
     pubkeyToAddress,
@@ -63,6 +60,8 @@ from coincurve.ecdsaotves import (
     ecdsaotves_rec_enc_key,
 )
 
+from basicswap.contrib.test_framework import segwit_addr
+from basicswap.contrib.test_framework.descriptors import descsum_create
 from basicswap.contrib.test_framework.messages import (
     COIN,
     COutPoint,
@@ -267,9 +266,21 @@ class BTCInterface(Secp256k1Interface):
         self._rpcauth = coin_settings["rpcauth"]
         self.rpc = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host)
         self._rpc_wallet = coin_settings.get("wallet_name", "wallet.dat")
+        self._rpc_wallet_watch = coin_settings.get(
+            "watch_wallet_name", self._rpc_wallet
+        )
         self.rpc_wallet = make_rpc_func(
             self._rpcport, self._rpcauth, host=self._rpc_host, wallet=self._rpc_wallet
         )
+        if self._rpc_wallet_watch == self._rpc_wallet:
+            self.rpc_wallet_watch = self.rpc_wallet
+        else:
+            self.rpc_wallet_watch = make_rpc_func(
+                self._rpcport,
+                self._rpcauth,
+                host=self._rpc_host,
+                wallet=self._rpc_wallet_watch,
+            )
         self.blocks_confirmed = coin_settings["blocks_confirmed"]
         self.setConfTarget(coin_settings["conf_target"])
         self._use_segwit = coin_settings["use_segwit"]
@@ -278,6 +289,7 @@ class BTCInterface(Secp256k1Interface):
         self._log = self._sc.log if self._sc and self._sc.log else logging
         self._expect_seedid_hex = None
         self._altruistic = coin_settings.get("altruistic", True)
+        self._use_descriptors = coin_settings.get("use_descriptors", False)
 
     def open_rpc(self, wallet=None):
         return openrpc(self._rpcport, self._rpcauth, wallet=wallet, host=self._rpc_host)
@@ -360,9 +372,40 @@ class BTCInterface(Secp256k1Interface):
         raise ValueError(f"Block header not found at time: {time}")
 
     def initialiseWallet(self, key_bytes: bytes) -> None:
-        key_wif = self.encodeKey(key_bytes)
-        self.rpc_wallet("sethdseed", [True, key_wif])
+        assert len(key_bytes) == 32
         self._have_checked_seed = False
+        if self._use_descriptors:
+            self._log.info("Importing descriptors")
+            ek = ExtKeyPair()
+            ek.set_seed(key_bytes)
+            ek_encoded: str = self.encode_secret_extkey(ek.encode_v())
+            desc_external = descsum_create(f"wpkh({ek_encoded}/0h/0h/*h)")
+            desc_internal = descsum_create(f"wpkh({ek_encoded}/0h/1h/*h)")
+            rv = self.rpc_wallet(
+                "importdescriptors",
+                [
+                    [
+                        {"desc": desc_external, "timestamp": "now", "active": True},
+                        {
+                            "desc": desc_internal,
+                            "timestamp": "now",
+                            "active": True,
+                            "internal": True,
+                        },
+                    ],
+                ],
+            )
+
+            num_successful: int = 0
+            for entry in rv:
+                if entry.get("success", False) is True:
+                    num_successful += 1
+            if num_successful != 2:
+                self._log.error(f"Failed to import descriptors: {rv}.")
+                raise ValueError("Failed to import descriptors.")
+        else:
+            key_wif = self.encodeKey(key_bytes)
+            self.rpc_wallet("sethdseed", [True, key_wif])
 
     def getWalletInfo(self):
         rv = self.rpc_wallet("getwalletinfo")
@@ -372,7 +415,14 @@ class BTCInterface(Secp256k1Interface):
         return rv
 
     def getWalletRestoreHeight(self) -> int:
-        start_time = self.rpc_wallet("getwalletinfo")["keypoololdest"]
+        if self._use_descriptors:
+            descriptor = self.getActiveDescriptor()
+            if descriptor is None:
+                start_time = 0
+            else:
+                start_time = descriptor["timestamp"]
+        else:
+            start_time = self.rpc_wallet("getwalletinfo")["keypoololdest"]
 
         blockchaininfo = self.getBlockchainInfo()
         best_block = blockchaininfo["bestblockhash"]
@@ -392,6 +442,8 @@ class BTCInterface(Secp256k1Interface):
                 )
                 if block_header["time"] < start_time:
                     return block_header["height"]
+                if "previousblockhash" not in block_header:  # Genesis block
+                    return block_header["height"]
                 block_hash = block_header["previousblockhash"]
         finally:
             self.close_rpc(rpc_conn)
@@ -401,7 +453,32 @@ class BTCInterface(Secp256k1Interface):
         wi = self.rpc_wallet("getwalletinfo")
         return "Not found" if "hdseedid" not in wi else wi["hdseedid"]
 
+    def getActiveDescriptor(self):
+        descriptors = self.rpc_wallet("listdescriptors")["descriptors"]
+        for descriptor in descriptors:
+            if (
+                descriptor["desc"].startswith("wpkh")
+                and descriptor["active"] is True
+                and descriptor["internal"] is False
+            ):
+                return descriptor
+        return None
+
     def checkExpectedSeed(self, expect_seedid: str) -> bool:
+        if self._use_descriptors:
+            descriptor = self.getActiveDescriptor()
+            if descriptor is None:
+                self._log.debug("Could not find active descriptor.")
+                return False
+
+            end = descriptor["desc"].find("/")
+            if end < 10:
+                return False
+            extkey = descriptor["desc"][5:end]
+            extkey_data = b58decode(extkey)[4:-4]
+            extkey_data_hash: bytes = hash160(extkey_data)
+            return True if extkey_data_hash.hex() == expect_seedid else False
+
         wallet_seed_id = self.getWalletSeedID()
         self._expect_seedid_hex = expect_seedid
         self._have_checked_seed = True
@@ -426,6 +503,10 @@ class BTCInterface(Secp256k1Interface):
         addr_info = self.rpc_wallet("getaddressinfo", [address])
         if not or_watch_only:
             return addr_info["ismine"]
+
+        if self._use_descriptors:
+            addr_info = self.rpc_wallet_watch("getaddressinfo", [address])
+
         return addr_info["ismine"] or addr_info["iswatchonly"]
 
     def checkAddressMine(self, address: str) -> None:
@@ -493,6 +574,20 @@ class BTCInterface(Secp256k1Interface):
         pkh = hash160(pk)
         return segwit_addr.encode(bech32_prefix, version, pkh)
 
+    def encode_secret_extkey(self, ek_data: bytes) -> str:
+        assert len(ek_data) == 74
+        prefix = self.chainparams_network()["ext_secret_key_prefix"]
+        data: bytes = prefix.to_bytes(4, "big") + ek_data
+        checksum = sha256(sha256(data))
+        return b58encode(data + checksum[0:4])
+
+    def encode_public_extkey(self, ek_data: bytes) -> str:
+        assert len(ek_data) == 74
+        prefix = self.chainparams_network()["ext_public_key_prefix"]
+        data: bytes = prefix.to_bytes(4, "big") + ek_data
+        checksum = sha256(sha256(data))
+        return b58encode(data + checksum[0:4])
+
     def pkh_to_address(self, pkh: bytes) -> str:
         # pkh is ripemd160(sha256(pk))
         assert len(pkh) == 20
@@ -528,7 +623,12 @@ class BTCInterface(Secp256k1Interface):
         pk = self.getPubkey(key)
         return hash160(pk)
 
-    def getSeedHash(self, seed) -> bytes:
+    def getSeedHash(self, seed: bytes) -> bytes:
+        if self._use_descriptors:
+            ek = ExtKeyPair()
+            ek.set_seed(seed)
+            return hash160(ek.encode_p())
+
         return self.getAddressHashFromKey(seed)[::-1]
 
     def encodeKey(self, key_bytes: bytes) -> str:
@@ -1411,7 +1511,7 @@ class BTCInterface(Secp256k1Interface):
         script_pk = self.getPkDest(Kbs)
 
         if locked_n is None:
-            wtx = self.rpc_wallet(
+            wtx = self.rpc_wallet_watch(
                 "gettransaction",
                 [
                     chain_b_lock_txid.hex(),
@@ -1448,10 +1548,23 @@ class BTCInterface(Secp256k1Interface):
 
         return bytes.fromhex(self.publishTx(b_lock_spend_tx))
 
-    def importWatchOnlyAddress(self, address: str, label: str):
+    def importWatchOnlyAddress(self, address: str, label: str) -> None:
+        if self._use_descriptors:
+            desc_watch = descsum_create(f"addr({address})")
+            rv = self.rpc_wallet_watch(
+                "importdescriptors",
+                [
+                    [
+                        {"desc": desc_watch, "timestamp": "now", "active": False},
+                    ],
+                ],
+            )
+            ensure(rv[0]["success"] is True, "importdescriptors failed for watchonly")
+            return
+
         self.rpc_wallet("importaddress", [address, label, False])
 
-    def isWatchOnlyAddress(self, address: str):
+    def isWatchOnlyAddress(self, address: str) -> bool:
         addr_info = self.rpc_wallet("getaddressinfo", [address])
         return addr_info["iswatchonly"]
 
@@ -1481,7 +1594,7 @@ class BTCInterface(Secp256k1Interface):
 
         return_txid = True if txid is None else False
         if txid is None:
-            txns = self.rpc_wallet(
+            txns = self.rpc_wallet_watch(
                 "listunspent",
                 [
                     0,
@@ -1502,7 +1615,7 @@ class BTCInterface(Secp256k1Interface):
 
         try:
             # set `include_watchonly` explicitly to `True` to get transactions for watchonly addresses also in BCH
-            tx = self.rpc_wallet("gettransaction", [txid.hex(), True])
+            tx = self.rpc_wallet_watch("gettransaction", [txid.hex(), True])
 
             block_height = 0
             if "blockhash" in tx:
diff --git a/tests/basicswap/common.py b/tests/basicswap/common.py
index 4e9d3d5..f8d1a33 100644
--- a/tests/basicswap/common.py
+++ b/tests/basicswap/common.py
@@ -2,7 +2,7 @@
 # -*- coding: utf-8 -*-
 
 # Copyright (c) 2020-2024 tecnovert
-# Copyright (c) 2024 The Basicswap developers
+# Copyright (c) 2024-2025 The Basicswap developers
 # Distributed under the MIT software license, see the accompanying
 # file LICENSE.txt or http://www.opensource.org/licenses/mit-license.php.
 
@@ -14,6 +14,7 @@ from urllib.request import urlopen
 
 from .util import read_json_api
 from basicswap.rpc import callrpc
+from basicswap.util import toBool
 from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
 from basicswap.bin.prepare import downloadPIVXParams
 
@@ -44,6 +45,8 @@ PIVX_BASE_ZMQ_PORT = 36892
 
 PREFIX_SECRET_KEY_REGTEST = 0x2E
 
+BTC_USE_DESCRIPTORS = toBool(os.getenv("BTC_USE_DESCRIPTORS", False))
+
 
 def prepareDataDir(
     datadir,
diff --git a/tests/basicswap/common_xmr.py b/tests/basicswap/common_xmr.py
index 1324494..97312f1 100644
--- a/tests/basicswap/common_xmr.py
+++ b/tests/basicswap/common_xmr.py
@@ -19,13 +19,10 @@ from io import StringIO
 from urllib.request import urlopen
 from unittest.mock import patch
 
-from basicswap.rpc_xmr import (
-    callrpc_xmr,
-)
+from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
+from basicswap.rpc_xmr import callrpc_xmr
 from tests.basicswap.mnemonics import mnemonics
-from tests.basicswap.util import (
-    waitForServer,
-)
+from tests.basicswap.util import waitForServer
 from tests.basicswap.common import (
     BASE_PORT,
     BASE_RPC_PORT,
@@ -35,6 +32,7 @@ from tests.basicswap.common import (
     LTC_BASE_PORT,
     LTC_BASE_RPC_PORT,
     PIVX_BASE_PORT,
+    BTC_USE_DESCRIPTORS,
 )
 from tests.basicswap.extended.test_dcr import (
     DCR_BASE_PORT,
@@ -49,8 +47,6 @@ from tests.basicswap.extended.test_doge import (
     DOGE_BASE_RPC_PORT,
 )
 
-from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
-
 import basicswap.config as cfg
 import basicswap.bin.run as runSystem
 
@@ -133,6 +129,7 @@ def run_prepare(
     os.environ["PART_RPC_PORT"] = str(PARTICL_RPC_PORT_BASE)
     os.environ["BTC_RPC_PORT"] = str(BITCOIN_RPC_PORT_BASE)
     os.environ["BTC_PORT"] = str(BITCOIN_PORT_BASE)
+    os.environ["BTC_USE_DESCRIPTORS"] = str(BTC_USE_DESCRIPTORS)
     os.environ["LTC_RPC_PORT"] = str(LITECOIN_RPC_PORT_BASE)
     os.environ["DCR_RPC_PORT"] = str(DECRED_RPC_PORT_BASE)
     os.environ["FIRO_RPC_PORT"] = str(FIRO_RPC_PORT_BASE)
diff --git a/tests/basicswap/test_btc_xmr.py b/tests/basicswap/test_btc_xmr.py
index 54d0145..35335d8 100644
--- a/tests/basicswap/test_btc_xmr.py
+++ b/tests/basicswap/test_btc_xmr.py
@@ -26,6 +26,7 @@ from basicswap.db import (
 from basicswap.util import (
     make_int,
 )
+from basicswap.util.extkey import ExtKeyPair
 from basicswap.interface.base import Curves
 from tests.basicswap.util import (
     read_json_api,
@@ -40,6 +41,7 @@ from tests.basicswap.common import (
     wait_for_none_active,
     BTC_BASE_RPC_PORT,
 )
+from basicswap.contrib.test_framework.descriptors import descsum_create
 from basicswap.contrib.test_framework.messages import (
     ToHex,
     FromHex,
@@ -58,6 +60,8 @@ from .test_xmr import BaseTest, test_delay_event, callnoderpc
 
 logger = logging.getLogger()
 
+test_seed = "8e54a313e6df8918df6d758fafdbf127a115175fdd2238d0e908dd8093c9ac3b"
+
 
 class TestFunctions(BaseTest):
     base_rpc_port = None
@@ -1166,7 +1170,6 @@ class BasicSwapTest(TestFunctions):
         logging.info("---------- Test {} hdwallet".format(self.test_coin_from.name))
         ci = self.swap_clients[0].ci(self.test_coin_from)
 
-        test_seed = "8e54a313e6df8918df6d758fafdbf127a115175fdd2238d0e908dd8093c9ac3b"
         test_wif = (
             self.swap_clients[0]
             .ci(self.test_coin_from)
@@ -1178,10 +1181,35 @@ class BasicSwapTest(TestFunctions):
             "createwallet", [new_wallet_name, False, True, "", False, False]
         )
         self.callnoderpc("sethdseed", [True, test_wif], wallet=new_wallet_name)
+
+        wi = self.callnoderpc("getwalletinfo", wallet=new_wallet_name)
+        assert wi["hdseedid"] == "3da5c0af91879e8ce97d9a843874601c08688078"
+
         addr = self.callnoderpc("getnewaddress", wallet=new_wallet_name)
-        self.callnoderpc("unloadwallet", [new_wallet_name])
+        addr_info = self.callnoderpc(
+            "getaddressinfo",
+            [
+                addr,
+            ],
+            wallet=new_wallet_name,
+        )
+        assert addr_info["hdmasterfingerprint"] == "a55b7ea9"
+        assert addr_info["hdkeypath"] == "m/0'/0'/0'"
         assert addr == "bcrt1qps7hnjd866e9ynxadgseprkc2l56m00dvwargr"
 
+        addr_change = self.callnoderpc("getrawchangeaddress", wallet=new_wallet_name)
+        addr_info = self.callnoderpc(
+            "getaddressinfo",
+            [
+                addr_change,
+            ],
+            wallet=new_wallet_name,
+        )
+        assert addr_info["hdmasterfingerprint"] == "a55b7ea9"
+        assert addr_info["hdkeypath"] == "m/0'/1'/0'"
+        assert addr_change == "bcrt1qdl9ryxkqjltv42lhfnqgdjf9tagxsjpp2xak9a"
+        self.callnoderpc("unloadwallet", [new_wallet_name])
+
         self.swap_clients[0].initialiseWallet(Coins.BTC, raise_errors=True)
         assert self.swap_clients[0].checkWalletSeed(Coins.BTC) is True
         for i in range(1500):
@@ -1561,6 +1589,97 @@ class BasicSwapTest(TestFunctions):
         )
         assert len(tx_wallet["blockhash"]) == 64
 
+    def test_013_descriptor_wallet(self):
+        logging.info(f"---------- Test {self.test_coin_from.name} descriptor wallet")
+
+        ci = self.swap_clients[0].ci(self.test_coin_from)
+
+        ek = ExtKeyPair()
+        ek.set_seed(bytes.fromhex(test_seed))
+        ek_encoded: str = ci.encode_secret_extkey(ek.encode_v())
+        new_wallet_name = "descriptors_" + random.randbytes(10).hex()
+        new_watch_wallet_name = "watch_descriptors_" + random.randbytes(10).hex()
+        # wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors
+        ci.rpc("createwallet", [new_wallet_name, False, True, "", False, True])
+        ci.rpc("createwallet", [new_watch_wallet_name, True, True, "", False, True])
+
+        desc_external = descsum_create(f"wpkh({ek_encoded}/0h/0h/*h)")
+        desc_internal = descsum_create(f"wpkh({ek_encoded}/0h/1h/*h)")
+        self.callnoderpc(
+            "importdescriptors",
+            [
+                [
+                    {
+                        "desc": desc_external,
+                        "timestamp": "now",
+                        "active": True,
+                        "range": [0, 10],
+                        "next_index": 0,
+                    },
+                    {
+                        "desc": desc_internal,
+                        "timestamp": "now",
+                        "active": True,
+                        "internal": True,
+                    },
+                ],
+            ],
+            wallet=new_wallet_name,
+        )
+
+        addr = self.callnoderpc("getnewaddress", wallet=new_wallet_name)
+        addr_info = self.callnoderpc(
+            "getaddressinfo",
+            [
+                addr,
+            ],
+            wallet=new_wallet_name,
+        )
+        assert addr_info["hdmasterfingerprint"] == "a55b7ea9"
+        assert addr_info["hdkeypath"] == "m/0h/0h/0h"
+        assert addr == "bcrt1qps7hnjd866e9ynxadgseprkc2l56m00dvwargr"
+
+        addr_change = self.callnoderpc("getrawchangeaddress", wallet=new_wallet_name)
+        addr_info = self.callnoderpc(
+            "getaddressinfo",
+            [
+                addr_change,
+            ],
+            wallet=new_wallet_name,
+        )
+        assert addr_info["hdmasterfingerprint"] == "a55b7ea9"
+        assert addr_info["hdkeypath"] == "m/0h/1h/0h"
+        assert addr_change == "bcrt1qdl9ryxkqjltv42lhfnqgdjf9tagxsjpp2xak9a"
+
+        desc_watch = descsum_create(f"addr({addr})")
+        self.callnoderpc(
+            "importdescriptors",
+            [
+                [
+                    {"desc": desc_watch, "timestamp": "now", "active": False},
+                ],
+            ],
+            wallet=new_watch_wallet_name,
+        )
+        ci.rpc_wallet("sendtoaddress", [addr, 1])
+        found: bool = False
+        for i in range(10):
+            txn_list = self.callnoderpc(
+                "listtransactions", ["*", 100, 0, True], wallet=new_watch_wallet_name
+            )
+            test_delay_event.wait(1)
+            if len(txn_list) > 0:
+                found = True
+                break
+        assert found
+
+        # Test that addresses can be generated beyond range in listdescriptors
+        for i in range(2000):
+            self.callnoderpc("getnewaddress", wallet=new_wallet_name)
+
+        self.callnoderpc("unloadwallet", [new_wallet_name])
+        self.callnoderpc("unloadwallet", [new_watch_wallet_name])
+
     def test_01_0_lock_bad_prevouts(self):
         logging.info(
             "---------- Test {} lock_bad_prevouts".format(self.test_coin_from.name)
@@ -1862,11 +1981,11 @@ class TestBTC(BasicSwapTest):
         assert "seed is set from the Basicswap mnemonic" in rv["error"]
 
         rv = read_json_api(1800, "getcoinseed", {"coin": "BTC"})
-        assert (
-            rv["seed"]
-            == "8e54a313e6df8918df6d758fafdbf127a115175fdd2238d0e908dd8093c9ac3b"
+        assert rv["seed"] == test_seed
+        assert rv["seed_id"] in (
+            "3da5c0af91879e8ce97d9a843874601c08688078",
+            "4a231080ec6f4078e543d39cc6dcf0b922c9b16b",
         )
-        assert rv["seed_id"] == "3da5c0af91879e8ce97d9a843874601c08688078"
         assert rv["seed_id"] == rv["expected_seed_id"]
 
         rv = read_json_api(
diff --git a/tests/basicswap/test_xmr.py b/tests/basicswap/test_xmr.py
index 527f335..f85f8f5 100644
--- a/tests/basicswap/test_xmr.py
+++ b/tests/basicswap/test_xmr.py
@@ -83,6 +83,7 @@ from tests.basicswap.common import (
     LTC_BASE_PORT,
     LTC_BASE_RPC_PORT,
     PREFIX_SECRET_KEY_REGTEST,
+    BTC_USE_DESCRIPTORS,
 )
 from basicswap.db_util import (
     remove_expired_data,
@@ -172,6 +173,7 @@ def prepare_swapclient_dir(
                 "datadir": os.path.join(datadir, "btc_" + str(node_id)),
                 "bindir": cfg.BITCOIN_BINDIR,
                 "use_segwit": True,
+                "use_descriptors": BTC_USE_DESCRIPTORS,
             },
         },
         "check_progress_seconds": 2,
@@ -189,6 +191,9 @@ def prepare_swapclient_dir(
         "restrict_unknown_seed_wallets": False,
     }
 
+    if BTC_USE_DESCRIPTORS:
+        settings["chainclients"]["bitcoin"]["watch_wallet_name"] = "bsx_watch"
+
     if Coins.XMR in with_coins:
         settings["chainclients"]["monero"] = {
             "connection_type": "rpc",
@@ -474,25 +479,29 @@ class BaseTest(unittest.TestCase):
                     if os.path.exists(
                         os.path.join(cfg.BITCOIN_BINDIR, "bitcoin-wallet")
                     ):
-                        try:
-                            callrpc_cli(
-                                cfg.BITCOIN_BINDIR,
-                                data_dir,
-                                "regtest",
-                                "-wallet=wallet.dat -legacy create",
-                                "bitcoin-wallet",
-                            )
-                        except Exception as e:
-                            logging.warning(
-                                f"bitcoin-wallet create failed {e}, retrying without -legacy"
-                            )
-                            callrpc_cli(
-                                cfg.BITCOIN_BINDIR,
-                                data_dir,
-                                "regtest",
-                                "-wallet=wallet.dat create",
-                                "bitcoin-wallet",
-                            )
+                        if BTC_USE_DESCRIPTORS:
+                            # How to set blank and disable_private_keys with wallet util?
+                            pass
+                        else:
+                            try:
+                                callrpc_cli(
+                                    cfg.BITCOIN_BINDIR,
+                                    data_dir,
+                                    "regtest",
+                                    "-wallet=wallet.dat -legacy create",
+                                    "bitcoin-wallet",
+                                )
+                            except Exception as e:
+                                logging.warning(
+                                    f"bitcoin-wallet create failed {e}, retrying without -legacy"
+                                )
+                                callrpc_cli(
+                                    cfg.BITCOIN_BINDIR,
+                                    data_dir,
+                                    "regtest",
+                                    "-wallet=wallet.dat create",
+                                    "bitcoin-wallet",
+                                )
 
                 cls.btc_daemons.append(
                     startDaemon(
@@ -505,9 +514,21 @@ class BaseTest(unittest.TestCase):
                     "Started %s %d", cfg.BITCOIND, cls.part_daemons[-1].handle.pid
                 )
 
-                waitForRPC(
-                    make_rpc_func(i, base_rpc_port=BTC_BASE_RPC_PORT), test_delay_event
-                )
+                if BTC_USE_DESCRIPTORS:
+                    rpc_func = make_rpc_func(i, base_rpc_port=BTC_BASE_RPC_PORT)
+                    waitForRPC(
+                        rpc_func, test_delay_event, rpc_command="getblockchaininfo"
+                    )
+                    # wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors
+                    rpc_func(
+                        "createwallet", ["wallet.dat", False, True, "", False, True]
+                    )
+                    rpc_func("createwallet", ["bsx_watch", True, True, "", False, True])
+                else:
+                    waitForRPC(
+                        make_rpc_func(i, base_rpc_port=BTC_BASE_RPC_PORT),
+                        test_delay_event,
+                    )
 
             if cls.start_ltc_nodes:
                 for i in range(NUM_LTC_NODES):
@@ -658,6 +679,11 @@ class BaseTest(unittest.TestCase):
                         xmr_ci.getMainWalletAddress(),
                     )
 
+                if BTC_USE_DESCRIPTORS:
+                    # sc.initialiseWallet(Coins.BTC)
+                    # Import a random seed to keep the existing test behaviour. BTC core rescans even with timestamp: now.
+                    sc.ci(Coins.BTC).initialiseWallet(random.randbytes(32))
+
                 t = HttpThread(sc.fp, TEST_HTTP_HOST, TEST_HTTP_PORT + i, False, sc)
                 cls.http_threads.append(t)
                 t.start()
@@ -685,6 +711,7 @@ class BaseTest(unittest.TestCase):
                     "getnewaddress",
                     ["mining_addr", "bech32"],
                     base_rpc_port=BTC_BASE_RPC_PORT,
+                    wallet="wallet.dat",
                 )
                 num_blocks = 400  # Mine enough to activate segwit
                 logging.info("Mining %d Bitcoin blocks to %s", num_blocks, cls.btc_addr)
@@ -700,6 +727,7 @@ class BaseTest(unittest.TestCase):
                     "getnewaddress",
                     ["initial addr"],
                     base_rpc_port=BTC_BASE_RPC_PORT,
+                    wallet="wallet.dat",
                 )
                 for i in range(5):
                     callnoderpc(
@@ -707,6 +735,7 @@ class BaseTest(unittest.TestCase):
                         "sendtoaddress",
                         [btc_addr1, 100],
                         base_rpc_port=BTC_BASE_RPC_PORT,
+                        wallet="wallet.dat",
                     )
 
                 # Switch addresses so wallet amounts stay constant