From 2a8c04b28504a72978f953f21c926123150ac858 Mon Sep 17 00:00:00 2001
From: tecnovert <tecnovert@tecnovert.net>
Date: Sun, 19 May 2024 17:45:20 +0200
Subject: [PATCH] Decred xmr swap tests.

---
 basicswap/basicswap.py               | 178 ++++---
 basicswap/basicswap_util.py          |   1 +
 basicswap/db.py                      |   5 +
 basicswap/interface/base.py          |  65 ++-
 basicswap/interface/btc.py           | 247 ++++------
 basicswap/interface/dash.py          |   2 +-
 basicswap/interface/dcr/dcr.py       | 685 ++++++++++++++++++++++++++-
 basicswap/interface/dcr/messages.py  |   9 +-
 basicswap/interface/dcr/rpc.py       |   9 +
 basicswap/interface/dcr/script.py    |   9 +-
 basicswap/interface/firo.py          |   4 +-
 basicswap/interface/nav.py           |  20 +-
 basicswap/interface/part.py          |  17 +-
 basicswap/interface/pivx.py          |   2 +-
 basicswap/interface/xmr.py           |   2 +-
 basicswap/protocols/xmr_swap_1.py    |  13 +-
 tests/basicswap/common.py            |   5 +-
 tests/basicswap/extended/test_dcr.py | 267 ++++++++++-
 tests/basicswap/test_btc_xmr.py      |   2 +-
 tests/basicswap/test_run.py          |  20 +-
 tests/basicswap/test_xmr.py          |   2 +-
 21 files changed, 1271 insertions(+), 293 deletions(-)

diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py
index 52a5184..04f4920 100644
--- a/basicswap/basicswap.py
+++ b/basicswap/basicswap.py
@@ -1153,14 +1153,26 @@ class BasicSwap(BaseApp):
             raise ValueError('Offer not found')
 
         self.loadBidTxns(bid, session)
+
+        coin_from = Coins(offer.coin_from)
+        coin_to = Coins(offer.coin_to)
+
         if offer.swap_type == SwapTypes.XMR_SWAP:
             xmr_swap = session.query(XmrSwap).filter_by(bid_id=bid.bid_id).first()
             self.watchXmrSwap(bid, offer, xmr_swap)
+            if coin_to.watch_blocks_for_scripts() and bid.xmr_a_lock_tx and bid.xmr_a_lock_tx.chain_height:
+                if not bid.xmr_b_lock_tx or not bid.xmr_b_lock_tx.txid:
+                    ci_from = self.ci(coin_from)
+                    ci_to = self.ci(coin_to)
+                    chain_a_block_header = ci_from.getBlockHeaderFromHeight(bid.xmr_a_lock_tx.chain_height)
+                    block_time = chain_a_block_header['time']
+                    chain_b_block_header = ci_to.getBlockHeaderAt(block_time)
+                    dest_script = ci_to.getPkDest(xmr_swap.pkbs)
+                    self.addWatchedScript(ci_to.coin_type(), bid.bid_id, dest_script, TxTypes.XMR_SWAP_B_LOCK)
+                    self.setLastHeightCheckedStart(ci_to.coin_type(), chain_b_block_header['height'])
         else:
             self.swaps_in_progress[bid.bid_id] = (bid, offer)
 
-            coin_from = Coins(offer.coin_from)
-            coin_to = Coins(offer.coin_to)
             if bid.initiate_tx and bid.initiate_tx.txid:
                 self.addWatchedOutput(coin_from, bid.bid_id, bid.initiate_tx.txid.hex(), bid.initiate_tx.vout, BidStates.SWAP_INITIATED)
             if bid.participate_tx and bid.participate_tx.txid:
@@ -1632,16 +1644,12 @@ class BasicSwap(BaseApp):
             if swap_type == SwapTypes.XMR_SWAP:
                 xmr_offer = XmrOffer()
 
-                if reverse_bid:
-                    # Delay before the chain a lock refund tx can be mined
-                    xmr_offer.lock_time_1 = ci_to.getExpectedSequence(lock_type, lock_value)
-                    # Delay before the follower can spend from the chain a lock refund tx
-                    xmr_offer.lock_time_2 = ci_to.getExpectedSequence(lock_type, lock_value)
-                else:
-                    # Delay before the chain a lock refund tx can be mined
-                    xmr_offer.lock_time_1 = ci_from.getExpectedSequence(lock_type, lock_value)
-                    # Delay before the follower can spend from the chain a lock refund tx
-                    xmr_offer.lock_time_2 = ci_from.getExpectedSequence(lock_type, lock_value)
+                chain_a_ci = ci_to if reverse_bid else ci_from
+                lock_value_2 = lock_value + 1000 if (None, DebugTypes.OFFER_LOCK_2_VALUE_INC) in self._debug_cases else lock_value
+                # Delay before the chain a lock refund tx can be mined
+                xmr_offer.lock_time_1 = chain_a_ci.getExpectedSequence(lock_type, lock_value)
+                # Delay before the follower can spend from the chain a lock refund tx
+                xmr_offer.lock_time_2 = chain_a_ci.getExpectedSequence(lock_type, lock_value_2)
 
                 xmr_offer.a_fee_rate = msg_buf.fee_rate_from
                 xmr_offer.b_fee_rate = msg_buf.fee_rate_to  # Unused: TODO - Set priority?
@@ -2643,8 +2651,9 @@ class BasicSwap(BaseApp):
                 txid = ci_from.publishTx(bid.initiate_txn_refund)
                 self.log.error('Submit refund_txn unexpectedly worked: ' + txid)
             except Exception as ex:
-                if 'non-BIP68-final' not in str(ex) and 'non-final' not in str(ex):
+                if ci_from.isTxNonFinalError(str(ex)) is False:
                     self.log.error('Submit refund_txn unexpected error' + str(ex))
+                    raise ex
 
         if txid is not None:
             msg_buf = BidAcceptMessage()
@@ -2654,7 +2663,6 @@ class BasicSwap(BaseApp):
 
             # pkh sent in script is hashed with sha256, Decred expects blake256
             if bid.pkhash_seller != pkhash_refund:
-                assert (ci_to.coin_type() == Coins.DCR or ci_from.coin_type() == Coins.DCR)  # [rm]
                 msg_buf.pkhash_seller = bid.pkhash_seller
 
             bid_bytes = msg_buf.SerializeToString()
@@ -2785,7 +2793,7 @@ class BasicSwap(BaseApp):
             msg_buf.amount_to = amount_to
 
             address_out = self.getReceiveAddressFromPool(coin_from, offer_id, TxTypes.XMR_SWAP_A_LOCK)
-            if coin_from == Coins.PART_BLIND:
+            if coin_from in (Coins.PART_BLIND, ):
                 addrinfo = ci_from.rpc('getaddressinfo', [address_out])
                 msg_buf.dest_af = bytes.fromhex(addrinfo['pubkey'])
             else:
@@ -3025,7 +3033,7 @@ class BasicSwap(BaseApp):
             msg_buf.a_lock_tx = xmr_swap.a_lock_tx
             msg_buf.a_lock_tx_script = xmr_swap.a_lock_tx_script
             msg_buf.a_lock_refund_tx = xmr_swap.a_lock_refund_tx
-            msg_buf.a_lock_refund_tx_script = xmr_swap.a_lock_refund_tx_script
+            msg_buf.a_lock_refund_tx_script = bytes(xmr_swap.a_lock_refund_tx_script)
             msg_buf.a_lock_refund_spend_tx = xmr_swap.a_lock_refund_spend_tx
             msg_buf.al_lock_refund_tx_sig = xmr_swap.al_lock_refund_tx_sig
 
@@ -3327,9 +3335,6 @@ class BasicSwap(BaseApp):
             secret = self.getContractSecret(bid_date, bid.contract_count)
         ensure(len(secret) == 32, 'Bad secret length')
 
-        self.log.debug('secret {}'.format(secret.hex()))
-        self.log.debug('sha256(secret) {}'.format(sha256(secret).hex()))
-
         if self.coin_clients[coin_type]['connection_type'] != 'rpc':
             return None
 
@@ -3475,14 +3480,7 @@ class BasicSwap(BaseApp):
         else:
             privkey_wif = self.ci(Coins.PART).encodeKey(privkey)
             refund_sig = self.callcoinrpc(Coins.PART, 'createsignaturewithkey', [refund_txn, prevout, privkey_wif, 'ALL', options])
-        if coin_type in (Coins.DCR, ):
-            witness_stack = [
-                bytes.fromhex(refund_sig),
-                pubkey,
-                (OpCodes.OP_0).to_bytes(1),
-                txn_script]
-            refund_txn = ci.setTxSignature(bytes.fromhex(refund_txn), witness_stack).hex()
-        elif coin_type in (Coins.PART, ) or self.coin_clients[coin_type]['use_segwit']:
+        if coin_type in (Coins.PART, Coins.DCR) or self.coin_clients[coin_type]['use_segwit']:
             witness_stack = [
                 bytes.fromhex(refund_sig),
                 pubkey,
@@ -3706,8 +3704,18 @@ class BasicSwap(BaseApp):
 
     def findTxB(self, ci_to, xmr_swap, bid, session, bid_sender: bool) -> bool:
         bid_changed = False
-        # Have to use findTxB instead of relying on the first seen height to detect chain reorgs
-        found_tx = ci_to.findTxB(xmr_swap.vkbv, xmr_swap.pkbs, bid.amount_to, ci_to.blocks_confirmed, bid.chain_b_height_start, bid_sender)
+
+        found_tx = None
+        if ci_to.coin_type() in (Coins.DCR, ):
+            if bid.xmr_b_lock_tx is None or bid.xmr_b_lock_tx.txid is None:
+                # Watching chain for dest_address with WatchedScript
+                pass
+            else:
+                dest_address = ci_to.pkh_to_address(ci_to.pkh(xmr_swap.pkbs))
+                found_tx = ci_to.getLockTxHeight(bid.xmr_b_lock_tx.txid, dest_address, bid.amount_to, bid.chain_b_height_start, vout=bid.xmr_b_lock_tx.vout)
+        else:
+            # Have to use findTxB instead of relying on the first seen height to detect chain reorgs
+            found_tx = ci_to.findTxB(xmr_swap.vkbv, xmr_swap.pkbs, bid.amount_to, ci_to.blocks_confirmed, bid.chain_b_height_start, bid_sender)
 
         if isinstance(found_tx, int) and found_tx == -1:
             if self.countBidEvents(bid, EventLogTypes.LOCK_TX_B_INVALID, session) < 1:
@@ -3716,20 +3724,18 @@ class BasicSwap(BaseApp):
         elif found_tx is not None:
             if found_tx['height'] != 0 and (bid.xmr_b_lock_tx is None or not bid.xmr_b_lock_tx.chain_height):
                 self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_SEEN, '', session)
-            if bid.xmr_b_lock_tx is None:
+
+            if bid.xmr_b_lock_tx is None or bid.xmr_b_lock_tx.chain_height is None:
                 self.log.debug('Found {} lock tx in chain'.format(ci_to.coin_name()))
                 xmr_swap.b_lock_tx_id = bytes.fromhex(found_tx['txid'])
+            if bid.xmr_b_lock_tx is None:
                 bid.xmr_b_lock_tx = SwapTx(
                     bid_id=bid.bid_id,
                     tx_type=TxTypes.XMR_SWAP_B_LOCK,
                     txid=xmr_swap.b_lock_tx_id,
-                    chain_height=found_tx['height'],
                 )
-                bid_changed = True
-                bid.xmr_b_lock_tx.setState(TxStates.TX_IN_CHAIN)
-            else:
-                bid.xmr_b_lock_tx.chain_height = found_tx['height']
-                bid_changed = True
+            bid.xmr_b_lock_tx.chain_height = found_tx['height']
+            bid_changed = True
         return bid_changed
 
     def checkXmrBidState(self, bid_id: bytes, bid, offer):
@@ -3785,7 +3791,7 @@ class BasicSwap(BaseApp):
                         self.saveBidInSession(bid_id, bid, session, xmr_swap)
                         session.commit()
 
-                    if TxTypes.XMR_SWAP_A_LOCK_REFUND_SWIPE not in bid.txns and BidStates(bid.state) != BidStates.BID_STALLED_FOR_TEST:
+                    if TxTypes.XMR_SWAP_A_LOCK_REFUND_SWIPE not in bid.txns:
                         try:
                             txid = ci_from.publishTx(xmr_swap.a_lock_refund_swipe_tx)
                             self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_A_REFUND_SWIPE_TX_PUBLISHED, '', session)
@@ -3827,7 +3833,7 @@ class BasicSwap(BaseApp):
                         session.commit()
                         return rv
                     except Exception as ex:
-                        if 'Transaction already in block chain' in str(ex):
+                        if ci_from.isTxExistsError(str(ex)):
                             self.log.info('Found coin a lock refund tx for bid {}'.format(bid_id.hex()))
                             txid = ci_from.getTxid(xmr_swap.a_lock_refund_tx)
                             if TxTypes.XMR_SWAP_A_LOCK_REFUND not in bid.txns:
@@ -3882,6 +3888,7 @@ class BasicSwap(BaseApp):
                 if lock_tx_chain_info['depth'] >= ci_from.blocks_confirmed:
                     self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_A_CONFIRMED, '', session)
                     bid.xmr_a_lock_tx.setState(TxStates.TX_CONFIRMED)
+
                     bid.setState(BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED)
                     bid_changed = True
 
@@ -3890,6 +3897,13 @@ class BasicSwap(BaseApp):
                         self.log.info('Sending adaptor-sig swap chain B lock tx for bid %s in %d seconds', bid_id.hex(), delay)
                         self.createActionInSession(delay, ActionTypes.SEND_XMR_SWAP_LOCK_TX_B, bid_id, session)
                         # bid.setState(BidStates.SWAP_DELAYING)
+                    elif ci_to.watch_blocks_for_scripts():
+                        chain_a_block_header = ci_from.getBlockHeaderFromHeight(bid.xmr_a_lock_tx.chain_height)
+                        block_time = chain_a_block_header['time']
+                        chain_b_block_header = ci_to.getBlockHeaderAt(block_time)
+                        dest_script = ci_to.getPkDest(xmr_swap.pkbs)
+                        self.addWatchedScript(ci_to.coin_type(), bid.bid_id, dest_script, TxTypes.XMR_SWAP_B_LOCK)
+                        self.setLastHeightCheckedStart(ci_to.coin_type(), chain_b_block_header['height'])
 
                 if bid_changed:
                     self.saveBidInSession(bid_id, bid, session, xmr_swap)
@@ -4131,7 +4145,7 @@ class BasicSwap(BaseApp):
                 self.logEvent(Concepts.BID, bid.bid_id, EventLogTypes.ITX_REFUND_PUBLISHED, '', None)
                 # State will update when spend is detected
             except Exception as ex:
-                if 'non-BIP68-final' not in str(ex) and 'non-final' not in str(ex):
+                if ci_from.isTxNonFinalError(str(ex)) is False:
                     self.log.warning('Error trying to submit initiate refund txn: %s', str(ex))
 
         if bid.getPTxState() in (TxStates.TX_SENT, TxStates.TX_CONFIRMED) \
@@ -4142,7 +4156,7 @@ class BasicSwap(BaseApp):
                 self.logEvent(Concepts.BID, bid.bid_id, EventLogTypes.PTX_REFUND_PUBLISHED, '', None)
                 # State will update when spend is detected
             except Exception as ex:
-                if 'non-BIP68-final' not in str(ex) and 'non-final' not in str(ex) and 'locks on inputs not met' not in str(ex):
+                if ci_to.isTxNonFinalError(str(ex)):
                     self.log.warning('Error trying to submit participate refund txn: %s', str(ex))
         return False  # Bid is still active
 
@@ -4298,6 +4312,7 @@ class BasicSwap(BaseApp):
             state = BidStates(bid.state)
             spending_txid = bytes.fromhex(spend_txid_hex)
 
+            bid.xmr_a_lock_tx.spend_txid = spending_txid
             if spending_txid == xmr_swap.a_lock_spend_tx_id:
                 if state == BidStates.XMR_SWAP_LOCK_RELEASED:
                     xmr_swap.a_lock_spend_tx = bytes.fromhex(spend_txn_hex)
@@ -4416,6 +4431,16 @@ class BasicSwap(BaseApp):
                 self.saveBid(watched_script.bid_id, bid)
             else:
                 self.log.warning('Could not find active bid for found watched script: {}'.format(watched_script.bid_id.hex()))
+        elif watched_script.tx_type == TxTypes.XMR_SWAP_B_LOCK:
+            bid = self.swaps_in_progress[watched_script.bid_id][0]
+            bid.xmr_b_lock_tx = SwapTx(
+                bid_id=watched_script.bid_id,
+                tx_type=TxTypes.XMR_SWAP_B_LOCK,
+                txid=txid,
+                vout=vout,
+            )
+            bid.xmr_b_lock_tx.setState(TxStates.TX_IN_CHAIN)
+            self.saveBid(watched_script.bid_id, bid)
         else:
             self.log.warning('Unknown found watched script tx type for bid {}'.format(watched_script.bid_id.hex()))
 
@@ -4852,12 +4877,12 @@ class BasicSwap(BaseApp):
 
                     xmr_offer.offer_id = offer_id
 
-                    if reverse_bid:
-                        xmr_offer.lock_time_1 = ci_to.getExpectedSequence(offer_data.lock_type, offer_data.lock_value)
-                        xmr_offer.lock_time_2 = ci_to.getExpectedSequence(offer_data.lock_type, offer_data.lock_value)
-                    else:
-                        xmr_offer.lock_time_1 = ci_from.getExpectedSequence(offer_data.lock_type, offer_data.lock_value)
-                        xmr_offer.lock_time_2 = ci_from.getExpectedSequence(offer_data.lock_type, offer_data.lock_value)
+                    chain_a_ci = ci_to if reverse_bid else ci_from
+                    lock_value_2 = offer_data.lock_value
+                    if (None, DebugTypes.OFFER_LOCK_2_VALUE_INC) in self._debug_cases:
+                        lock_value_2 += 1000
+                    xmr_offer.lock_time_1 = chain_a_ci.getExpectedSequence(offer_data.lock_type, offer_data.lock_value)
+                    xmr_offer.lock_time_2 = chain_a_ci.getExpectedSequence(offer_data.lock_type, lock_value_2)
 
                     xmr_offer.a_fee_rate = offer_data.fee_rate_from
                     xmr_offer.b_fee_rate = offer_data.fee_rate_to
@@ -5618,6 +5643,8 @@ class BasicSwap(BaseApp):
             a_fee_rate, xmr_swap.vkbv)
 
         xmr_swap.a_lock_spend_tx_id = ci_from.getTxid(xmr_swap.a_lock_spend_tx)
+        if bid.xmr_a_lock_tx:
+            bid.xmr_a_lock_tx.spend_txid = xmr_swap.a_lock_spend_tx_id
         prevout_amount = ci_from.getLockTxSwapOutputValue(bid, xmr_swap)
         xmr_swap.al_lock_spend_tx_esig = ci_from.signTxOtVES(kal, xmr_swap.pkasf, xmr_swap.a_lock_spend_tx, 0, xmr_swap.a_lock_tx_script, prevout_amount)
         '''
@@ -5628,33 +5655,37 @@ class BasicSwap(BaseApp):
             xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script,
             xmr_swap.dest_af, a_fee_rate, xmr_swap.vkbv)
         '''
-        delay = self.get_short_delay_event_seconds()
-        self.log.info('Sending lock spend tx message for bid %s in %d seconds', bid_id.hex(), delay)
-        self.createActionInSession(delay, ActionTypes.SEND_XMR_SWAP_LOCK_SPEND_MSG, bid_id, session)
 
+        lock_tx_sent: bool = False
         # publishalocktx
         if bid.xmr_a_lock_tx and bid.xmr_a_lock_tx.state:
             if bid.xmr_a_lock_tx.state >= TxStates.TX_SENT:
-                raise ValueError('Lock tx has already been sent {}'.format(bid.xmr_a_lock_tx.txid.hex()))
+                self.log.warning('Lock tx has already been sent {}'.format(bid.xmr_a_lock_tx.txid.hex()))
+                lock_tx_sent = True
 
-        lock_tx_signed = ci_from.signTxWithWallet(xmr_swap.a_lock_tx)
-        txid_hex = ci_from.publishTx(lock_tx_signed)
+        if lock_tx_sent is False:
+            lock_tx_signed = ci_from.signTxWithWallet(xmr_swap.a_lock_tx)
+            txid_hex = ci_from.publishTx(lock_tx_signed)
 
-        vout_pos = ci_from.getTxOutputPos(xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script)
-        self.log.debug('Submitted lock txn %s to %s chain for bid %s', txid_hex, ci_from.coin_name(), bid_id.hex())
+            vout_pos = ci_from.getTxOutputPos(xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script)
+            self.log.debug('Submitted lock txn %s to %s chain for bid %s', txid_hex, ci_from.coin_name(), bid_id.hex())
 
-        if bid.xmr_a_lock_tx is None:
-            bid.xmr_a_lock_tx = SwapTx(
-                bid_id=bid_id,
-                tx_type=TxTypes.XMR_SWAP_A_LOCK,
-                txid=bytes.fromhex(txid_hex),
-                vout=vout_pos,
-            )
-        bid.xmr_a_lock_tx.setState(TxStates.TX_SENT)
+            if bid.xmr_a_lock_tx is None:
+                bid.xmr_a_lock_tx = SwapTx(
+                    bid_id=bid_id,
+                    tx_type=TxTypes.XMR_SWAP_A_LOCK,
+                    txid=bytes.fromhex(txid_hex),
+                    vout=vout_pos,
+                )
+            bid.xmr_a_lock_tx.setState(TxStates.TX_SENT)
+            self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_A_PUBLISHED, '', session)
 
         bid.setState(BidStates.XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX)
         self.watchXmrSwap(bid, offer, xmr_swap)
-        self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_A_PUBLISHED, '', session)
+
+        delay = self.get_short_delay_event_seconds()
+        self.log.info('Sending lock spend tx message for bid %s in %d seconds', bid_id.hex(), delay)
+        self.createActionInSession(delay, ActionTypes.SEND_XMR_SWAP_LOCK_SPEND_MSG, bid_id, session)
 
         self.saveBidInSession(bid_id, bid, session, xmr_swap)
 
@@ -5802,8 +5833,10 @@ class BasicSwap(BaseApp):
         v = ci_from.verifyTxSig(xmr_swap.a_lock_spend_tx, af_lock_spend_sig, xmr_swap.pkaf, 0, xmr_swap.a_lock_tx_script, prevout_amount)
         ensure(v, 'Invalid coin A lock tx spend tx follower sig')
 
-        witness_stack = [
-            b'',
+        witness_stack = []
+        if coin_from not in (Coins.DCR,):
+            witness_stack += [b'',]
+        witness_stack += [
             al_lock_spend_sig,
             af_lock_spend_sig,
             xmr_swap.a_lock_tx_script,
@@ -5868,7 +5901,8 @@ class BasicSwap(BaseApp):
             else:
                 address_to = self.getReceiveAddressFromPool(coin_to, bid_id, TxTypes.XMR_SWAP_B_LOCK_SPEND)
 
-            txid = ci_to.spendBLockTx(xmr_swap.b_lock_tx_id, address_to, xmr_swap.vkbv, vkbs, bid.amount_to, b_fee_rate, bid.chain_b_height_start)
+            lock_tx_vout = bid.getLockTXBVout()
+            txid = ci_to.spendBLockTx(xmr_swap.b_lock_tx_id, address_to, xmr_swap.vkbv, vkbs, bid.amount_to, b_fee_rate, bid.chain_b_height_start, lock_tx_vout=lock_tx_vout)
             self.log.debug('Submitted lock B spend txn %s to %s chain for bid %s', txid.hex(), ci_to.coin_name(), bid_id.hex())
             self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_SPEND_TX_PUBLISHED, '', session)
         except Exception as ex:
@@ -5933,7 +5967,9 @@ class BasicSwap(BaseApp):
                 address_to = self.getCachedStealthAddressForCoin(coin_to)
             else:
                 address_to = self.getReceiveAddressFromPool(coin_to, bid_id, TxTypes.XMR_SWAP_B_LOCK_REFUND)
-            txid = ci_to.spendBLockTx(xmr_swap.b_lock_tx_id, address_to, xmr_swap.vkbv, vkbs, bid.amount_to, b_fee_rate, bid.chain_b_height_start)
+
+            lock_tx_vout = bid.getLockTXBVout()
+            txid = ci_to.spendBLockTx(xmr_swap.b_lock_tx_id, address_to, xmr_swap.vkbv, vkbs, bid.amount_to, b_fee_rate, bid.chain_b_height_start, lock_tx_vout=lock_tx_vout)
             self.log.debug('Submitted lock B refund txn %s to %s chain for bid %s', txid.hex(), ci_to.coin_name(), bid_id.hex())
             self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_REFUND_TX_PUBLISHED, '', session)
         except Exception as ex:
@@ -6041,8 +6077,10 @@ class BasicSwap(BaseApp):
             al_lock_refund_spend_tx_sig = ci_from.signTx(kal, xmr_swap.a_lock_refund_spend_tx, 0, xmr_swap.a_lock_refund_tx_script, prevout_amount)
 
             self.log.debug('Setting lock refund spend tx sigs')
-            witness_stack = [
-                b'',
+            witness_stack = []
+            if coin_from not in (Coins.DCR, ):
+                witness_stack += [b'',]
+            witness_stack += [
                 al_lock_refund_spend_tx_sig,
                 xmr_swap.af_lock_refund_spend_tx_sig,
                 bytes((1,)),
@@ -6099,6 +6137,8 @@ class BasicSwap(BaseApp):
         try:
             xmr_swap.a_lock_spend_tx = msg_data.a_lock_spend_tx
             xmr_swap.a_lock_spend_tx_id = ci_from.getTxid(xmr_swap.a_lock_spend_tx)
+            if bid.xmr_a_lock_tx:
+                bid.xmr_a_lock_tx.spend_txid = xmr_swap.a_lock_spend_tx_id
             xmr_swap.kal_sig = msg_data.kal_sig
 
             ci_from.verifySCLockSpendTx(
diff --git a/basicswap/basicswap_util.py b/basicswap/basicswap_util.py
index 8f53362..f4d8b3d 100644
--- a/basicswap/basicswap_util.py
+++ b/basicswap/basicswap_util.py
@@ -203,6 +203,7 @@ class DebugTypes(IntEnum):
     B_LOCK_TX_MISSED_SEND = auto()
     DUPLICATE_ACTIONS = auto()
     DONT_CONFIRM_PTX = auto()
+    OFFER_LOCK_2_VALUE_INC = auto()
 
 
 class NotificationTypes(IntEnum):
diff --git a/basicswap/db.py b/basicswap/db.py
index 466283a..943ad76 100644
--- a/basicswap/db.py
+++ b/basicswap/db.py
@@ -192,6 +192,11 @@ class Bid(Base):
         else:
             self.states += pack_state(new_state, now)
 
+    def getLockTXBVout(self):
+        if self.xmr_b_lock_tx:
+            return self.xmr_b_lock_tx.vout
+        return None
+
 
 class SwapTx(Base):
     __tablename__ = 'transactions'
diff --git a/basicswap/interface/base.py b/basicswap/interface/base.py
index ff69e1c..7a1ce0c 100644
--- a/basicswap/interface/base.py
+++ b/basicswap/interface/base.py
@@ -44,6 +44,10 @@ class CoinInterface:
     def watch_blocks_for_scripts() -> bool:
         return False
 
+    @staticmethod
+    def compareFeeRates(a, b) -> bool:
+        return abs(a - b) < 20
+
     def __init__(self, network):
         self.setDefaults()
         self._network = network
@@ -149,8 +153,44 @@ class CoinInterface:
     def use_tx_vsize(self) -> bool:
         return self._use_segwit
 
+    def getLockTxSwapOutputValue(self, bid, xmr_swap):
+        return bid.amount
 
-class Secp256k1Interface(CoinInterface):
+    def getLockRefundTxSwapOutputValue(self, bid, xmr_swap):
+        return xmr_swap.a_swap_refund_value
+
+    def getLockRefundTxSwapOutput(self, xmr_swap):
+        # Only one prevout exists
+        return 0
+
+
+class AdaptorSigInterface():
+    def getScriptLockTxDummyWitness(self, script: bytes):
+        return [
+            b'',
+            bytes(72),
+            bytes(72),
+            bytes(len(script))
+        ]
+
+    def getScriptLockRefundSpendTxDummyWitness(self, script: bytes):
+        return [
+            b'',
+            bytes(72),
+            bytes(72),
+            bytes((1,)),
+            bytes(len(script))
+        ]
+
+    def getScriptLockRefundSwipeTxDummyWitness(self, script: bytes):
+        return [
+            bytes(72),
+            b'',
+            bytes(len(script))
+        ]
+
+
+class Secp256k1Interface(CoinInterface, AdaptorSigInterface):
     @staticmethod
     def curve_type():
         return Curves.secp256k1
@@ -170,3 +210,26 @@ class Secp256k1Interface(CoinInterface):
 
     def verifyPubkey(self, pubkey_bytes: bytes) -> bool:
         return verify_secp256k1_point(pubkey_bytes)
+
+    def isValidAddressHash(self, address_hash: bytes) -> bool:
+        hash_len = len(address_hash)
+        if hash_len == 20:
+            return True
+
+    def isValidPubkey(self, pubkey: bytes) -> bool:
+        try:
+            self.verifyPubkey(pubkey)
+            return True
+        except Exception:
+            return False
+
+    def verifySig(self, pubkey: bytes, signed_hash: bytes, sig: bytes) -> bool:
+        pubkey = PublicKey(pubkey)
+        return pubkey.verify(sig, signed_hash, hasher=None)
+
+    def sumKeys(self, ka: bytes, kb: bytes) -> bytes:
+        # TODO: Add to coincurve
+        return i2b((b2i(ka) + b2i(kb)) % ep.o)
+
+    def sumPubkeys(self, Ka: bytes, Kb: bytes) -> bytes:
+        return PublicKey.combine_keys([PublicKey(Ka), PublicKey(Kb)]).format()
diff --git a/basicswap/interface/btc.py b/basicswap/interface/btc.py
index 4f62030..152f57c 100644
--- a/basicswap/interface/btc.py
+++ b/basicswap/interface/btc.py
@@ -28,7 +28,6 @@ from basicswap.util import (
     b2h, i2b, b2i, i2h,
 )
 from basicswap.util.ecc import (
-    ep,
     pointToCPK, CPKToPoint,
 )
 from basicswap.util.script import (
@@ -66,7 +65,6 @@ from basicswap.contrib.test_framework.messages import (
     CTxIn,
     CTxInWitness,
     CTxOut,
-    uint256_from_str,
 )
 from basicswap.contrib.test_framework.script import (
     CScript, CScriptOp,
@@ -120,6 +118,57 @@ def find_vout_for_address_from_txobj(tx_obj, addr: str) -> int:
     raise RuntimeError("Vout not found for address: txid={}, addr={}".format(tx_obj['txid'], addr))
 
 
+def extractScriptLockScriptValues(script_bytes: bytes) -> (bytes, bytes):
+    script_len = len(script_bytes)
+    ensure(script_len == 71, 'Bad script length')
+    o = 0
+    ensure_op(script_bytes[o] == OP_2)
+    ensure_op(script_bytes[o + 1] == 33)
+    o += 2
+    pk1 = script_bytes[o: o + 33]
+    o += 33
+    ensure_op(script_bytes[o] == 33)
+    o += 1
+    pk2 = script_bytes[o: o + 33]
+    o += 33
+    ensure_op(script_bytes[o] == OP_2)
+    ensure_op(script_bytes[o + 1] == OP_CHECKMULTISIG)
+
+    return pk1, pk2
+
+
+def extractScriptLockRefundScriptValues(script_bytes: bytes):
+    script_len = len(script_bytes)
+    ensure(script_len > 73, 'Bad script length')
+    ensure_op(script_bytes[0] == OP_IF)
+    ensure_op(script_bytes[1] == OP_2)
+    ensure_op(script_bytes[2] == 33)
+    pk1 = script_bytes[3: 3 + 33]
+    ensure_op(script_bytes[36] == 33)
+    pk2 = script_bytes[37: 37 + 33]
+    ensure_op(script_bytes[70] == OP_2)
+    ensure_op(script_bytes[71] == OP_CHECKMULTISIG)
+    ensure_op(script_bytes[72] == OP_ELSE)
+    o = 73
+    csv_val, nb = decodeScriptNum(script_bytes, o)
+    o += nb
+
+    ensure(script_len == o + 5 + 33, 'Bad script length')  # Fails if script too long
+    ensure_op(script_bytes[o] == OP_CHECKSEQUENCEVERIFY)
+    o += 1
+    ensure_op(script_bytes[o] == OP_DROP)
+    o += 1
+    ensure_op(script_bytes[o] == 33)
+    o += 1
+    pk3 = script_bytes[o: o + 33]
+    o += 33
+    ensure_op(script_bytes[o] == OP_CHECKSIG)
+    o += 1
+    ensure_op(script_bytes[o] == OP_ENDIF)
+
+    return pk1, pk2, csv_val, pk3
+
+
 class BTCInterface(Secp256k1Interface):
 
     @staticmethod
@@ -157,10 +206,6 @@ class BTCInterface(Secp256k1Interface):
             rv += output.nValue
         return rv
 
-    @staticmethod
-    def compareFeeRates(a, b) -> bool:
-        return abs(a - b) < 20
-
     @staticmethod
     def xmr_swap_a_lock_spend_tx_vsize() -> int:
         return 147
@@ -214,6 +259,23 @@ class BTCInterface(Secp256k1Interface):
         self._log = self._sc.log if self._sc and self._sc.log else logging
         self._expect_seedid_hex = None
 
+    def open_rpc(self, wallet=None):
+        return openrpc(self._rpcport, self._rpcauth, wallet=wallet, host=self._rpc_host)
+
+    def json_request(self, rpc_conn, method, params):
+        try:
+            v = rpc_conn.json_request(method, params)
+            r = json.loads(v.decode('utf-8'))
+        except Exception as ex:
+            traceback.print_exc()
+            raise ValueError('RPC Server Error ' + str(ex))
+        if 'error' in r and r['error'] is not None:
+            raise ValueError('RPC error ' + str(r['error']))
+        return r['result']
+
+    def close_rpc(self, rpc_conn):
+        rpc_conn.close()
+
     def checkWallets(self) -> int:
         wallets = self.rpc('listwallets')
 
@@ -231,25 +293,6 @@ class BTCInterface(Secp256k1Interface):
 
         return len(wallets)
 
-    def open_rpc(self, wallet=None):
-        return openrpc(self._rpcport, self._rpcauth, wallet=wallet, host=self._rpc_host)
-
-    def json_request(self, rpc_conn, method, params):
-        try:
-            v = rpc_conn.json_request(method, params)
-            r = json.loads(v.decode('utf-8'))
-        except Exception as ex:
-            traceback.print_exc()
-            raise ValueError('RPC Server Error ' + str(ex))
-
-        if 'error' in r and r['error'] is not None:
-            raise ValueError('RPC error ' + str(r['error']))
-
-        return r['result']
-
-    def close_rpc(self, rpc_conn):
-        rpc_conn.close()
-
     def testDaemonRPC(self, with_wallet=True) -> None:
         self.rpc_wallet('getwalletinfo' if with_wallet else 'getblockchaininfo')
 
@@ -278,7 +321,7 @@ class BTCInterface(Secp256k1Interface):
 
         max_tries = 5000
         for i in range(max_tries):
-            prev_block_header = self.rpc('getblock', [last_block_header['previousblockhash']])
+            prev_block_header = self.rpc('getblockheader', [last_block_header['previousblockhash']])
             if prev_block_header['time'] <= time:
                 return last_block_header if block_after else prev_block_header
 
@@ -343,18 +386,6 @@ class BTCInterface(Secp256k1Interface):
             self._log.debug('validateaddress failed: {}'.format(address))
         return False
 
-    def isValidAddressHash(self, address_hash: bytes) -> bool:
-        hash_len = len(address_hash)
-        if hash_len == 20:
-            return True
-
-    def isValidPubkey(self, pubkey: bytes) -> bool:
-        try:
-            self.verifyPubkey(pubkey)
-            return True
-        except Exception:
-            return False
-
     def isAddressMine(self, address: str, or_watch_only: bool = False) -> bool:
         addr_info = self.rpc_wallet('getaddressinfo', [address])
         if not or_watch_only:
@@ -468,13 +499,6 @@ class BTCInterface(Secp256k1Interface):
     def decodeKey(self, k: str) -> bytes:
         return decodeWif(k)
 
-    def sumKeys(self, ka, kb):
-        # TODO: Add to coincurve
-        return i2b((b2i(ka) + b2i(kb)) % ep.o)
-
-    def sumPubkeys(self, Ka, Kb):
-        return PublicKey.combine_keys([PublicKey(Ka), PublicKey(Kb)]).format()
-
     def getScriptForPubkeyHash(self, pkh: bytes) -> CScript:
         # p2wpkh
         return CScript([OP_0, pkh])
@@ -485,24 +509,6 @@ class BTCInterface(Secp256k1Interface):
         tx.deserialize(BytesIO(tx_bytes))
         return tx
 
-    def extractScriptLockScriptValues(self, script_bytes: bytes):
-        script_len = len(script_bytes)
-        ensure(script_len == 71, 'Bad script length')
-        o = 0
-        ensure_op(script_bytes[o] == OP_2)
-        ensure_op(script_bytes[o + 1] == 33)
-        o += 2
-        pk1 = script_bytes[o: o + 33]
-        o += 33
-        ensure_op(script_bytes[o] == 33)
-        o += 1
-        pk2 = script_bytes[o: o + 33]
-        o += 33
-        ensure_op(script_bytes[o] == OP_2)
-        ensure_op(script_bytes[o + 1] == OP_CHECKMULTISIG)
-
-        return pk1, pk2
-
     def createSCLockTx(self, value: int, script: bytearray, vkbv: bytes = None) -> bytes:
         tx = CTransaction()
         tx.nVersion = self.txVersion()
@@ -512,37 +518,6 @@ class BTCInterface(Secp256k1Interface):
     def fundSCLockTx(self, tx_bytes, feerate, vkbv=None):
         return self.fundTx(tx_bytes, feerate)
 
-    def extractScriptLockRefundScriptValues(self, script_bytes: bytes):
-        script_len = len(script_bytes)
-        ensure(script_len > 73, 'Bad script length')
-        ensure_op(script_bytes[0] == OP_IF)
-        ensure_op(script_bytes[1] == OP_2)
-        ensure_op(script_bytes[2] == 33)
-        pk1 = script_bytes[3: 3 + 33]
-        ensure_op(script_bytes[36] == 33)
-        pk2 = script_bytes[37: 37 + 33]
-        ensure_op(script_bytes[70] == OP_2)
-        ensure_op(script_bytes[71] == OP_CHECKMULTISIG)
-        ensure_op(script_bytes[72] == OP_ELSE)
-        o = 73
-        csv_val, nb = decodeScriptNum(script_bytes, o)
-        o += nb
-
-        ensure(script_len == o + 5 + 33, 'Bad script length')  # Fails if script too long
-        ensure_op(script_bytes[o] == OP_CHECKSEQUENCEVERIFY)
-        o += 1
-        ensure_op(script_bytes[o] == OP_DROP)
-        o += 1
-        ensure_op(script_bytes[o] == 33)
-        o += 1
-        pk3 = script_bytes[o: o + 33]
-        o += 33
-        ensure_op(script_bytes[o] == OP_CHECKSIG)
-        o += 1
-        ensure_op(script_bytes[o] == OP_ENDIF)
-
-        return pk1, pk2, csv_val, pk3
-
     def genScriptLockRefundTxScript(self, Kal, Kaf, csv_val) -> CScript:
 
         Kal_enc = Kal if len(Kal) == 33 else self.encodePubkey(Kal)
@@ -634,7 +609,7 @@ class BTCInterface(Secp256k1Interface):
         ensure(locked_n is not None, 'Output not found in tx')
         locked_coin = tx_lock_refund.vout[locked_n].nValue
 
-        A, B, lock2_value, C = self.extractScriptLockRefundScriptValues(script_lock_refund)
+        A, B, lock2_value, C = extractScriptLockRefundScriptValues(script_lock_refund)
 
         tx_lock_refund.rehash()
         tx_lock_refund_hash_int = tx_lock_refund.sha256
@@ -721,7 +696,7 @@ class BTCInterface(Secp256k1Interface):
         ensure(locked_coin == swap_value, 'Bad locked value')
 
         # Check script
-        A, B = self.extractScriptLockScriptValues(script_out)
+        A, B = extractScriptLockScriptValues(script_out)
         ensure(A == Kal, 'Bad script pubkey')
         ensure(B == Kaf, 'Bad script pubkey')
 
@@ -789,7 +764,7 @@ class BTCInterface(Secp256k1Interface):
         locked_coin = tx.vout[locked_n].nValue
 
         # Check script and values
-        A, B, csv_val, C = self.extractScriptLockRefundScriptValues(script_out)
+        A, B, csv_val, C = extractScriptLockRefundScriptValues(script_out)
         ensure(A == Kal, 'Bad script pubkey')
         ensure(B == Kaf, 'Bad script pubkey')
         ensure(csv_val == csv_val_expect, 'Bad script csv value')
@@ -922,6 +897,9 @@ class BTCInterface(Secp256k1Interface):
     def decryptOtVES(self, k: bytes, esig: bytes) -> bytes:
         return ecdsaotves_dec_sig(k, esig) + bytes((SIGHASH_ALL,))
 
+    def recoverEncKey(self, esig, sig, K):
+        return ecdsaotves_rec_enc_key(K, esig, sig[:-1])  # Strip sighash type
+
     def verifyTxSig(self, tx_bytes: bytes, sig: bytes, K: bytes, input_n: int, prevout_script: bytes, prevout_value: int) -> bool:
         tx = self.loadTx(tx_bytes)
         sig_hash = SegwitV0SignatureHash(prevout_script, tx, input_n, SIGHASH_ALL, prevout_value)
@@ -929,11 +907,7 @@ class BTCInterface(Secp256k1Interface):
         pubkey = PublicKey(K)
         return pubkey.verify(sig[: -1], sig_hash, hasher=None)  # Pop the hashtype byte
 
-    def verifySig(self, pubkey, signed_hash, sig):
-        pubkey = PublicKey(pubkey)
-        return pubkey.verify(sig, signed_hash, hasher=None)
-
-    def fundTx(self, tx, feerate):
+    def fundTx(self, tx: bytes, feerate) -> bytes:
         feerate_str = self.format_amount(feerate)
         # TODO: unlock unspents if bid cancelled
         options = {
@@ -943,7 +917,7 @@ class BTCInterface(Secp256k1Interface):
         rv = self.rpc_wallet('fundrawtransaction', [tx.hex(), options])
         return bytes.fromhex(rv['hex'])
 
-    def listInputs(self, tx_bytes):
+    def listInputs(self, tx_bytes: bytes):
         tx = self.loadTx(tx_bytes)
 
         all_locked = self.rpc_wallet('listlockunspent')
@@ -1049,11 +1023,11 @@ class BTCInterface(Secp256k1Interface):
         tx.wit.vtxinwit.clear()
         return tx.serialize()
 
-    def extractLeaderSig(self, tx_bytes) -> bytes:
+    def extractLeaderSig(self, tx_bytes: bytes) -> bytes:
         tx = self.loadTx(tx_bytes)
         return tx.wit.vtxinwit[0].scriptWitness.stack[1]
 
-    def extractFollowerSig(self, tx_bytes) -> bytes:
+    def extractFollowerSig(self, tx_bytes: bytes) -> bytes:
         tx = self.loadTx(tx_bytes)
         return tx.wit.vtxinwit[0].scriptWitness.stack[2]
 
@@ -1076,9 +1050,6 @@ class BTCInterface(Secp256k1Interface):
 
         return bytes.fromhex(self.publishTx(b_lock_tx))
 
-    def recoverEncKey(self, esig, sig, K):
-        return ecdsaotves_rec_enc_key(K, esig, sig[:-1])  # Strip sighash type
-
     def getTxVSize(self, tx, add_bytes: int = 0, add_witness_bytes: int = 0) -> int:
         wsf = self.witnessScaleFactor()
         len_full = len(tx.serialize_with_witness()) + add_bytes + add_witness_bytes
@@ -1108,10 +1079,10 @@ class BTCInterface(Secp256k1Interface):
         witness_bytes = 109
         vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes)
         pay_fee = round(fee_rate * vsize / 1000)
-        self._log.info(f'BLockSpendTx  fee_rate, vsize, fee: {fee_rate}, {vsize}, {pay_fee}.')
+        self._log.info(f'BLockSpendTx fee_rate, vsize, fee: {fee_rate}, {vsize}, {pay_fee}.')
         return pay_fee
 
-    def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int) -> bytes:
+    def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int, lock_tx_vout=None) -> bytes:
         self._log.info('spendBLockTx %s:\n', chain_b_lock_txid.hex())
         wtx = self.rpc_wallet('gettransaction', [chain_b_lock_txid.hex(), ])
         lock_tx = self.loadTx(bytes.fromhex(wtx['hex']))
@@ -1126,7 +1097,7 @@ class BTCInterface(Secp256k1Interface):
         tx.nVersion = self.txVersion()
 
         script_lock = self.getScriptForPubkeyHash(Kbs)
-        chain_b_lock_txid_int = uint256_from_str(chain_b_lock_txid[::-1])
+        chain_b_lock_txid_int = b2i(chain_b_lock_txid)
 
         tx.vin.append(CTxIn(COutPoint(chain_b_lock_txid_int, locked_n),
                             nSequence=0,
@@ -1148,7 +1119,7 @@ class BTCInterface(Secp256k1Interface):
         addr_info = self.rpc_wallet('getaddressinfo', [address])
         return addr_info['iswatchonly']
 
-    def getSCLockScriptAddress(self, lock_script):
+    def getSCLockScriptAddress(self, lock_script: bytes) -> str:
         lock_tx_dest = self.getScriptDest(lock_script)
         return self.encodeScriptDest(lock_tx_dest)
 
@@ -1225,25 +1196,25 @@ class BTCInterface(Secp256k1Interface):
         params = [addr_to, value, '', '', subfee, True, self._conf_target]
         return self.rpc_wallet('sendtoaddress', params)
 
-    def signCompact(self, k, message):
+    def signCompact(self, k, message: str) -> bytes:
         message_hash = sha256(bytes(message, 'utf-8'))
 
         privkey = PrivateKey(k)
         return privkey.sign_recoverable(message_hash, hasher=None)[:64]
 
-    def signRecoverable(self, k, message):
+    def signRecoverable(self, k, message: str) -> bytes:
         message_hash = sha256(bytes(message, 'utf-8'))
 
         privkey = PrivateKey(k)
         return privkey.sign_recoverable(message_hash, hasher=None)
 
-    def verifyCompactSig(self, K, message, sig):
+    def verifyCompactSig(self, K, message: str, sig) -> None:
         message_hash = sha256(bytes(message, 'utf-8'))
         pubkey = PublicKey(K)
         rv = pubkey.verify_compact(sig, message_hash, hasher=None)
         assert (rv is True)
 
-    def verifySigAndRecover(self, sig, message):
+    def verifySigAndRecover(self, sig, message: str) -> bytes:
         message_hash = sha256(bytes(message, 'utf-8'))
         pubkey = PublicKey.from_signature_and_message(sig, message_hash, hasher=None)
         return pubkey.format()
@@ -1271,40 +1242,6 @@ class BTCInterface(Secp256k1Interface):
     def showLockTransfers(self, kbv, Kbs, restore_height):
         raise ValueError('Unimplemented')
 
-    def getLockTxSwapOutputValue(self, bid, xmr_swap):
-        return bid.amount
-
-    def getLockRefundTxSwapOutputValue(self, bid, xmr_swap):
-        return xmr_swap.a_swap_refund_value
-
-    def getLockRefundTxSwapOutput(self, xmr_swap):
-        # Only one prevout exists
-        return 0
-
-    def getScriptLockTxDummyWitness(self, script: bytes):
-        return [
-            b'',
-            bytes(72),
-            bytes(72),
-            bytes(len(script))
-        ]
-
-    def getScriptLockRefundSpendTxDummyWitness(self, script: bytes):
-        return [
-            b'',
-            bytes(72),
-            bytes(72),
-            bytes((1,)),
-            bytes(len(script))
-        ]
-
-    def getScriptLockRefundSwipeTxDummyWitness(self, script: bytes):
-        return [
-            bytes(72),
-            b'',
-            bytes(len(script))
-        ]
-
     def getWitnessStackSerialisedLength(self, witness_stack):
         length = getCompactSizeLen(len(witness_stack))
         for e in witness_stack:
@@ -1501,7 +1438,7 @@ class BTCInterface(Secp256k1Interface):
     def createRedeemTxn(self, prevout, output_addr: str, output_value: int, txn_script: bytes = None) -> str:
         tx = CTransaction()
         tx.nVersion = self.txVersion()
-        prev_txid = uint256_from_str(bytes.fromhex(prevout['txid'])[::-1])
+        prev_txid = b2i(bytes.fromhex(prevout['txid']))
         tx.vin.append(CTxIn(COutPoint(prev_txid, prevout['vout'])))
         pkh = self.decodeAddress(output_addr)
         script = self.getScriptForPubkeyHash(pkh)
@@ -1513,7 +1450,7 @@ class BTCInterface(Secp256k1Interface):
         tx = CTransaction()
         tx.nVersion = self.txVersion()
         tx.nLockTime = locktime
-        prev_txid = uint256_from_str(bytes.fromhex(prevout['txid'])[::-1])
+        prev_txid = b2i(bytes.fromhex(prevout['txid']))
         tx.vin.append(CTxIn(COutPoint(prev_txid, prevout['vout']), nSequence=sequence,))
         pkh = self.decodeAddress(output_addr)
         script = self.getScriptForPubkeyHash(pkh)
@@ -1551,6 +1488,12 @@ class BTCInterface(Secp256k1Interface):
             'amount': txjs['vout'][n]['value']
         }
 
+    def isTxExistsError(self, err_str: str) -> bool:
+        return 'Transaction already in block chain' in err_str
+
+    def isTxNonFinalError(self, err_str: str) -> bool:
+        return 'non-BIP68-final' in err_str or 'non-final' in err_str
+
 
 def testBTCInterface():
     print('TODO: testBTCInterface')
diff --git a/basicswap/interface/dash.py b/basicswap/interface/dash.py
index 933ea98..7649e73 100644
--- a/basicswap/interface/dash.py
+++ b/basicswap/interface/dash.py
@@ -66,7 +66,7 @@ class DASHInterface(BTCInterface):
         add_bytes = 107
         size = len(tx.serialize_with_witness()) + add_bytes
         pay_fee = round(fee_rate * size / 1000)
-        self._log.info(f'BLockSpendTx  fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}.')
+        self._log.info(f'BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}.')
         return pay_fee
 
     def findTxnByHash(self, txid_hex: str):
diff --git a/basicswap/interface/dcr/dcr.py b/basicswap/interface/dcr/dcr.py
index c6195bc..a27efaf 100644
--- a/basicswap/interface/dcr/dcr.py
+++ b/basicswap/interface/dcr/dcr.py
@@ -7,20 +7,29 @@
 
 import base64
 import hashlib
+import json
 import logging
 import random
+import traceback
 
 from basicswap.basicswap_util import (
     getVoutByScriptPubKey,
     TxLockTypes
 )
 from basicswap.chainparams import Coins
-from basicswap.contrib.test_framework.messages import (
-    uint256_from_str,
+from basicswap.contrib.test_framework.script import (
+    CScriptNum,
+)
+from basicswap.interface.base import (
+    Secp256k1Interface,
+)
+from basicswap.interface.btc import (
+    extractScriptLockScriptValues,
+    extractScriptLockRefundScriptValues,
 )
-from basicswap.interface.btc import Secp256k1Interface
 from basicswap.util import (
     ensure,
+    b2h, b2i, i2b, i2h,
 )
 from basicswap.util.address import (
     b58decode,
@@ -36,28 +45,40 @@ from basicswap.util.script import (
 )
 from basicswap.util.extkey import ExtKeyPair
 from basicswap.util.integer import encode_varint
-from basicswap.interface.dcr.rpc import make_rpc_func
+from basicswap.interface.dcr.rpc import make_rpc_func, openrpc
 from .messages import (
+    COutPoint,
     CTransaction,
     CTxIn,
     CTxOut,
-    COutPoint,
+    findOutput,
     SigHashType,
     TxSerializeType,
 )
 from .script import (
-    push_script_data,
-    OP_HASH160,
-    OP_EQUAL,
-    OP_DUP,
-    OP_EQUALVERIFY,
+    OP_CHECKMULTISIG,
+    OP_CHECKSEQUENCEVERIFY,
     OP_CHECKSIG,
+    OP_DROP,
+    OP_DUP,
+    OP_ELSE,
+    OP_ENDIF,
+    OP_EQUAL,
+    OP_EQUALVERIFY,
+    OP_HASH160,
+    OP_IF,
+    push_script_data,
 )
-
 from coincurve.keys import (
     PrivateKey,
     PublicKey,
 )
+from coincurve.ecdsaotves import (
+    ecdsaotves_enc_sign,
+    ecdsaotves_enc_verify,
+    ecdsaotves_dec_sig,
+    ecdsaotves_rec_enc_key
+)
 
 
 SEQUENCE_LOCKTIME_GRANULARITY = 9  # 512 seconds
@@ -203,6 +224,10 @@ class DCRInterface(Secp256k1Interface):
     def watch_blocks_for_scripts() -> bool:
         return True
 
+    @staticmethod
+    def depth_spendable() -> int:
+        return 0
+
     def __init__(self, coin_settings, network, swap_client=None):
         super().__init__(network)
         self._rpc_host = coin_settings.get('rpchost', '127.0.0.1')
@@ -212,8 +237,10 @@ class DCRInterface(Secp256k1Interface):
         self._log = self._sc.log if self._sc and self._sc.log else logging
         self.rpc = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host)
         if 'walletrpcport' in coin_settings:
-            self.rpc_wallet = make_rpc_func(coin_settings['walletrpcport'], self._rpcauth, host=self._rpc_host)
+            self._walletrpcport = coin_settings['walletrpcport']
+            self.rpc_wallet = make_rpc_func(self._walletrpcport, self._rpcauth, host=self._rpc_host)
         else:
+            self._walletrpcport = None
             self.rpc_wallet = None
         self.blocks_confirmed = coin_settings['blocks_confirmed']
         self.setConfTarget(coin_settings['conf_target'])
@@ -221,6 +248,23 @@ class DCRInterface(Secp256k1Interface):
         self._use_segwit = True  # Decred is natively segwit
         self._connection_type = coin_settings['connection_type']
 
+    def open_rpc(self):
+        return openrpc(self._rpcport, self._rpcauth, host=self._rpc_host)
+
+    def json_request(self, rpc_conn, method, params):
+        try:
+            v = rpc_conn.json_request(method, params)
+            r = json.loads(v.decode('utf-8'))
+        except Exception as ex:
+            traceback.print_exc()
+            raise ValueError('RPC Server Error ' + str(ex))
+        if 'error' in r and r['error'] is not None:
+            raise ValueError('RPC error ' + str(r['error']))
+        return r['result']
+
+    def close_rpc(self, rpc_conn):
+        rpc_conn.close()
+
     def use_tx_vsize(self) -> bool:
         return False
 
@@ -242,6 +286,7 @@ class DCRInterface(Secp256k1Interface):
         return b58encode(data + checksum[0:4])
 
     def decode_address(self, address: str) -> bytes:
+        # Different from decodeAddress returns more prefix bytes
         addr_data = b58decode(address)
         if addr_data is None:
             return None
@@ -251,6 +296,9 @@ class DCRInterface(Secp256k1Interface):
             raise ValueError('Checksum mismatch')
         return prefixed_data
 
+    def decodeAddress(self, address: str) -> bytes:
+        return self.decode_address(address)[2:]
+
     def testDaemonRPC(self, with_wallet=True) -> None:
         if with_wallet:
             self.rpc_wallet('getinfo')
@@ -289,6 +337,11 @@ class DCRInterface(Secp256k1Interface):
 
         return rv
 
+    def getSpendableBalance(self) -> int:
+        balances = self.rpc_wallet('getbalance')
+        default_account_bal = balances['balances'][0]  # 0 always default?
+        return self.make_int(default_account_bal['spendable'])
+
     def getSeedHash(self, seed: bytes) -> bytes:
         # m / purpose' / coin_type' / account' / change / address_index
         # m/44'/coin_type'/0'/0/0
@@ -339,6 +392,7 @@ class DCRInterface(Secp256k1Interface):
 
     def setTxSignature(self, tx_bytes: bytes, stack, txi: int = 0) -> bytes:
         tx = self.loadTx(tx_bytes)
+
         script_data = bytearray()
         for data in stack:
             push_script_data(script_data, data)
@@ -390,6 +444,13 @@ class DCRInterface(Secp256k1Interface):
         assert len(pkh) == 20
         return OP_DUP.to_bytes(1) + OP_HASH160.to_bytes(1) + len(pkh).to_bytes(1) + pkh + OP_EQUALVERIFY.to_bytes(1) + OP_CHECKSIG.to_bytes(1)
 
+    def getPkDest(self, K: bytes) -> bytearray:
+        return self.getPubkeyHashDest(self.pkh(K))
+
+    def getSCLockScriptAddress(self, lock_script: bytes) -> str:
+        lock_tx_dest = self.getScriptDest(lock_script)
+        return self.encodeScriptDest(lock_tx_dest)
+
     def get_fee_rate(self, conf_target: int = 2) -> (float, str):
         chain_client_settings = self._sc.getChainClientSettings(self.coin_type())  # basicswap.json
         override_feerate = chain_client_settings.get('override_feerate', None)
@@ -525,6 +586,29 @@ class DCRInterface(Secp256k1Interface):
 
         return sum_value
 
+    def signCompact(self, k, message):
+        message_hash = blake256(bytes(message, 'utf-8'))
+
+        privkey = PrivateKey(k)
+        return privkey.sign_recoverable(message_hash, hasher=None)[:64]
+
+    def signRecoverable(self, k, message: str) -> bytes:
+        message_hash = blake256(bytes(message, 'utf-8'))
+
+        privkey = PrivateKey(k)
+        return privkey.sign_recoverable(message_hash, hasher=None)
+
+    def verifyCompactSig(self, K, message: str, sig) -> None:
+        message_hash = blake256(bytes(message, 'utf-8'))
+        pubkey = PublicKey(K)
+        rv = pubkey.verify_compact(sig, message_hash, hasher=None)
+        assert (rv is True)
+
+    def verifySigAndRecover(self, sig, message: str) -> bytes:
+        message_hash = blake256(bytes(message, 'utf-8'))
+        pubkey = PublicKey.from_signature_and_message(sig, message_hash, hasher=None)
+        return pubkey.format()
+
     def verifyMessage(self, address: str, message: str, signature: str, message_magic: str = None) -> bool:
         if message_magic is None:
             message_magic = self.chainparams()['message_magic']
@@ -546,7 +630,12 @@ class DCRInterface(Secp256k1Interface):
         return True if address_hash == pubkey_hash else False
 
     def signTxWithWallet(self, tx) -> bytes:
-        return bytes.fromhex(self.rpc('signrawtransaction', [tx.hex()])['hex'])
+        return bytes.fromhex(self.rpc_wallet('signrawtransaction', [tx.hex()])['hex'])
+
+    def signTxWithKey(self, tx: bytes, key: bytes) -> bytes:
+        key_wif = self.encodeKey(key)
+        rv = self.rpc_wallet('signrawtransaction', [tx.hex(), [], [key_wif, ]])
+        return bytes.fromhex(rv['hex'])
 
     def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str:
 
@@ -593,6 +682,9 @@ class DCRInterface(Secp256k1Interface):
                 # self._log.warning('gettxout {}'.format(e))
                 return None
 
+        if found_vout is None:
+            return None
+
         block_height: int = 0
         confirmations: int = 0 if 'confirmations' not in txout else txout['confirmations']
 
@@ -601,6 +693,7 @@ class DCRInterface(Secp256k1Interface):
             block_height = self.getChainHeight() - confirmations
 
         rv = {
+            'txid': txid.hex(),
             'depth': confirmations,
             'index': found_vout,
             'height': block_height}
@@ -628,7 +721,7 @@ class DCRInterface(Secp256k1Interface):
     def createRedeemTxn(self, prevout, output_addr: str, output_value: int, txn_script: bytes = None) -> str:
         tx = CTransaction()
         tx.version = self.txVersion()
-        prev_txid = uint256_from_str(bytes.fromhex(prevout['txid'])[::-1])
+        prev_txid = b2i(bytes.fromhex(prevout['txid']))
         tx.vin.append(CTxIn(COutPoint(prev_txid, prevout['vout'], 0)))
         pkh = self.decode_address(output_addr)[2:]
         script = self.getPubkeyHashDest(pkh)
@@ -639,7 +732,7 @@ class DCRInterface(Secp256k1Interface):
         tx = CTransaction()
         tx.version = self.txVersion()
         tx.locktime = locktime
-        prev_txid = uint256_from_str(bytes.fromhex(prevout['txid'])[::-1])
+        prev_txid = b2i(bytes.fromhex(prevout['txid']))
         tx.vin.append(CTxIn(COutPoint(prev_txid, prevout['vout'], 0), sequence=sequence,))
         pkh = self.decode_address(output_addr)[2:]
         script = self.getPubkeyHashDest(pkh)
@@ -678,6 +771,22 @@ class DCRInterface(Secp256k1Interface):
         block_hash = self.rpc('getblockhash', [height])
         return self.rpc('getblockheader', [block_hash])
 
+    def getBlockHeaderAt(self, time: int, block_after=False):
+        blockchaininfo = self.rpc('getblockchaininfo')
+        last_block_header = self.rpc('getblockheader', [blockchaininfo['bestblockhash']])
+
+        max_tries = 5000
+        for i in range(max_tries):
+            prev_block_header = self.rpc('getblockheader', [last_block_header['previousblockhash']])
+            if prev_block_header['time'] <= time:
+                return last_block_header if block_after else prev_block_header
+
+            last_block_header = prev_block_header
+        raise ValueError(f'Block header not found at time: {time}')
+
+    def getMempoolTx(self, txid):
+        raise ValueError('TODO')
+
     def getBlockWithTxns(self, block_hash: str):
         block = self.rpc('getblock', [block_hash, True, True])
 
@@ -697,3 +806,549 @@ class DCRInterface(Secp256k1Interface):
 
     def describeTx(self, tx_hex: str):
         return self.rpc('decoderawtransaction', [tx_hex])
+
+    def fundTx(self, tx: bytes, feerate) -> bytes:
+        feerate_str = float(self.format_amount(feerate))
+        # TODO: unlock unspents if bid cancelled
+        options = {
+            'feeRate': feerate_str,
+        }
+        rv = self.rpc_wallet('fundrawtransaction', [tx.hex(), 'default', options])
+        tx_bytes = bytes.fromhex(rv['hex'])
+
+        tx_obj = self.loadTx(tx_bytes)
+        for txi in tx_obj.vin:
+            utxos = [{'amount': float(self.format_amount(txi.value_in)),
+                      'txid': i2h(txi.prevout.hash),
+                      'vout': txi.prevout.n,
+                      'tree': txi.prevout.tree}]
+            rv = self.rpc_wallet('lockunspent', [False, utxos])
+
+        return tx_bytes
+
+    def createSCLockTx(self, value: int, script: bytearray, vkbv: bytes = None) -> bytes:
+        tx = CTransaction()
+        tx.version = self.txVersion()
+        tx.vout.append(self.txoType()(value, self.getScriptDest(script)))
+        return tx.serialize()
+
+    def fundSCLockTx(self, tx_bytes, feerate, vkbv=None):
+        return self.fundTx(tx_bytes, feerate)
+
+    def genScriptLockRefundTxScript(self, Kal, Kaf, csv_val) -> bytes:
+
+        Kal_enc = Kal if len(Kal) == 33 else self.encodePubkey(Kal)
+        Kaf_enc = Kaf if len(Kaf) == 33 else self.encodePubkey(Kaf)
+
+        script = bytearray()
+        script += OP_IF.to_bytes(1)
+        push_script_data(script, bytes((2,)))
+        push_script_data(script, Kal_enc)
+        push_script_data(script, Kaf_enc)
+        push_script_data(script, bytes((2,)))
+        script += OP_CHECKMULTISIG.to_bytes(1)
+        script += OP_ELSE.to_bytes(1)
+        script += CScriptNum.encode(CScriptNum(csv_val))
+        script += OP_CHECKSEQUENCEVERIFY.to_bytes(1)
+        script += OP_DROP.to_bytes(1)
+        push_script_data(script, Kaf_enc)
+        script += OP_CHECKSIG.to_bytes(1)
+        script += OP_ENDIF.to_bytes(1)
+
+        return script
+
+    def createSCLockSpendTx(self, tx_lock_bytes, script_lock, pkh_dest, tx_fee_rate, vkbv=None, fee_info={}):
+        tx_lock = self.loadTx(tx_lock_bytes)
+        output_script = self.getScriptDest(script_lock)
+        locked_n = findOutput(tx_lock, output_script)
+        ensure(locked_n is not None, 'Output not found in tx')
+        locked_coin = tx_lock.vout[locked_n].value
+
+        tx_lock_id_int = b2i(tx_lock.TxHash())
+
+        tx = CTransaction()
+        tx.version = self.txVersion()
+        tx.vin.append(CTxIn(COutPoint(tx_lock_id_int, locked_n, 0)))
+        tx.vout.append(self.txoType()(locked_coin, self.getPubkeyHashDest(pkh_dest)))
+
+        dummy_witness_stack = self.getScriptLockTxDummyWitness(script_lock)
+        size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
+        pay_fee = round(tx_fee_rate * size / 1000)
+        tx.vout[0].value = locked_coin - pay_fee
+
+        fee_info['fee_paid'] = pay_fee
+        fee_info['rate_used'] = tx_fee_rate
+        fee_info['size'] = size
+
+        self._log.info('createSCLockSpendTx %s:\n    fee_rate, size, fee: %ld, %ld, %ld.',
+                       tx.TxHash().hex(), tx_fee_rate, size, pay_fee)
+
+        return tx.serialize(TxSerializeType.NoWitness)
+
+    def createSCLockRefundTx(self, tx_lock_bytes, script_lock, Kal, Kaf, lock1_value, csv_val, tx_fee_rate, vkbv=None):
+        tx_lock = CTransaction()
+        tx_lock = self.loadTx(tx_lock_bytes)
+
+        output_script = self.getScriptDest(script_lock)
+        locked_n = findOutput(tx_lock, output_script)
+        ensure(locked_n is not None, 'Output not found in tx')
+        locked_coin = tx_lock.vout[locked_n].value
+
+        tx_lock_id_int = b2i(tx_lock.TxHash())
+
+        refund_script = self.genScriptLockRefundTxScript(Kal, Kaf, csv_val)
+        tx = CTransaction()
+        tx.version = self.txVersion()
+        tx.vin.append(CTxIn(COutPoint(tx_lock_id_int, locked_n, 0),
+                            sequence=lock1_value))
+        tx.vout.append(self.txoType()(locked_coin, self.getScriptDest(refund_script)))
+
+        dummy_witness_stack = self.getScriptLockTxDummyWitness(script_lock)
+        size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
+        pay_fee = round(tx_fee_rate * size / 1000)
+        tx.vout[0].value = locked_coin - pay_fee
+
+        self._log.info('createSCLockRefundTx %s:\n    fee_rate, size, fee: %ld, %ld, %ld.',
+                       tx.TxHash().hex(), tx_fee_rate, size, pay_fee)
+
+        return tx.serialize(TxSerializeType.NoWitness), refund_script, tx.vout[0].value
+
+    def createSCLockRefundSpendTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_refund_to, tx_fee_rate, vkbv=None):
+        # Returns the coinA locked coin to the leader
+        # The follower will sign the multisig path with a signature encumbered by the leader's coinB spend pubkey
+        # If the leader publishes the decrypted signature the leader's coinB spend privatekey will be revealed to the follower
+
+        tx_lock_refund = self.loadTx(tx_lock_refund_bytes)
+
+        output_script = self.getScriptDest(script_lock_refund)
+        locked_n = findOutput(tx_lock_refund, output_script)
+        ensure(locked_n is not None, 'Output not found in tx')
+        locked_coin = tx_lock_refund.vout[locked_n].value
+
+        tx_lock_refund_hash_int = b2i(tx_lock_refund.TxHash())
+
+        tx = CTransaction()
+        tx.version = self.txVersion()
+        tx.vin.append(CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n, 0),
+                            sequence=0))
+
+        tx.vout.append(self.txoType()(locked_coin, self.getPubkeyHashDest(pkh_refund_to)))
+
+        dummy_witness_stack = self.getScriptLockRefundSpendTxDummyWitness(script_lock_refund)
+        size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
+        pay_fee = round(tx_fee_rate * size / 1000)
+        tx.vout[0].value = locked_coin - pay_fee
+
+        self._log.info('createSCLockRefundSpendTx %s:\n    fee_rate, size, fee: %ld, %ld, %ld.',
+                       tx.TxHash().hex(), tx_fee_rate, size, pay_fee)
+
+        return tx.serialize(TxSerializeType.NoWitness)
+
+    def verifySCLockTx(self, tx_bytes, script_out,
+                       swap_value,
+                       Kal, Kaf,
+                       feerate,
+                       check_lock_tx_inputs, vkbv=None):
+        # Verify:
+        #
+
+        # Not necessary to check the lock txn is mineable, as protocol will wait for it to confirm
+        # However by checking early we can avoid wasting time processing unmineable txns
+        # Check fee is reasonable
+
+        tx = self.loadTx(tx_bytes)
+        txid = self.getTxid(tx)
+        self._log.info('Verifying lock tx: {}.'.format(b2h(txid)))
+
+        ensure(tx.version == self.txVersion(), 'Bad version')
+        ensure(tx.locktime == 0, 'Bad locktime')
+        ensure(tx.expiry == 0, 'Bad expiry')
+
+        script_pk = self.getScriptDest(script_out)
+        locked_n = findOutput(tx, script_pk)
+        ensure(locked_n is not None, 'Lock output not found in tx')
+        locked_coin = tx.vout[locked_n].value
+
+        # Check value
+        ensure(locked_coin == swap_value, 'Bad locked value')
+
+        # Check script
+        A, B = extractScriptLockScriptValues(script_out)
+        ensure(A == Kal, 'Bad script pubkey')
+        ensure(B == Kaf, 'Bad script pubkey')
+
+        if check_lock_tx_inputs:
+            # TODO: Check that inputs are unspent
+            # Verify fee rate
+            inputs_value = 0
+            add_bytes = 0
+            add_witness_bytes = 0
+            for pi in tx.vin:
+                ptx = self.rpc('getrawtransaction', [i2h(pi.prevout.hash), True])
+                prevout = ptx['vout'][pi.prevout.n]
+                inputs_value += self.make_int(prevout['value'])
+                self._log.info('prevout: {}.'.format(prevout))
+                prevout_type = prevout['scriptPubKey']['type']
+
+                '''
+                if prevout_type == 'witness_v0_keyhash':
+                    #add_witness_bytes += 107  # sig 72, pk 33 and 2 size bytes
+                    #add_witness_bytes += getCompactSizeLen(107)
+                else:
+                    # Assume P2PKH, TODO more types
+                    add_bytes += 107  # OP_PUSH72 <ecdsa_signature> OP_PUSH33 <public_key>
+                '''
+
+            outputs_value = 0
+            for txo in tx.vout:
+                outputs_value += txo.nValue
+            fee_paid = inputs_value - outputs_value
+            assert (fee_paid > 0)
+
+            size = len(tx.serialize()) + add_witness_bytes
+            fee_rate_paid = fee_paid * 1000 // size
+
+            self._log.info('tx amount, size, feerate: %ld, %ld, %ld', locked_coin, size, fee_rate_paid)
+
+            if not self.compareFeeRates(fee_rate_paid, feerate):
+                self._log.warning('feerate paid doesn\'t match expected: %ld, %ld', fee_rate_paid, feerate)
+                # TODO: Display warning to user
+
+        return txid, locked_n
+
+    def verifySCLockSpendTx(self, tx_bytes,
+                            lock_tx_bytes, lock_tx_script,
+                            a_pkhash_f, feerate, vkbv=None):
+        # Verify:
+        #   Must have only one input with correct prevout (n is always 0) and sequence
+        #   Must have only one output with destination and amount
+
+        tx = self.loadTx(tx_bytes)
+        txid = self.getTxid(tx)
+        self._log.info('Verifying lock spend tx: {}.'.format(b2h(txid)))
+
+        ensure(tx.version == self.txVersion(), 'Bad version')
+        ensure(tx.locktime == 0, 'Bad locktime')
+        ensure(tx.expiry == 0, 'Bad expiry')
+        ensure(len(tx.vin) == 1, 'tx doesn\'t have one input')
+
+        lock_tx = self.loadTx(lock_tx_bytes)
+        lock_tx_id = self.getTxid(lock_tx)
+
+        output_script = self.getScriptDest(lock_tx_script)
+        locked_n = findOutput(lock_tx, output_script)
+        ensure(locked_n is not None, 'Output not found in tx')
+        locked_coin = lock_tx.vout[locked_n].value
+
+        ensure(tx.vin[0].sequence == 0, 'Bad input nSequence')
+        ensure(len(tx.vin[0].signature_script) == 0, 'Input sig not empty')
+        ensure(i2b(tx.vin[0].prevout.hash) == lock_tx_id and tx.vin[0].prevout.n == locked_n, 'Input prevout mismatch')
+
+        ensure(len(tx.vout) == 1, 'tx doesn\'t have one output')
+        p2wpkh = self.getPubkeyHashDest(a_pkhash_f)
+        ensure(tx.vout[0].script_pubkey == p2wpkh, 'Bad output destination')
+
+        # The value of the lock tx output should already be verified, if the fee is as expected the difference will be the correct amount
+        fee_paid = locked_coin - tx.vout[0].value
+        assert (fee_paid > 0)
+
+        dummy_witness_stack = self.getScriptLockTxDummyWitness(lock_tx_script)
+        size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
+        fee_rate_paid = fee_paid * 1000 // size
+
+        self._log.info('tx amount, size, feerate: %ld, %ld, %ld', tx.vout[0].value, size, fee_rate_paid)
+
+        if not self.compareFeeRates(fee_rate_paid, feerate):
+            raise ValueError('Bad fee rate, expected: {}'.format(feerate))
+
+        return True
+
+    def verifySCLockRefundTx(self, tx_bytes, lock_tx_bytes, script_out,
+                             prevout_id, prevout_n, prevout_seq, prevout_script,
+                             Kal, Kaf, csv_val_expect, swap_value, feerate, vkbv=None):
+        # Verify:
+        #   Must have only one input with correct prevout and sequence
+        #   Must have only one output to the p2wsh of the lock refund script
+        #   Output value must be locked_coin - lock tx fee
+
+        tx = self.loadTx(tx_bytes)
+        txid = self.getTxid(tx)
+        self._log.info('Verifying lock refund tx: {}.'.format(b2h(txid)))
+
+        ensure(tx.version == self.txVersion(), 'Bad version')
+        ensure(tx.locktime == 0, 'locktime not 0')
+        ensure(tx.expiry == 0, 'Bad expiry')
+        ensure(len(tx.vin) == 1, 'tx doesn\'t have one input')
+
+        ensure(tx.vin[0].sequence == prevout_seq, 'Bad input sequence')
+        ensure(i2b(tx.vin[0].prevout.hash) == prevout_id and tx.vin[0].prevout.n == prevout_n and tx.vin[0].prevout.tree == 0, 'Input prevout mismatch')
+        ensure(len(tx.vin[0].signature_script) == 0, 'Input sig not empty')
+
+        ensure(len(tx.vout) == 1, 'tx doesn\'t have one output')
+
+        script_pk = self.getScriptDest(script_out)
+        locked_n = findOutput(tx, script_pk)
+        ensure(locked_n is not None, 'Output not found in tx')
+        locked_coin = tx.vout[locked_n].value
+
+        # Check script and values
+        A, B, csv_val, C = extractScriptLockRefundScriptValues(script_out)
+        ensure(A == Kal, 'Bad script pubkey')
+        ensure(B == Kaf, 'Bad script pubkey')
+        ensure(csv_val == csv_val_expect, 'Bad script csv value')
+        ensure(C == Kaf, 'Bad script pubkey')
+
+        fee_paid = swap_value - locked_coin
+        assert (fee_paid > 0)
+
+        dummy_witness_stack = self.getScriptLockTxDummyWitness(prevout_script)
+        size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
+        fee_rate_paid = fee_paid * 1000 // size
+
+        self._log.info('tx amount, size, feerate: %ld, %ld, %ld', locked_coin, size, fee_rate_paid)
+
+        if not self.compareFeeRates(fee_rate_paid, feerate):
+            raise ValueError('Bad fee rate, expected: {}'.format(feerate))
+
+        return txid, locked_coin, locked_n
+
+    def verifySCLockRefundSpendTx(self, tx_bytes, lock_refund_tx_bytes,
+                                  lock_refund_tx_id, prevout_script,
+                                  Kal,
+                                  prevout_n, prevout_value, feerate, vkbv=None):
+        # Verify:
+        #   Must have only one input with correct prevout (n is always 0) and sequence
+        #   Must have only one output sending lock refund tx value - fee to leader's address, TODO: follower shouldn't need to verify destination addr
+        tx = self.loadTx(tx_bytes)
+        txid = self.getTxid(tx)
+        self._log.info('Verifying lock refund spend tx: {}.'.format(b2h(txid)))
+
+        ensure(tx.version == self.txVersion(), 'Bad version')
+        ensure(tx.locktime == 0, 'locktime not 0')
+        ensure(tx.expiry == 0, 'Bad expiry')
+        ensure(len(tx.vin) == 1, 'tx doesn\'t have one input')
+
+        ensure(tx.vin[0].sequence == 0, 'Bad input sequence')
+        ensure(len(tx.vin[0].signature_script) == 0, 'Input sig not empty')
+        ensure(i2b(tx.vin[0].prevout.hash) == lock_refund_tx_id and tx.vin[0].prevout.n == 0 and tx.vin[0].prevout.tree == 0, 'Input prevout mismatch')
+
+        ensure(len(tx.vout) == 1, 'tx doesn\'t have one output')
+
+        # Destination doesn't matter to the follower
+        '''
+        p2wpkh = CScript([OP_0, hash160(Kal)])
+        locked_n = findOutput(tx, p2wpkh)
+        ensure(locked_n is not None, 'Output not found in lock refund spend tx')
+        '''
+        tx_value = tx.vout[0].value
+
+        fee_paid = prevout_value - tx_value
+        assert (fee_paid > 0)
+
+        dummy_witness_stack = self.getScriptLockRefundSpendTxDummyWitness(prevout_script)
+        size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
+        fee_rate_paid = fee_paid * 1000 // size
+
+        self._log.info('tx amount, size, feerate: %ld, %ld, %ld', tx_value, size, fee_rate_paid)
+
+        if not self.compareFeeRates(fee_rate_paid, feerate):
+            raise ValueError('Bad fee rate, expected: {}'.format(feerate))
+
+        return True
+
+    def createSCLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate, vkbv=None):
+        # lock refund swipe tx
+        # Sends the coinA locked coin to the follower
+
+        tx_lock_refund = self.loadTx(tx_lock_refund_bytes)
+
+        output_script = self.getScriptDest(script_lock_refund)
+        locked_n = findOutput(tx_lock_refund, output_script)
+        ensure(locked_n is not None, 'Output not found in tx')
+        locked_amount = tx_lock_refund.vout[locked_n].value
+
+        A, B, lock2_value, C = extractScriptLockRefundScriptValues(script_lock_refund)
+
+        tx_lock_refund_hash_int = b2i(tx_lock_refund.TxHash())
+
+        tx = CTransaction()
+        tx.version = self.txVersion()
+        tx.vin.append(CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n, 0),
+                            sequence=lock2_value,))
+
+        tx.vout.append(self.txoType()(locked_amount, self.getPubkeyHashDest(pkh_dest)))
+
+        dummy_witness_stack = self.getScriptLockRefundSwipeTxDummyWitness(script_lock_refund)
+        size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
+        pay_fee = round(tx_fee_rate * size / 1000)
+        tx.vout[0].value = locked_amount - pay_fee
+
+        self._log.info('createSCLockRefundSpendToFTx %s:\n    fee_rate, size, fee: %ld, %ld, %ld.',
+                       tx.TxHash().hex(), tx_fee_rate, size, pay_fee)
+
+        return tx.serialize(TxSerializeType.NoWitness)
+
+    def signTxOtVES(self, key_sign: bytes, pubkey_encrypt: bytes, tx_bytes: bytes, input_n: int, prevout_script: bytes, prevout_value: int) -> bytes:
+        tx = self.loadTx(tx_bytes)
+        sig_hash = DCRSignatureHash(prevout_script, SigHashType.SigHashAll, tx, input_n)
+
+        return ecdsaotves_enc_sign(key_sign, pubkey_encrypt, sig_hash)
+
+    def verifyTxOtVES(self, tx_bytes: bytes, ct: bytes, Ks: bytes, Ke: bytes, input_n: int, prevout_script: bytes, prevout_value):
+        tx = self.loadTx(tx_bytes)
+        sig_hash = DCRSignatureHash(prevout_script, SigHashType.SigHashAll, tx, input_n)
+        return ecdsaotves_enc_verify(Ks, Ke, sig_hash, ct)
+
+    def decryptOtVES(self, k: bytes, esig: bytes) -> bytes:
+        return ecdsaotves_dec_sig(k, esig) + bytes((SigHashType.SigHashAll,))
+
+    def recoverEncKey(self, esig, sig, K):
+        return ecdsaotves_rec_enc_key(K, esig, sig[:-1])  # Strip sighash type
+
+    def getTxOutputPos(self, tx, script):
+        if isinstance(tx, bytes):
+            tx = self.loadTx(tx)
+        script_pk = self.getScriptDest(script)
+        return findOutput(tx, script_pk)
+
+    def getScriptLockTxDummyWitness(self, script: bytes):
+        return [
+            bytes(72),
+            bytes(72),
+            bytes(len(script))
+        ]
+
+    def getScriptLockRefundSpendTxDummyWitness(self, script: bytes):
+        return [
+            bytes(72),
+            bytes(72),
+            bytes((1,)),
+            bytes(len(script))
+        ]
+
+    def extractLeaderSig(self, tx_bytes: bytes) -> bytes:
+        tx = self.loadTx(tx_bytes)
+
+        sig_len = tx.vin[0].signature_script[0]
+        return tx.vin[0].signature_script[1: 1 + sig_len]
+
+    def extractFollowerSig(self, tx_bytes: bytes) -> bytes:
+        tx = self.loadTx(tx_bytes)
+
+        sig_len = tx.vin[0].signature_script[0]
+        ofs = 1 + sig_len
+        sig_len = tx.vin[0].signature_script[ofs]
+        ofs += 1
+        return tx.vin[0].signature_script[ofs: ofs + sig_len]
+
+    def unlockInputs(self, tx_bytes):
+        tx = self.loadTx(tx_bytes)
+
+        inputs = []
+        for txi in tx.vin:
+            inputs.append({'amount': float(self.format_amount(txi.value_in)), 'txid': i2h(txi.prevout.hash), 'vout': txi.prevout.n, 'tree': txi.prevout.tree})
+        self.rpc_wallet('lockunspent', [True, inputs])
+
+    def getWalletRestoreHeight(self) -> int:
+        start_time = self.rpc_wallet('getinfo')['keypoololdest']
+
+        blockchaininfo = self.getBlockchainInfo()
+        best_block = blockchaininfo['bestblockhash']
+
+        chain_synced = round(blockchaininfo['verificationprogress'], 3)
+        if chain_synced < 1.0:
+            raise ValueError('{} chain isn\'t synced.'.format(self.coin_name()))
+
+        self._log.debug('Finding block at time: {}'.format(start_time))
+
+        rpc_conn = self.open_rpc()
+        try:
+            block_hash = best_block
+            while True:
+                block_header = self.json_request(rpc_conn, 'getblockheader', [block_hash])
+                if block_header['time'] < start_time:
+                    return block_header['height']
+                # genesis block
+                if block_header['previousblockhash'] == '0000000000000000000000000000000000000000000000000000000000000000':
+                    return block_header['height']
+
+                block_hash = block_header['previousblockhash']
+        finally:
+            self.close_rpc(rpc_conn)
+        raise ValueError('{} wallet restore height not found.'.format(self.coin_name()))
+
+    def createBLockTx(self, Kbs, output_amount, vkbv=None) -> bytes:
+        tx = CTransaction()
+        tx.version = self.txVersion()
+        script_pk = self.getPkDest(Kbs)
+        tx.vout.append(self.txoType()(output_amount, script_pk))
+        return tx.serialize()
+
+    def publishBLockTx(self, kbv, Kbs, output_amount, feerate, unlock_time: int = 0) -> bytes:
+        b_lock_tx = self.createBLockTx(Kbs, output_amount)
+
+        b_lock_tx = self.fundTx(b_lock_tx, feerate)
+        b_lock_tx_id = self.getTxid(b_lock_tx)
+        b_lock_tx = self.signTxWithWallet(b_lock_tx)
+
+        return bytes.fromhex(self.publishTx(b_lock_tx))
+
+    def getBLockSpendTxFee(self, tx, fee_rate: int) -> int:
+        witness_bytes = 120  # TODO
+        size = len(tx.serialize()) + witness_bytes
+        pay_fee = round(fee_rate * size / 1000)
+        self._log.info(f'BLockSpendTx fee_rate, vsize, fee: {fee_rate}, {size}, {pay_fee}.')
+        return pay_fee
+
+    def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int, lock_tx_vout=None) -> bytes:
+        self._log.info('spendBLockTx %s:\n', chain_b_lock_txid.hex())
+        locked_n = lock_tx_vout
+
+        Kbs = self.getPubkey(kbs)
+        script_pk = self.getPkDest(Kbs)
+
+        if locked_n is None:
+            self._log.debug(f'Unknown lock vout, searching tx: {chain_b_lock_txid.hex()}')
+            # When refunding a lock tx, it should be in the wallet as a sent tx
+            wtx = self.rpc_wallet('gettransaction', [chain_b_lock_txid.hex(), ])
+            lock_tx = self.loadTx(bytes.fromhex(wtx['hex']))
+            locked_n = findOutput(lock_tx, script_pk)
+
+        ensure(locked_n is not None, 'Output not found in tx')
+        pkh_to = self.decodeAddress(address_to)
+
+        tx = CTransaction()
+        tx.version = self.txVersion()
+
+        chain_b_lock_txid_int = b2i(chain_b_lock_txid)
+
+        tx.vin.append(CTxIn(COutPoint(chain_b_lock_txid_int, locked_n, 0),
+                            sequence=0))
+        tx.vout.append(self.txoType()(cb_swap_value, self.getPubkeyHashDest(pkh_to)))
+
+        pay_fee = self.getBLockSpendTxFee(tx, b_fee)
+        tx.vout[0].value = cb_swap_value - pay_fee
+
+        b_lock_spend_tx = tx.serialize()
+        b_lock_spend_tx = self.signTxWithKey(b_lock_spend_tx, kbs)
+
+        return bytes.fromhex(self.publishTx(b_lock_spend_tx))
+
+    def findTxnByHash(self, txid_hex: str):
+        try:
+            txout = self.rpc('gettxout', [txid_hex, 0, 0, True])
+        except Exception as e:
+            # self._log.warning('gettxout {}'.format(e))
+            return None
+
+        confirmations: int = 0 if 'confirmations' not in txout else txout['confirmations']
+        if confirmations >= self.blocks_confirmed:
+            block_height = self.getChainHeight() - confirmations  # TODO: Better way?
+            return {'txid': txid_hex, 'amount': 0, 'height': block_height}
+        return None
+
+    def isTxExistsError(self, err_str: str) -> bool:
+        return 'transaction already exists' in err_str or 'already have transaction' in err_str
+
+    def isTxNonFinalError(self, err_str: str) -> bool:
+        return 'locks on inputs not met' in err_str
diff --git a/basicswap/interface/dcr/messages.py b/basicswap/interface/dcr/messages.py
index 8138c41..2c37aa0 100644
--- a/basicswap/interface/dcr/messages.py
+++ b/basicswap/interface/dcr/messages.py
@@ -153,7 +153,7 @@ class CTransaction:
             o += script_bytes
 
     def serialize(self, ser_type=TxSerializeType.Full) -> bytes:
-        data = bytearray()
+        data = bytes()
         version = (self.version & 0xffff) | (ser_type << 16)
         data += version.to_bytes(4, 'little')
 
@@ -195,3 +195,10 @@ class CTransaction:
 
     def TxHashFull(self) -> bytes:
         raise ValueError('todo')
+
+
+def findOutput(tx, script_pk: bytes):
+    for i in range(len(tx.vout)):
+        if tx.vout[i].script_pubkey == script_pk:
+            return i
+    return None
diff --git a/basicswap/interface/dcr/rpc.py b/basicswap/interface/dcr/rpc.py
index fd9761b..b536e11 100644
--- a/basicswap/interface/dcr/rpc.py
+++ b/basicswap/interface/dcr/rpc.py
@@ -27,6 +27,15 @@ def callrpc(rpc_port, auth, method, params=[], host='127.0.0.1'):
     return r['result']
 
 
+def openrpc(rpc_port, auth, host='127.0.0.1'):
+    try:
+        url = 'http://{}@{}:{}/'.format(auth, host, rpc_port)
+        return Jsonrpc(url)
+    except Exception as ex:
+        traceback.print_exc()
+        raise ValueError('RPC error ' + str(ex))
+
+
 def make_rpc_func(port, auth, host='127.0.0.1'):
     port = port
     auth = auth
diff --git a/basicswap/interface/dcr/script.py b/basicswap/interface/dcr/script.py
index bdda286..a40edb2 100644
--- a/basicswap/interface/dcr/script.py
+++ b/basicswap/interface/dcr/script.py
@@ -9,14 +9,19 @@ OP_0 = 0x00
 OP_DATA_1 = 0x01
 OP_1NEGATE = 0x4f
 OP_1 = 0x51
+OP_IF = 0x63
+OP_ELSE = 0x67
+OP_ENDIF = 0x68
+OP_DROP = 0x75
+OP_DUP = 0x76
 OP_EQUAL = 0x87
+OP_EQUALVERIFY = 0x88
 OP_PUSHDATA1 = 0x4c
 OP_PUSHDATA2 = 0x4d
 OP_PUSHDATA4 = 0x4e
-OP_DUP = 0x76
-OP_EQUALVERIFY = 0x88
 OP_HASH160 = 0xa9
 OP_CHECKSIG = 0xac
+OP_CHECKMULTISIG = 0xae
 OP_CHECKSEQUENCEVERIFY = 0xb2
 
 
diff --git a/basicswap/interface/firo.py b/basicswap/interface/firo.py
index 0be2c3f..d7f7701 100644
--- a/basicswap/interface/firo.py
+++ b/basicswap/interface/firo.py
@@ -76,7 +76,7 @@ class FIROInterface(BTCInterface):
             return addr_info['ismine']
         return addr_info['ismine'] or addr_info['iswatchonly']
 
-    def getSCLockScriptAddress(self, lock_script):
+    def getSCLockScriptAddress(self, lock_script: bytes) -> str:
         lock_tx_dest = self.getScriptDest(lock_script)
         address = self.encodeScriptDest(lock_tx_dest)
 
@@ -201,7 +201,7 @@ class FIROInterface(BTCInterface):
         add_bytes = 107
         size = len(tx.serialize_with_witness()) + add_bytes
         pay_fee = round(fee_rate * size / 1000)
-        self._log.info(f'BLockSpendTx  fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}.')
+        self._log.info(f'BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}.')
         return pay_fee
 
     def signTxWithKey(self, tx: bytes, key: bytes) -> bytes:
diff --git a/basicswap/interface/nav.py b/basicswap/interface/nav.py
index 250d257..ade690e 100644
--- a/basicswap/interface/nav.py
+++ b/basicswap/interface/nav.py
@@ -13,7 +13,12 @@ from coincurve.keys import (
     PublicKey,
     PrivateKey,
 )
-from .btc import BTCInterface, find_vout_for_address_from_txobj, findOutput
+from basicswap.interface.btc import (
+    BTCInterface,
+    extractScriptLockRefundScriptValues,
+    findOutput,
+    find_vout_for_address_from_txobj,
+)
 from basicswap.rpc import make_rpc_func
 from basicswap.chainparams import Coins
 from basicswap.interface.contrib.nav_test_framework.mininode import (
@@ -24,7 +29,6 @@ from basicswap.interface.contrib.nav_test_framework.mininode import (
     CTransaction,
     CTxInWitness,
     FromHex,
-    uint256_from_str,
 )
 from basicswap.util.crypto import hash160
 from basicswap.util.address import (
@@ -33,7 +37,7 @@ from basicswap.util.address import (
     encodeAddress,
 )
 from basicswap.util import (
-    i2b, i2h,
+    b2i, i2b, i2h,
     ensure,
 )
 from basicswap.basicswap_util import (
@@ -305,7 +309,7 @@ class NAVInterface(BTCInterface):
     def createRedeemTxn(self, prevout, output_addr: str, output_value: int, txn_script: bytes) -> str:
         tx = CTransaction()
         tx.nVersion = self.txVersion()
-        prev_txid = uint256_from_str(bytes.fromhex(prevout['txid'])[::-1])
+        prev_txid = b2i(bytes.fromhex(prevout['txid']))
 
         tx.vin.append(CTxIn(COutPoint(prev_txid, prevout['vout']),
                             scriptSig=self.getScriptScriptSig(txn_script)))
@@ -319,7 +323,7 @@ class NAVInterface(BTCInterface):
         tx = CTransaction()
         tx.nVersion = self.txVersion()
         tx.nLockTime = locktime
-        prev_txid = uint256_from_str(bytes.fromhex(prevout['txid'])[::-1])
+        prev_txid = b2i(bytes.fromhex(prevout['txid']))
         tx.vin.append(CTxIn(COutPoint(prev_txid, prevout['vout']),
                             nSequence=sequence,
                             scriptSig=self.getScriptScriptSig(txn_script)))
@@ -512,7 +516,7 @@ class NAVInterface(BTCInterface):
         tx.vout.append(self.txoType()(output_amount, script_pk))
         return tx.serialize()
 
-    def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int) -> bytes:
+    def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int, lock_tx_vout=None) -> bytes:
         self._log.info('spendBLockTx %s:\n', chain_b_lock_txid.hex())
         wtx = self.rpc('gettransaction', [chain_b_lock_txid.hex(), ])
         lock_tx = self.loadTx(bytes.fromhex(wtx['hex']))
@@ -526,7 +530,7 @@ class NAVInterface(BTCInterface):
         tx = CTransaction()
         tx.nVersion = self.txVersion()
 
-        chain_b_lock_txid_int = uint256_from_str(chain_b_lock_txid[::-1])
+        chain_b_lock_txid_int = b2i(chain_b_lock_txid)
 
         script_sig = self.getInputScriptForPubkeyHash(self.getPubkeyHash(Kbs))
 
@@ -678,7 +682,7 @@ class NAVInterface(BTCInterface):
         ensure(locked_n is not None, 'Output not found in tx')
         locked_coin = tx_lock_refund.vout[locked_n].nValue
 
-        A, B, lock2_value, C = self.extractScriptLockRefundScriptValues(script_lock_refund)
+        A, B, lock2_value, C = extractScriptLockRefundScriptValues(script_lock_refund)
 
         tx_lock_refund.rehash()
         tx_lock_refund_hash_int = tx_lock_refund.sha256
diff --git a/basicswap/interface/part.py b/basicswap/interface/part.py
index db9af17..8d54cf2 100644
--- a/basicswap/interface/part.py
+++ b/basicswap/interface/part.py
@@ -28,8 +28,13 @@ from basicswap.util.script import (
 from basicswap.util.address import (
     encodeStealthAddress,
 )
+from basicswap.interface.btc import (
+    BTCInterface,
+    extractScriptLockScriptValues,
+    extractScriptLockRefundScriptValues,
+)
+
 from basicswap.chainparams import Coins, chainparams
-from .btc import BTCInterface
 
 
 class BalanceTypes(IntEnum):
@@ -354,7 +359,7 @@ class PARTInterfaceBlind(PARTInterface):
         lock_txo_scriptpk = bytes.fromhex(lock_tx_obj['vout'][lock_output_n]['scriptPubKey']['hex'])
         script_pk = CScript([OP_0, hashlib.sha256(script_out).digest()])
         ensure(lock_txo_scriptpk == script_pk, 'Bad output script')
-        A, B = self.extractScriptLockScriptValues(script_out)
+        A, B = extractScriptLockScriptValues(script_out)
         ensure(A == Kal, 'Bad script leader pubkey')
         ensure(B == Kaf, 'Bad script follower pubkey')
 
@@ -402,7 +407,7 @@ class PARTInterfaceBlind(PARTInterface):
         lock_refund_txo_scriptpk = bytes.fromhex(lock_refund_tx_obj['vout'][lock_refund_output_n]['scriptPubKey']['hex'])
         script_pk = CScript([OP_0, hashlib.sha256(script_out).digest()])
         ensure(lock_refund_txo_scriptpk == script_pk, 'Bad output script')
-        A, B, csv_val, C = self.extractScriptLockRefundScriptValues(script_out)
+        A, B, csv_val, C = extractScriptLockRefundScriptValues(script_out)
         ensure(A == Kal, 'Bad script pubkey')
         ensure(B == Kaf, 'Bad script pubkey')
         ensure(csv_val == csv_val_expect, 'Bad script csv value')
@@ -632,7 +637,7 @@ class PARTInterfaceBlind(PARTInterface):
         addr_info = self.rpc_wallet('getaddressinfo', [addr_out])
         output_pubkey_hex = addr_info['pubkey']
 
-        A, B, lock2_value, C = self.extractScriptLockRefundScriptValues(script_lock_refund)
+        A, B, lock2_value, C = extractScriptLockRefundScriptValues(script_lock_refund)
 
         # Follower won't be able to decode output to check amount, shouldn't matter as fee is public and output is to leader, sum has to balance
 
@@ -715,7 +720,7 @@ class PARTInterfaceBlind(PARTInterface):
                 return -1
         return None
 
-    def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int, spend_actual_balance: bool = False) -> bytes:
+    def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int, spend_actual_balance: bool = False, lock_tx_vout=None) -> bytes:
         Kbv = self.getPubkey(kbv)
         Kbs = self.getPubkey(kbs)
         sx_addr = self.formatStealthAddress(Kbv, Kbs)
@@ -851,7 +856,7 @@ class PARTInterfaceAnon(PARTInterface):
                 return -1
         return None
 
-    def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int, spend_actual_balance: bool = False) -> bytes:
+    def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int, spend_actual_balance: bool = False, lock_tx_vout=None) -> bytes:
         Kbv = self.getPubkey(kbv)
         Kbs = self.getPubkey(kbs)
         sx_addr = self.formatStealthAddress(Kbv, Kbs)
diff --git a/basicswap/interface/pivx.py b/basicswap/interface/pivx.py
index de420c5..038744c 100644
--- a/basicswap/interface/pivx.py
+++ b/basicswap/interface/pivx.py
@@ -107,7 +107,7 @@ class PIVXInterface(BTCInterface):
         add_bytes = 107
         size = len(tx.serialize_with_witness()) + add_bytes
         pay_fee = round(fee_rate * size / 1000)
-        self._log.info(f'BLockSpendTx  fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}.')
+        self._log.info(f'BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}.')
         return pay_fee
 
     def signTxWithKey(self, tx: bytes, key: bytes) -> bytes:
diff --git a/basicswap/interface/xmr.py b/basicswap/interface/xmr.py
index 104c588..30b587f 100644
--- a/basicswap/interface/xmr.py
+++ b/basicswap/interface/xmr.py
@@ -409,7 +409,7 @@ class XMRInterface(CoinInterface):
 
             return None
 
-    def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee_rate: int, restore_height: int, spend_actual_balance: bool = False) -> bytes:
+    def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee_rate: int, restore_height: int, spend_actual_balance: bool = False, lock_tx_vout=None) -> bytes:
         '''
         Notes:
         "Error: No unlocked balance in the specified subaddress(es)" can mean not enough funds after tx fee.
diff --git a/basicswap/protocols/xmr_swap_1.py b/basicswap/protocols/xmr_swap_1.py
index 70acc53..833449c 100644
--- a/basicswap/protocols/xmr_swap_1.py
+++ b/basicswap/protocols/xmr_swap_1.py
@@ -21,13 +21,17 @@ from basicswap.basicswap_util import (
 from . import ProtocolInterface
 from basicswap.contrib.test_framework.script import (
     CScript, CScriptOp,
-    OP_CHECKMULTISIG)
+    OP_CHECKMULTISIG
+)
 
 
 def addLockRefundSigs(self, xmr_swap, ci):
     self.log.debug('Setting lock refund tx sigs')
-    witness_stack = [
-        b'',
+
+    witness_stack = []
+    if ci.coin_type() not in (Coins.DCR, ):
+        witness_stack += [b'', ]
+    witness_stack += [
         xmr_swap.al_lock_refund_tx_sig,
         xmr_swap.af_lock_refund_tx_sig,
         xmr_swap.a_lock_tx_script,
@@ -74,7 +78,8 @@ def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key):
             address_to = self.getCachedStealthAddressForCoin(offer.coin_to)
 
         amount = bid.amount_to
-        txid = ci_to.spendBLockTx(xmr_swap.b_lock_tx_id, address_to, xmr_swap.vkbv, vkbs, bid.amount_to, xmr_offer.b_fee_rate, bid.chain_b_height_start, spend_actual_balance=True)
+        lock_tx_vout = bid.getLockTXBVout()
+        txid = ci_to.spendBLockTx(xmr_swap.b_lock_tx_id, address_to, xmr_swap.vkbv, vkbs, amount, xmr_offer.b_fee_rate, bid.chain_b_height_start, spend_actual_balance=True, lock_tx_vout=lock_tx_vout)
         self.log.debug('Submitted lock B spend txn %s to %s chain for bid %s', txid.hex(), ci_to.coin_name(), bid_id.hex())
         self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_SPEND_TX_PUBLISHED, txid.hex(), session)
         session.commit()
diff --git a/tests/basicswap/common.py b/tests/basicswap/common.py
index 604acc0..6ad5f89 100644
--- a/tests/basicswap/common.py
+++ b/tests/basicswap/common.py
@@ -419,9 +419,10 @@ def compare_bid_states(states, expect_states, exact_match: bool = True) -> bool:
     return True
 
 
-def compare_bid_states_unordered(states, expect_states) -> bool:
+def compare_bid_states_unordered(states, expect_states, ignore_states=[]) -> bool:
+    ignore_states.append('Bid Delaying')
     for i in range(len(states) - 1, -1, -1):
-        if states[i][1] == 'Bid Delaying':
+        if states[i][1] in ignore_states:
             del states[i]
 
     try:
diff --git a/tests/basicswap/extended/test_dcr.py b/tests/basicswap/extended/test_dcr.py
index de861a1..49f2a86 100644
--- a/tests/basicswap/extended/test_dcr.py
+++ b/tests/basicswap/extended/test_dcr.py
@@ -5,9 +5,6 @@
 # Distributed under the MIT software license, see the accompanying
 # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
 
-# TODO
-#  - Occasionally DCR simnet chain stalls.
-
 import copy
 import logging
 import os
@@ -27,6 +24,7 @@ from basicswap.basicswap import (
 )
 from basicswap.basicswap_util import (
     TxLockTypes,
+    TxTypes
 )
 from basicswap.util.crypto import (
     hash160
@@ -80,7 +78,25 @@ def make_rpc_func(node_id, base_rpc_port):
     return rpc_func
 
 
-def test_success_path(self, coin_from: Coins, coin_to: Coins):
+def wait_for_dcr_height(http_port, num_blocks=3):
+    logging.info('Waiting for DCR chain height %d', num_blocks)
+    for i in range(60):
+        if test_delay_event.is_set():
+            raise ValueError('Test stopped.')
+        try:
+            wallet = read_json_api(http_port, 'wallets/dcr')
+            decred_blocks = wallet['blocks']
+            print('decred_blocks', decred_blocks)
+            if decred_blocks >= num_blocks:
+                return
+        except Exception as e:
+            print('Error reading wallets', str(e))
+
+        test_delay_event.wait(1)
+    raise ValueError(f'wait_for_decred_blocks failed http_port: {http_port}')
+
+
+def run_test_success_path(self, coin_from: Coins, coin_to: Coins):
     logging.info(f'---------- Test {coin_from.name} to {coin_to.name}')
 
     node_from = 0
@@ -106,7 +122,7 @@ def test_success_path(self, coin_from: Coins, coin_to: Coins):
     swap_clients[node_from].acceptBid(bid_id)
 
     wait_for_bid(test_delay_event, swap_clients[node_from], bid_id, BidStates.SWAP_COMPLETED, wait_for=120)
-    wait_for_bid(test_delay_event, swap_clients[node_to], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=120)
+    wait_for_bid(test_delay_event, swap_clients[node_to], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=30)
 
     # Verify lock tx spends are found in the expected wallets
     bid, offer = swap_clients[node_from].getBidAndOffer(bid_id)
@@ -139,7 +155,7 @@ def test_success_path(self, coin_from: Coins, coin_to: Coins):
     assert (compare_bid_states(bidder_states, self.states_bidder_sh[0]) is True)
 
 
-def test_bad_ptx(self, coin_from: Coins, coin_to: Coins):
+def run_test_bad_ptx(self, coin_from: Coins, coin_to: Coins):
     # Invalid PTX sent, swap should stall and ITx and PTx should be reclaimed by senders
     logging.info(f'---------- Test bad ptx {coin_from.name} to {coin_to.name}')
 
@@ -164,7 +180,7 @@ def test_bad_ptx(self, coin_from: Coins, coin_to: Coins):
     swap_clients[node_to].setBidDebugInd(bid_id, DebugTypes.MAKE_INVALID_PTX)
 
     wait_for_bid(test_delay_event, swap_clients[node_from], bid_id, BidStates.SWAP_COMPLETED, wait_for=120)
-    wait_for_bid(test_delay_event, swap_clients[node_to], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=120)
+    wait_for_bid(test_delay_event, swap_clients[node_to], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=30)
 
     js_0_bid = read_json_api(1800 + node_from, 'bids/{}'.format(bid_id.hex()))
     js_1_bid = read_json_api(1800 + node_to, 'bids/{}'.format(bid_id.hex()))
@@ -190,6 +206,8 @@ def test_bad_ptx(self, coin_from: Coins, coin_to: Coins):
     offerer_states = read_json_api(1800 + node_from, path)
     bidder_states = read_json_api(1800 + node_to, path)
 
+    if coin_to not in (Coins.XMR,):
+        return
     # Hard to get the timing right
     assert (compare_bid_states_unordered(offerer_states, self.states_offerer_sh[1]) is True)
     assert (compare_bid_states_unordered(bidder_states, self.states_bidder_sh[1]) is True)
@@ -200,7 +218,7 @@ def test_bad_ptx(self, coin_from: Coins, coin_to: Coins):
     assert (js_1['num_swapping'] == 0 and js_1['num_watched_outputs'] == 0)
 
 
-def test_itx_refund(self, coin_from: Coins, coin_to: Coins):
+def run_test_itx_refund(self, coin_from: Coins, coin_to: Coins):
     # Offerer claims PTX and refunds ITX after lock expires
     # Bidder loses PTX value without gaining ITX value
     logging.info(f'---------- Test itx refund {coin_from.name} to {coin_to.name}')
@@ -250,6 +268,189 @@ def test_itx_refund(self, coin_from: Coins, coin_to: Coins):
     assert (bid.amount_to - node_from_ci_to.make_int(wtx['details'][0]['amount']) < max_fee)
 
 
+def run_test_ads_success_path(self, coin_from: Coins, coin_to: Coins):
+    logging.info(f'---------- Test ADS swap {coin_from.name} to {coin_to.name}')
+
+    # Offerer sends the offer
+    # Bidder sends the bid
+    id_offerer: int = 0
+    id_bidder: int = 1
+
+    swap_clients = self.swap_clients
+    reverse_bid: bool = coin_from in swap_clients[id_offerer].scriptless_coins
+    ci_from = swap_clients[id_offerer].ci(coin_from)
+    ci_to = swap_clients[id_bidder].ci(coin_to)
+
+    self.prepare_balance(coin_to, 100.0, 1801, 1800)
+    self.prepare_balance(coin_from, 100.0, 1800, 1801)
+
+    # Leader sends the initial (chain a) lock tx.
+    # Follower sends the participate (chain b) lock tx.
+    id_leader: int = id_bidder if reverse_bid else id_offerer
+    id_follower: int = id_offerer if reverse_bid else id_bidder
+    logging.info(f'Offerer, bidder, leader, follower: {id_offerer}, {id_bidder}, {id_leader}, {id_follower}')
+
+    amt_swap = ci_from.make_int(random.uniform(0.1, 2.0), r=1)
+    rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1)
+    offer_id = swap_clients[id_offerer].postOffer(coin_from, coin_to, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP)
+
+    wait_for_offer(test_delay_event, swap_clients[id_bidder], offer_id)
+    bid_id = swap_clients[id_bidder].postXmrBid(offer_id, amt_swap)
+    wait_for_bid(test_delay_event, swap_clients[id_offerer], bid_id, BidStates.BID_RECEIVED)
+
+    swap_clients[id_offerer].acceptBid(bid_id)
+
+    wait_for_bid(test_delay_event, swap_clients[id_offerer], bid_id, BidStates.SWAP_COMPLETED, wait_for=(180))
+    wait_for_bid(test_delay_event, swap_clients[id_bidder], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=(30))
+
+    if reverse_bid:
+        return  # TODO
+
+    # Verify lock tx spends are found in the expected wallets
+    bid, xmr_swap = swap_clients[id_offerer].getXmrBid(bid_id)
+
+    node_from_ci_to = swap_clients[0].ci(coin_to)
+    max_fee: int = 10000
+    if node_from_ci_to.coin_type() in (Coins.XMR, ):
+        pass
+    else:
+        wtx = node_from_ci_to.rpc_wallet('gettransaction', [bid.xmr_b_lock_tx.spend_txid.hex(),])
+        assert (bid.amount_to - node_from_ci_to.make_int(wtx['details'][0]['amount']) < max_fee)
+
+    node_to_ci_from = swap_clients[1].ci(coin_from)
+    if node_to_ci_from.coin_type() in (Coins.XMR, ):
+        pass
+    else:
+        wtx = node_to_ci_from.rpc_wallet('gettransaction', [xmr_swap.a_lock_spend_tx_id.hex(),])
+        assert (bid.amount - node_to_ci_from.make_int(wtx['details'][0]['amount']) < max_fee)
+
+    bid_id_hex = bid_id.hex()
+    path = f'bids/{bid_id_hex}/states'
+    offerer_states = read_json_api(1800 + id_offerer, path)
+    bidder_states = read_json_api(1800 + id_bidder, path)
+
+    assert (compare_bid_states(offerer_states, self.states_offerer[0]) is True)
+    assert (compare_bid_states(bidder_states, self.states_bidder[0]) is True)
+
+
+def run_test_ads_both_refund(self, coin_from: Coins, coin_to: Coins, lock_value: int = 32) -> None:
+    logging.info('---------- Test {} to {} both lock txns refunded'.format(coin_from.name, coin_to.name))
+
+    id_offerer: int = 0
+    id_bidder: int = 1
+
+    swap_clients = self.swap_clients
+    reverse_bid: bool = coin_from in swap_clients[id_offerer].scriptless_coins
+    ci_from = swap_clients[id_offerer].ci(coin_from)
+    ci_to = swap_clients[id_offerer].ci(coin_to)
+
+    if reverse_bid:
+        self.prepare_balance(coin_to, 100.0, 1801, 1800)
+        self.prepare_balance(coin_from, 100.0, 1800, 1801)
+
+    id_leader: int = id_bidder if reverse_bid else id_offerer
+    id_follower: int = id_offerer if reverse_bid else id_bidder
+    logging.info(f'Offerer, bidder, leader, follower: {id_offerer}, {id_bidder}, {id_leader}, {id_follower}')
+
+    amt_swap = ci_from.make_int(random.uniform(0.1, 2.0), r=1)
+    rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1)
+
+    debug_case = (None, DebugTypes.OFFER_LOCK_2_VALUE_INC)
+    try:
+        swap_clients[0]._debug_cases.append(debug_case)
+        swap_clients[1]._debug_cases.append(debug_case)
+        offer_id = swap_clients[id_offerer].postOffer(
+            coin_from, coin_to, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP,
+            lock_type=TxLockTypes.SEQUENCE_LOCK_BLOCKS, lock_value=lock_value)
+        wait_for_offer(test_delay_event, swap_clients[id_bidder], offer_id)
+        offer = swap_clients[id_bidder].getOffer(offer_id)
+    finally:
+        swap_clients[0]._debug_cases.remove(debug_case)
+        swap_clients[1]._debug_cases.remove(debug_case)
+
+    bid_id = swap_clients[id_bidder].postXmrBid(offer_id, offer.amount_from)
+    wait_for_bid(test_delay_event, swap_clients[id_offerer], bid_id, BidStates.BID_RECEIVED)
+
+    swap_clients[id_follower].setBidDebugInd(bid_id, DebugTypes.CREATE_INVALID_COIN_B_LOCK)
+    swap_clients[id_offerer].acceptBid(bid_id)
+
+    leader_sent_bid: bool = True if reverse_bid else False
+    wait_for_bid(test_delay_event, swap_clients[id_leader], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, sent=leader_sent_bid, wait_for=(self.extra_wait_time + 180))
+    wait_for_bid(test_delay_event, swap_clients[id_follower], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, sent=(not leader_sent_bid), wait_for=(self.extra_wait_time + 40))
+
+    if reverse_bid:
+        return  # TODO
+
+    # Verify lock tx spends are found in the expected wallets
+    bid, xmr_swap = swap_clients[id_offerer].getXmrBid(bid_id)
+    lock_refund_spend_txid = bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND_SPEND].txid
+
+    bid, xmr_swap = swap_clients[id_bidder].getXmrBid(bid_id)
+
+    node_from_ci_from = swap_clients[0].ci(coin_from)
+    max_fee: int = 10000
+    if node_from_ci_from.coin_type() in (Coins.XMR, ):
+        pass
+    else:
+        wtx = node_from_ci_from.rpc_wallet('gettransaction', [lock_refund_spend_txid.hex(),])
+        assert (bid.amount - node_from_ci_from.make_int(wtx['details'][0]['amount']) < max_fee)
+
+    node_to_ci_to = swap_clients[1].ci(coin_to)
+    if node_to_ci_to.coin_type() in (Coins.XMR, ):
+        pass
+    else:
+        wtx = node_to_ci_to.rpc_wallet('gettransaction', [bid.xmr_b_lock_tx.spend_txid.hex(),])
+        assert (bid.amount_to - node_to_ci_to.make_int(wtx['details'][0]['amount']) < max_fee)
+
+    bid_id_hex = bid_id.hex()
+    path = f'bids/{bid_id_hex}/states'
+    offerer_states = read_json_api(1800 + id_offerer, path)
+    bidder_states = read_json_api(1800 + id_bidder, path)
+
+    assert (compare_bid_states(offerer_states, self.states_offerer[1]) is True)
+    assert (compare_bid_states(bidder_states, self.states_bidder[1]) is True)
+
+
+def run_test_ads_swipe_refund(self, coin_from: Coins, coin_to: Coins, lock_value: int = 32) -> None:
+    logging.info('---------- Test {} to {} coin a lock refund tx swiped'.format(coin_from.name, coin_to.name))
+
+    id_offerer: int = 0
+    id_bidder: int = 1
+
+    swap_clients = self.swap_clients
+    reverse_bid: bool = coin_from in swap_clients[id_offerer].scriptless_coins
+    ci_from = swap_clients[id_offerer].ci(coin_from)
+    ci_to = swap_clients[id_offerer].ci(coin_to)
+
+    id_leader: int = id_bidder if reverse_bid else id_offerer
+    id_follower: int = id_offerer if reverse_bid else id_bidder
+    logging.info(f'Offerer, bidder, leader, follower: {id_offerer}, {id_bidder}, {id_leader}, {id_follower}')
+
+    if reverse_bid:
+        self.prepare_balance(coin_to, 100.0, 1801, 1800)
+        self.prepare_balance(coin_from, 100.0, 1800, 1801)
+
+    amt_swap = ci_from.make_int(random.uniform(0.1, 2.0), r=1)
+    rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1)
+    offer_id = swap_clients[id_offerer].postOffer(
+        coin_from, coin_to, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP,
+        lock_type=TxLockTypes.SEQUENCE_LOCK_BLOCKS, lock_value=lock_value)
+    wait_for_offer(test_delay_event, swap_clients[id_bidder], offer_id)
+    offer = swap_clients[id_bidder].getOffer(offer_id)
+
+    bid_id = swap_clients[id_bidder].postXmrBid(offer_id, offer.amount_from)
+    wait_for_bid(test_delay_event, swap_clients[id_offerer], bid_id, BidStates.BID_RECEIVED)
+
+    swap_clients[id_follower].setBidDebugInd(bid_id, DebugTypes.BID_STOP_AFTER_COIN_A_LOCK)
+    swap_clients[id_leader].setBidDebugInd(bid_id, DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND)
+
+    swap_clients[id_offerer].acceptBid(bid_id)
+
+    leader_sent_bid: bool = True if reverse_bid else False
+    wait_for_bid(test_delay_event, swap_clients[id_leader], bid_id, BidStates.BID_STALLED_FOR_TEST, wait_for=(self.extra_wait_time + 180), sent=leader_sent_bid)
+    wait_for_bid(test_delay_event, swap_clients[id_follower], bid_id, BidStates.XMR_SWAP_FAILED_SWIPED, wait_for=(self.extra_wait_time + 80), sent=(not leader_sent_bid))
+
+
 def prepareDCDDataDir(datadir, node_id, conf_file, dir_prefix, num_nodes=3):
     node_dir = os.path.join(datadir, dir_prefix + str(node_id))
     if not os.path.exists(node_dir):
@@ -298,8 +499,9 @@ class Test(BaseTest):
     test_coin = Coins.DCR
     dcr_daemons = []
     start_ltc_nodes = False
-    start_xmr_nodes = False
+    start_xmr_nodes = True
     dcr_mining_addr = 'SsYbXyjkKAEXXcGdFgr4u4bo4L8RkCxwQpH'
+    extra_wait_time = 0
 
     hex_seeds = [
         'e8574b2a94404ee62d8acc0258cab4c0defcfab8a5dfc2f4954c1f9d7e09d72a',
@@ -331,15 +533,19 @@ class Test(BaseTest):
         ci0 = cls.swap_clients[0].ci(cls.test_coin)
 
         num_passed: int = 0
-        for i in range(5):
+        for i in range(30):
             try:
                 ci0.rpc_wallet('purchaseticket', [cls.dcr_ticket_account, 0.1, 0])
                 num_passed += 1
+                if num_passed >= 5:
+                    break
+                test_delay_event.wait(0.1)
             except Exception as e:
                 if 'double spend' in str(e):
                     pass
                 else:
                     logging.warning('coins_loop purchaseticket {}'.format(e))
+                test_delay_event.wait(0.5)
 
         try:
             if num_passed >= 5:
@@ -436,6 +642,8 @@ class Test(BaseTest):
             'address': js_w[coin_ticker][address_type],
             'subfee': False,
         }
+        if coin in (Coins.XMR, ):
+            post_json['sweepall'] = False
         json_rv = read_json_api(port_take_from_node, 'wallets/{}/withdraw'.format(coin_ticker.lower()), post_json)
         assert (len(json_rv['txid']) == 64)
         wait_for_amount: float = amount
@@ -763,22 +971,49 @@ class Test(BaseTest):
         assert (amount_proved >= require_amount)
 
     def test_02_part_coin(self):
-        test_success_path(self, Coins.PART, self.test_coin)
+        run_test_success_path(self, Coins.PART, self.test_coin)
 
     def test_03_coin_part(self):
-        test_success_path(self, self.test_coin, Coins.PART)
+        run_test_success_path(self, self.test_coin, Coins.PART)
 
     def test_04_part_coin_bad_ptx(self):
-        test_bad_ptx(self, Coins.PART, self.test_coin)
+        run_test_bad_ptx(self, Coins.PART, self.test_coin)
 
     def test_05_coin_part_bad_ptx(self):
-        test_bad_ptx(self, self.test_coin, Coins.PART)
+        run_test_bad_ptx(self, self.test_coin, Coins.PART)
 
     def test_06_part_coin_itx_refund(self):
-        test_itx_refund(self, Coins.PART, self.test_coin)
+        run_test_itx_refund(self, Coins.PART, self.test_coin)
 
     def test_07_coin_part_itx_refund(self):
-        test_itx_refund(self, self.test_coin, Coins.PART)
+        run_test_itx_refund(self, self.test_coin, Coins.PART)
+
+    def test_08_ads_coin_xmr(self):
+        run_test_ads_success_path(self, self.test_coin, Coins.XMR)
+
+    def test_09_ads_xmr_coin(self):
+        # Reverse bid
+        run_test_ads_success_path(self, Coins.XMR, self.test_coin)
+
+    def test_10_ads_part_coin(self):
+        run_test_ads_success_path(self, Coins.PART, self.test_coin)
+
+    def test_11_ads_coin_xmr_both_refund(self):
+        run_test_ads_both_refund(self, self.test_coin, Coins.XMR, lock_value=20)
+
+    def test_12_ads_xmr_coin_both_refund(self):
+        # Reverse bid
+        run_test_ads_both_refund(self, Coins.XMR, self.test_coin, lock_value=20)
+
+    def test_13_ads_part_coin_both_refund(self):
+        run_test_ads_both_refund(self, Coins.PART, self.test_coin, lock_value=20)
+
+    def test_14_ads_coin_xmr_swipe_refund(self):
+        run_test_ads_swipe_refund(self, self.test_coin, Coins.XMR, lock_value=20)
+
+    def test_15_ads_xmr_coin_swipe_refund(self):
+        # Reverse bid
+        run_test_ads_swipe_refund(self, Coins.XMR, self.test_coin, lock_value=20)
 
 
 if __name__ == '__main__':
diff --git a/tests/basicswap/test_btc_xmr.py b/tests/basicswap/test_btc_xmr.py
index ed13666..7543df0 100644
--- a/tests/basicswap/test_btc_xmr.py
+++ b/tests/basicswap/test_btc_xmr.py
@@ -26,7 +26,7 @@ from basicswap.util import (
     make_int,
     format_amount,
 )
-from basicswap.interface import Curves
+from basicswap.interface.base import Curves
 from tests.basicswap.util import (
     read_json_api,
 )
diff --git a/tests/basicswap/test_run.py b/tests/basicswap/test_run.py
index 629aec3..9af3624 100644
--- a/tests/basicswap/test_run.py
+++ b/tests/basicswap/test_run.py
@@ -337,7 +337,7 @@ class Test(BaseTest):
         wait_for_in_progress(test_delay_event, swap_clients[1], bid_id, sent=True)
 
         wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=80)
-        wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=80)
+        wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=30)
 
         # Verify lock tx spends are found in the expected wallets
         bid, offer = swap_clients[0].getBidAndOffer(bid_id)
@@ -381,7 +381,7 @@ class Test(BaseTest):
         wait_for_in_progress(test_delay_event, swap_clients[0], bid_id, sent=True)
 
         wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=80)
-        wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, wait_for=80)
+        wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, wait_for=30)
 
         js_0 = read_json_api(1800)
         js_1 = read_json_api(1801)
@@ -404,7 +404,7 @@ class Test(BaseTest):
         wait_for_in_progress(test_delay_event, swap_clients[1], bid_id, sent=True)
 
         wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=80)
-        wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=80)
+        wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=30)
 
         js_0 = read_json_api(1800)
         js_1 = read_json_api(1801)
@@ -428,7 +428,7 @@ class Test(BaseTest):
         swap_clients[0].acceptBid(bid_id)
 
         wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=80)
-        wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.BID_ABANDONED, sent=True, wait_for=80)
+        wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.BID_ABANDONED, sent=True, wait_for=30)
 
         js_0_bid = read_json_api(1800, 'bids/{}'.format(bid_id.hex()))
         js_1_bid = read_json_api(1801, 'bids/{}'.format(bid_id.hex()))
@@ -464,7 +464,7 @@ class Test(BaseTest):
         swap_clients[0].acceptBid(bid_id)
 
         wait_for_bid_tx_state(test_delay_event, swap_clients[0], bid_id, TxStates.TX_REDEEMED, TxStates.TX_REDEEMED, wait_for=80)
-        wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=80)
+        wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=30)
 
         js_0 = read_json_api(1800)
         assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0)
@@ -513,7 +513,7 @@ class Test(BaseTest):
         bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
 
         wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=80)
-        wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=80)
+        wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=30)
 
     def test_10_bad_ptx(self):
         # Invalid PTX sent, swap should stall and ITx and PTx should be reclaimed by senders
@@ -534,7 +534,7 @@ class Test(BaseTest):
         swap_clients[0].acceptBid(bid_id)
 
         wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=120)
-        wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=120)
+        wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=30)
 
         js_0_bid = read_json_api(1800, 'bids/{}'.format(bid_id.hex()))
         js_1_bid = read_json_api(1801, 'bids/{}'.format(bid_id.hex()))
@@ -574,7 +574,7 @@ class Test(BaseTest):
         swap_clients[0].acceptBid(bid_id)
 
         wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=120)
-        wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.BID_ABANDONED, sent=True, wait_for=120)
+        wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.BID_ABANDONED, sent=True, wait_for=30)
 
         js_0_bid = read_json_api(1800, 'bids/{}'.format(bid_id.hex()))
         js_1_bid = read_json_api(1801, 'bids/{}'.format(bid_id.hex()))
@@ -727,7 +727,7 @@ class Test(BaseTest):
         swap_clients[2].acceptBid(bid_id)
 
         wait_for_bid(test_delay_event, swap_clients[2], bid_id, BidStates.SWAP_COMPLETED, wait_for=80)
-        wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=80)
+        wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=30)
 
         # Verify expected inputs were used
         bid, offer = swap_clients[2].getBidAndOffer(bid_id)
@@ -762,7 +762,7 @@ class Test(BaseTest):
         wait_for_in_progress(test_delay_event, swap_clients[0], bid_id, sent=True)
 
         wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=80)
-        wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, wait_for=80)
+        wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, wait_for=30)
 
     def pass_99_delay(self):
         logging.info('Delay')
diff --git a/tests/basicswap/test_xmr.py b/tests/basicswap/test_xmr.py
index b8d67c6..599cbef 100644
--- a/tests/basicswap/test_xmr.py
+++ b/tests/basicswap/test_xmr.py
@@ -1528,7 +1528,7 @@ class Test(BaseTest):
         swap_clients[0].acceptXmrBid(bid_id)
 
         wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, wait_for=1800)
-        wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, wait_for=1800, sent=True)
+        wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, wait_for=30, sent=True)
 
     def test_16_new_subaddress(self):
         logging.info('---------- Test that new subaddresses are created')