From 67518efcad1134dd57c5d933c4bd274920afd7cb Mon Sep 17 00:00:00 2001
From: tecnovert <tecnovert@tecnovert.net>
Date: Mon, 24 Jan 2022 23:32:48 +0200
Subject: [PATCH] ui: Add option to create sized utxo.

---
 basicswap/basicswap.py          | 19 ++++++++++++------
 basicswap/http_server.py        | 34 +++++++++++++++++++++++++++++++++
 basicswap/interface_btc.py      | 17 ++++++++++++++---
 basicswap/interface_part.py     | 10 +++++-----
 basicswap/templates/wallet.html | 22 ++++++++++++++++++++-
 5 files changed, 87 insertions(+), 15 deletions(-)

diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py
index 9b7856c..c697843 100644
--- a/basicswap/basicswap.py
+++ b/basicswap/basicswap.py
@@ -1522,6 +1522,18 @@ class BasicSwap(BaseApp):
             self.mxDB.release()
         return self._contract_count
 
+    def getUnspentsByAddr(self, coin_type):
+        ci = self.ci(coin_type)
+
+        unspent_addr = dict()
+        unspent = self.callcoinrpc(coin_type, 'listunspent')
+        for u in unspent:
+            if u['spendable'] is not True:
+                continue
+            unspent_addr[u['address']] = unspent_addr.get(u['address'], 0) + ci.make_int(u['amount'], r=1)
+
+        return unspent_addr
+
     def getProofOfFunds(self, coin_type, amount_for, extra_commit_bytes):
         ci = self.ci(coin_type)
         self.log.debug('getProofOfFunds %s %s', ci.coin_name(), ci.format_amount(amount_for))
@@ -1530,12 +1542,7 @@ class BasicSwap(BaseApp):
             return (None, None)
 
         # TODO: Lock unspent and use same output/s to fund bid
-        unspent_addr = dict()
-        unspent = self.callcoinrpc(coin_type, 'listunspent')
-        for u in unspent:
-            if u['spendable'] is not True:
-                continue
-            unspent_addr[u['address']] = unspent_addr.get(u['address'], 0) + ci.make_int(u['amount'], r=1)
+        unspent_addr = self.getUnspentsByAddr(coin_type)
 
         sign_for_addr = None
         for addr, value in unspent_addr.items():
diff --git a/basicswap/http_server.py b/basicswap/http_server.py
index 3098de5..fe0069f 100644
--- a/basicswap/http_server.py
+++ b/basicswap/http_server.py
@@ -421,6 +421,7 @@ class HttpHandler(BaseHTTPRequestHandler):
         page_data = {}
         messages = []
         form_data = self.checkForm(post_string, 'settings', messages)
+        show_utxo_groups = False
         if form_data:
             cid = str(int(coin_id))
 
@@ -473,6 +474,25 @@ class HttpHandler(BaseHTTPRequestHandler):
                         except Exception as e:
                             messages.append('Error: {}'.format(str(e)))
                     swap_client.updateWalletsInfo(True, coin_id)
+            elif have_data_entry(form_data, 'showutxogroups'):
+                show_utxo_groups = True
+            elif have_data_entry(form_data, 'create_utxo'):
+                show_utxo_groups = True
+                try:
+                    value = get_data_entry(form_data, 'utxo_value')
+                    page_data['utxo_value'] = value
+
+                    ci = swap_client.ci(coin_id)
+
+                    value_sats = ci.make_int(value)
+
+                    txid, address = ci.createUTXO(value_sats)
+                    messages.append('Created new utxo of value {} and address {}<br/>In txid: {}'.format(value, address, txid))
+                except Exception as e:
+                    messages.append('Error: {}'.format(str(e)))
+                    if swap_client.debug is True:
+                        swap_client.log.error(traceback.format_exc())
+
 
         swap_client.updateWalletsInfo()
         wallets = swap_client.getCachedWalletsInfo({'coin_id': coin_id})
@@ -541,6 +561,20 @@ class HttpHandler(BaseHTTPRequestHandler):
                 wallet_data['wd_address'] = page_data['wd_address_' + cid]
             if 'wd_subfee_' + cid in page_data:
                 wallet_data['wd_subfee'] = page_data['wd_subfee_' + cid]
+            if 'utxo_value' in page_data:
+                wallet_data['utxo_value'] = page_data['utxo_value']
+
+            if show_utxo_groups:
+                utxo_groups = ''
+
+                unspent_by_addr = swap_client.getUnspentsByAddr(k)
+
+                sorted_unspent_by_addr = sorted(unspent_by_addr.items(), key=lambda x:x[1], reverse=True)
+                for kv in sorted_unspent_by_addr:
+                    utxo_groups += kv[0] + ' ' + ci.format_amount(kv[1]) + '\n'
+
+                wallet_data['show_utxo_groups'] = True
+                wallet_data['utxo_groups'] = utxo_groups
 
         template = env.get_template('wallet.html')
         return bytes(template.render(
diff --git a/basicswap/interface_btc.py b/basicswap/interface_btc.py
index 8afcfb7..ac5919f 100644
--- a/basicswap/interface_btc.py
+++ b/basicswap/interface_btc.py
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-# Copyright (c) 2020-2021 tecnovert
+# Copyright (c) 2020-2022 tecnovert
 # Distributed under the MIT software license, see the accompanying
 # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
 
@@ -176,6 +176,7 @@ class BTCInterface(CoinInterface):
         self.rpc_callback = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host)
         self.blocks_confirmed = coin_settings['blocks_confirmed']
         self.setConfTarget(coin_settings['conf_target'])
+        self._use_segwit = coin_settings['use_segwit']
         self._sc = swap_client
         self._log = self._sc.log if self._sc and self._sc.log else logging
 
@@ -265,8 +266,8 @@ class BTCInterface(CoinInterface):
     def getWalletSeedID(self):
         return self.rpc_callback('getwalletinfo')['hdseedid']
 
-    def getNewAddress(self, use_segwit):
-        args = ['swap_receive']
+    def getNewAddress(self, use_segwit, label='swap_receive'):
+        args = [label]
         if use_segwit:
             args.append('bech32')
         return self.rpc_callback('getnewaddress', args)
@@ -1128,6 +1129,16 @@ class BTCInterface(CoinInterface):
     def getSpendableBalance(self):
         return self.make_int(self.rpc_callback('getbalances')['mine']['trusted'])
 
+    def createUTXO(self, value_sats):
+        # Create a new address and send value_sats to it
+
+        spendable_balance = self.getSpendableBalance()
+        if spendable_balance < value_sats:
+            raise ValueError('Balance too low')
+
+        address = self.getNewAddress(self._use_segwit, 'create_utxo')
+        return self.withdrawCoin(self.format_amount(value_sats), address, False), address
+
 
 def testBTCInterface():
     print('testBTCInterface')
diff --git a/basicswap/interface_part.py b/basicswap/interface_part.py
index 321d2c4..4885f32 100644
--- a/basicswap/interface_part.py
+++ b/basicswap/interface_part.py
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-# Copyright (c) 2020-2021 tecnovert
+# Copyright (c) 2020-2022 tecnovert
 # Distributed under the MIT software license, see the accompanying
 # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
 
@@ -70,11 +70,11 @@ class PARTInterface(BTCInterface):
         # TODO: Double check
         return True
 
-    def getNewAddress(self, use_segwit):
-        return self.rpc_callback('getnewaddress', ['swap_receive'])
+    def getNewAddress(self, use_segwit, label='swap_receive'):
+        return self.rpc_callback('getnewaddress', [label])
 
-    def getNewStealthAddress(self):
-        return self.rpc_callback('getnewstealthaddress', ['swap_stealth'])
+    def getNewStealthAddress(self, label='swap_stealth'):
+        return self.rpc_callback('getnewstealthaddress', [label])
 
     def haveSpentIndex(self):
         version = self.getDaemonVersion()
diff --git a/basicswap/templates/wallet.html b/basicswap/templates/wallet.html
index f5f79b0..8e875b8 100644
--- a/basicswap/templates/wallet.html
+++ b/basicswap/templates/wallet.html
@@ -15,7 +15,6 @@
 
 <form method="post">
 
-
 {% if w.updating %}
 <h5>Updating</h5>
 {% endif %}
@@ -62,8 +61,26 @@
 </select></td></tr>
 {% endif %}
 <tr><td>Fee Rate:</td><td>{{ w.fee_rate }}</td><td>Est Fee:</td><td>{{ w.est_fee }}</td></tr>
+
+{% if w.cid != '6' %}
+{% if w.show_utxo_groups %}
+<tr><td colspan=3>
+<textarea class="monospace" id="tx_view" rows="10" cols="150" readonly>
+{{ w.utxo_groups }}
+</textarea>
+</td></tr>
+<tr><td><input type="submit" id="create_utxo" name="create_utxo" value="Create UTXO" onclick="return confirmUTXOResize();"></td><td>Amount: <input type="text" name="utxo_value" value="{{ w.utxo_value }}"></td></tr>
+<tr><td>
+  <input type="submit" id="closeutxogroups" name="closeutxogroups" value="Close UTXO Groups">
+</td></tr>
+{% else %}
+<tr><td>
+  <input type="submit" id="showutxogroups" name="showutxogroups" value="Show UTXO Groups">
+</td></tr>
+{% endif %}
 </table>
 {% endif %}
+{% endif %}
 {% endif %} <!-- havedata -->
 
 <input type="hidden" name="formid" value="{{ form_id }}">
@@ -78,5 +95,8 @@ function confirmReseed() {
 function confirmWithdrawal() {
     return confirm("Are you sure?");
 }
+function confirmUTXOResize() {
+    return confirm("Are you sure?");
+}
 </script>
 </body></html>