diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index f80cf47..0497003 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -1910,7 +1910,10 @@ class BasicSwap(BaseApp): def withdrawCoin(self, coin_type, value, addr_to, subfee: bool) -> str: ci = self.ci(coin_type) - self.log.info('withdrawCoin {} {} to {} {}'.format(value, ci.ticker(), addr_to, ' subfee' if subfee else '')) + if subfee and coin_type == Coins.XMR: + self.log.info('withdrawCoin sweep all {} to {}'.format(ci.ticker(), addr_to)) + else: + self.log.info('withdrawCoin {} {} to {} {}'.format(value, ci.ticker(), addr_to, ' subfee' if subfee else '')) txid = ci.withdrawCoin(value, addr_to, subfee) self.log.debug('In txn: {}'.format(txid)) diff --git a/basicswap/interface/xmr.py b/basicswap/interface/xmr.py index 265da44..2bd255b 100644 --- a/basicswap/interface/xmr.py +++ b/basicswap/interface/xmr.py @@ -473,25 +473,24 @@ class XMRInterface(CoinInterface): return bytes.fromhex(rv['tx_hash_list'][0]) - def withdrawCoin(self, value: int, addr_to: str, subfee: bool) -> str: + def withdrawCoin(self, value: int, addr_to: str, sweepall: bool) -> str: with self._mx_wallet: - value_sats = make_int(value, self.exp()) - self.openWallet(self._wallet_filename) self.rpc_wallet('refresh') - if subfee: + if sweepall: balance = self.rpc_wallet('get_balance') - diff = balance['unlocked_balance'] - value_sats - if diff >= 0 and diff <= 10: - self._log.info('subfee enabled and value close to total, using sweep_all.') - params = {'address': addr_to} - if self._fee_priority > 0: - params['priority'] = self._fee_priority - rv = self.rpc_wallet('sweep_all', params) - return rv['tx_hash_list'][0] - raise ValueError('Withdraw value must be close to total to use subfee/sweep_all.') + if balance['balance'] != balance['unlocked_balance']: + raise ValueError('Balance must be fully confirmed to use sweep all.') + self._log.info('XMR withdraw sweep_all.') + self._log.debug('XMR balance: {}'.format(balance['balance'])) + params = {'address': addr_to} + if self._fee_priority > 0: + params['priority'] = self._fee_priority + rv = self.rpc_wallet('sweep_all', params) + return rv['tx_hash_list'][0] + value_sats: int = make_int(value, self.exp()) params = {'destinations': [{'amount': value_sats, 'address': addr_to}]} if self._fee_priority > 0: params['priority'] = self._fee_priority diff --git a/basicswap/js_server.py b/basicswap/js_server.py index 6ae75eb..d137fe3 100644 --- a/basicswap/js_server.py +++ b/basicswap/js_server.py @@ -52,12 +52,20 @@ def getFormData(post_string: str, is_json: bool): def withdraw_coin(swap_client, coin_type, post_string, is_json): post_data = getFormData(post_string, is_json) - - value = get_data_entry(post_data, 'value') address = get_data_entry(post_data, 'address') - subfee = get_data_entry(post_data, 'subfee') - if not isinstance(subfee, bool): - subfee = toBool(subfee) + + if coin_type == Coins.XMR: + value = None + sweepall = get_data_entry(post_data, 'sweepall') + if not isinstance(sweepall, bool): + sweepall = toBool(sweepall) + if not sweepall: + value = get_data_entry(post_data, 'value') + else: + value = get_data_entry(post_data, 'value') + subfee = get_data_entry(post_data, 'subfee') + if not isinstance(subfee, bool): + subfee = toBool(subfee) if coin_type == Coins.PART: type_from = get_data_entry_or(post_data, 'type_from', 'plain') @@ -66,6 +74,8 @@ def withdraw_coin(swap_client, coin_type, post_string, is_json): elif coin_type == Coins.LTC: type_from = get_data_entry_or(post_data, 'type_from', 'plain') txid_hex = swap_client.withdrawLTC(type_from, value, address, subfee) + elif coin_type == Coins.XMR: + txid_hex = swap_client.withdrawCoin(coin_type, value, address, sweepall) else: txid_hex = swap_client.withdrawCoin(coin_type, value, address, subfee) diff --git a/basicswap/templates/wallet.html b/basicswap/templates/wallet.html index d807f14..5fa2c7f 100644 --- a/basicswap/templates/wallet.html +++ b/basicswap/templates/wallet.html @@ -326,14 +326,21 @@ - + + {% if w.cid == '6' %} {# XMR #} + Sweep All: + + + + {% else %} Subtract Fee: + {% endif %} {% if w.cid == '1' %} {# PART #} @@ -623,6 +630,24 @@ document.addEventListener('DOMContentLoaded', () => { } calculateTotalUsdValue(); + + function set_sweep_all(element) { + let input = document.getElementById('amount'); + if (element.checked) { + input.disabled = true; + input.style.display = 'none'; + } else { + input.disabled = false; + input.style.display = 'block'; + } + } + let cb_sweepall = document.getElementById('sweepall'); + if (cb_sweepall) { + set_sweep_all(cb_sweepall); + cb_sweepall.addEventListener('change', (event) => { + set_sweep_all(event.currentTarget); + }) + } }); diff --git a/basicswap/ui/page_wallet.py b/basicswap/ui/page_wallet.py index 861f1c4..17ed173 100644 --- a/basicswap/ui/page_wallet.py +++ b/basicswap/ui/page_wallet.py @@ -150,20 +150,24 @@ def page_wallet(self, url_split, post_string): err_messages.append('Reseed failed ' + str(ex)) swap_client.updateWalletsInfo(True, coin_id) elif bytes('withdraw_' + cid, 'utf-8') in form_data: - try: - value = form_data[bytes('amt_' + cid, 'utf-8')][0].decode('utf-8') - page_data['wd_value_' + cid] = value - except Exception as e: - err_messages.append('Missing value') + subfee = True if bytes('subfee_' + cid, 'utf-8') in form_data else False + page_data['wd_subfee_' + cid] = subfee + + sweepall = True if bytes('sweepall_' + cid, 'utf-8') in form_data else False + page_data['wd_sweepall_' + cid] = sweepall + value = None + if not sweepall: + try: + value = form_data[bytes('amt_' + cid, 'utf-8')][0].decode('utf-8') + page_data['wd_value_' + cid] = value + except Exception as e: + err_messages.append('Missing value') try: address = form_data[bytes('to_' + cid, 'utf-8')][0].decode('utf-8') page_data['wd_address_' + cid] = address except Exception as e: err_messages.append('Missing address') - subfee = True if bytes('subfee_' + cid, 'utf-8') in form_data else False - page_data['wd_subfee_' + cid] = subfee - if coin_id == Coins.PART: try: type_from = form_data[bytes('withdraw_type_from_' + cid, 'utf-8')][0].decode('utf-8') @@ -179,7 +183,7 @@ def page_wallet(self, url_split, post_string): except Exception as e: err_messages.append('Missing type') - if len(messages) == 0: + if len(err_messages) == 0: ci = swap_client.ci(coin_id) ticker = ci.ticker() try: @@ -189,6 +193,12 @@ def page_wallet(self, url_split, post_string): elif coin_id == Coins.LTC: txid = swap_client.withdrawLTC(type_from, value, address, subfee) messages.append('Withdrew {} {} (from {}) to address {}
In txid: {}'.format(value, ticker, type_from, address, txid)) + elif coin_id == Coins.XMR: + txid = swap_client.withdrawCoin(coin_id, value, address, sweepall) + if sweepall: + messages.append('Swept all {} to address {}
In txid: {}'.format(ticker, address, txid)) + else: + messages.append('Withdrew {} {} to address {}
In txid: {}'.format(value, ticker, address, txid)) else: txid = swap_client.withdrawCoin(coin_id, value, address, subfee) messages.append('Withdrew {} {} to address {}
In txid: {}'.format(value, ticker, address, txid)) @@ -261,6 +271,8 @@ def page_wallet(self, url_split, post_string): 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 'wd_sweepall_' + cid in page_data: + wallet_data['wd_sweepall'] = page_data['wd_sweepall_' + cid] if 'utxo_value' in page_data: wallet_data['utxo_value'] = page_data['utxo_value'] diff --git a/tests/basicswap/test_xmr.py b/tests/basicswap/test_xmr.py index 2417847..1518657 100644 --- a/tests/basicswap/test_xmr.py +++ b/tests/basicswap/test_xmr.py @@ -1151,21 +1151,10 @@ class Test(BaseTest): js_1 = read_json_api(1801, 'wallets') assert (float(js_1[Coins.XMR.name]['balance']) > 0.0) - post_json = { - 'value': 0.001, - 'address': address_to, - 'subfee': True, - } - rv = read_json_api(1801, 'wallets/xmr/withdraw', post_json) - assert ('Withdraw value must be close to total to use subfee' in rv['error']) - post_json['value'] = 1000000000.0 - rv = read_json_api(1801, 'wallets/xmr/withdraw', post_json) - assert ('Withdraw value must be close to total to use subfee' in rv['error']) - post_json = { 'value': 1.1, 'address': address_to, - 'subfee': False, + 'sweepall': False, } rv = read_json_api(1801, 'wallets/xmr/withdraw', post_json) assert (len(rv['txid']) == 64) @@ -1552,28 +1541,44 @@ class Test(BaseTest): def test_97_withdraw_all(self): logging.info('---------- Test XMR withdrawal all') + + wait_for_balance(test_delay_event, 'http://127.0.0.1:1800/json/wallets/xmr', 'unconfirmed', 0.0) + wallets0 = read_json_api(TEST_HTTP_PORT + 0, 'wallets') + xmr_total = float(wallets0[Coins.XMR.name]['balance']) + + if xmr_total < 10.0: + address_to = read_json_api(1800, 'wallets')[Coins.XMR.name]['deposit_address'] + post_json = { + 'value': 10.0, + 'address': address_to, + 'sweepall': False, + } + json_rv = read_json_api(TEST_HTTP_PORT + 1, 'wallets/xmr/withdraw', post_json) + wait_for_balance(test_delay_event, 'http://127.0.0.1:1800/json/wallets/xmr', 'balance', 10.0) + + post_json = { + 'address': read_json_api(1801, 'wallets')[Coins.XMR.name]['deposit_address'], + 'sweepall': True, + } + json_rv = json.loads(post_json_req('http://127.0.0.1:{}/json/wallets/xmr/withdraw'.format(TEST_HTTP_PORT + 0), post_json)) + assert (len(json_rv['txid']) == 64) + try: logging.info('Disabling XMR mining') pause_event.clear() - js_0 = read_json_api(1800, 'wallets') - address_to = js_0[Coins.XMR.name]['deposit_address'] + address_to = read_json_api(1800, 'wallets')[Coins.XMR.name]['deposit_address'] wallets1 = read_json_api(TEST_HTTP_PORT + 1, 'wallets') xmr_total = float(wallets1[Coins.XMR.name]['balance']) assert (xmr_total > 10) post_json = { - 'value': 10, 'address': address_to, - 'subfee': True, + 'sweepall': True, } json_rv = json.loads(post_json_req('http://127.0.0.1:{}/json/wallets/xmr/withdraw'.format(TEST_HTTP_PORT + 1), post_json)) - assert (json_rv['error'] == 'Withdraw value must be close to total to use subfee/sweep_all.') - - post_json['value'] = xmr_total - json_rv = json.loads(post_json_req('http://127.0.0.1:{}/json/wallets/xmr/withdraw'.format(TEST_HTTP_PORT + 1), post_json)) - assert (len(json_rv['txid']) == 64) + assert ('Balance must be fully confirmed to use sweep all' in json_rv['error']) finally: logging.info('Restoring XMR mining') pause_event.set()