diff --git a/basicswap/interface/ltc.py b/basicswap/interface/ltc.py index 6450198..63b6217 100644 --- a/basicswap/interface/ltc.py +++ b/basicswap/interface/ltc.py @@ -92,7 +92,9 @@ class LTCInterface(BTCInterface): if "address" not in u: continue utxo_address: str = u["address"] - if any(utxo_address.startswith(prefix) for prefix in ("mweb1", "tmweb1")): + if any( + utxo_address.startswith(prefix) for prefix in ("ltcmweb1", "tmweb1") + ): continue if "desc" in u: desc = u["desc"] @@ -111,6 +113,76 @@ class LTCInterface(BTCInterface): ) + self.make_int(u["amount"], r=1) return unspent_addr + def getMWEBBalance(self) -> int: + if self.useBackend(): + raise ValueError("MWEB not supported in electrum mode") + + value: int = 0 + unspent = self.rpc_wallet( + "listunspent", + [ + 0, + ], + ) + for u in unspent: + if "address" not in u: + continue + utxo_address: str = u["address"] + if any( + utxo_address.startswith(prefix) for prefix in ("ltcmweb1", "tmweb1") + ): + value += self.make_int(u["amount"], r=1) + return value + + def convertMWEBBalance(self): + if self.useBackend(): + raise ValueError("MWEB not supported in electrum mode") + + self._log.info(f"convertMWEBBalance - {self.ticker()}") + locked_before = self.rpc_wallet("listlockunspent") + lock_utxos = [] + try: + # Hack: mark all the other utxos as unspendable, alternative is to use a mweb_transfer wallet + utxos = self.rpc_wallet("listunspent") + mweb_amount: int = 0 + for utxo in utxos: + utxo_address: str = utxo.get("address", "") + if any( + utxo_address.startswith(prefix) for prefix in ("ltcmweb1", "tmweb1") + ): + mweb_amount += self.make_int(utxo["amount"], r=1) + continue + utxo_op = {"txid": utxo["txid"], "vout": utxo["vout"]} + if utxo_op in locked_before: + continue + lock_utxos.append(utxo_op) + + if mweb_amount == 0: + raise ValueError("No MWEB outputs to convert") + self.rpc_wallet("lockunspent", [False, lock_utxos]) + subfee_to_mweb: bool = True + convert_value = self.format_amount(mweb_amount) + plain_addr: str = self.rpc_wallet("getnewaddress", ["transfer", "bech32"]) + + # Double check generated address is owned by this wallet + if not self.isAddressMine(plain_addr): + raise ValueError("Generated address not owned by wallet!") + params = [ + plain_addr, + convert_value, + "", + "", + subfee_to_mweb, + True, + self._conf_target, + ] + txid = self.rpc_wallet("sendtoaddress", params) + + self._log.info(f"MWEB in plain converted in txid: {self._log.id(txid)}") + return txid + finally: + self.rpc_wallet("lockunspent", [True, lock_utxos]) + def unlockWallet(self, password: str, check_seed: bool = True) -> None: if password == "": return diff --git a/basicswap/js_server.py b/basicswap/js_server.py index fa83cae..af60043 100644 --- a/basicswap/js_server.py +++ b/basicswap/js_server.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020-2024 tecnovert -# Copyright (c) 2024-2025 The Basicswap developers +# Copyright (c) 2024-2026 The Basicswap developers # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -129,7 +129,6 @@ def js_walletbalances(self, url_split, post_string, is_json) -> bytes: swap_client = self.server.swap_client try: - swap_client.updateWalletsInfo() wallets = swap_client.getCachedWalletsInfo() coins_with_balances = [] @@ -332,6 +331,18 @@ def js_wallets(self, url_split, post_string, is_json): return bytes( json.dumps(swap_client.ci(coin_type).getNewMwebAddress()), "UTF-8" ) + elif cmd == "mwebbalance": + # mweb outputs left behind when sending LTC -> MWEB + if coin_type not in (Coins.LTC,): + raise ValueError("Invalid coin for command") + ci = swap_client.ci(coin_type) + return bytes(json.dumps(ci.format_amount(ci.getMWEBBalance())), "UTF-8") + elif cmd == "convertmweb": + if coin_type not in (Coins.LTC,): + raise ValueError("Invalid coin for command") + return bytes( + json.dumps(swap_client.ci(coin_type).convertMWEBBalance()), "UTF-8" + ) elif cmd == "watchaddress": post_data = getFormData(post_string, is_json) address = get_data_entry(post_data, "address") diff --git a/basicswap/static/js/modules/event-handlers.js b/basicswap/static/js/modules/event-handlers.js index 1f96c8e..35557e8 100644 --- a/basicswap/static/js/modules/event-handlers.js +++ b/basicswap/static/js/modules/event-handlers.js @@ -40,6 +40,10 @@ }); }, + confirmMWEBChangeConvert: function() { + return confirm('Confirm MWEB change conversion: This will create a tx sending all spendable MWEB outputs in the plain LTC wallet to LTC.'); + }, + confirmReseed: function() { return confirm('Are you sure you want to reseed the wallet? This will generate new addresses.'); }, @@ -60,7 +64,7 @@ }, fillDonationAddress: function(address, coinType) { - + let addressInput = null; addressInput = window.DOMCache @@ -188,7 +192,7 @@ }, lookup_rates: function() { - + if (window.lookup_rates && typeof window.lookup_rates === 'function') { window.lookup_rates(); } else { @@ -282,6 +286,16 @@ } }); + document.addEventListener('click', (e) => { + const target = e.target.closest('[data-confirm-mweb-change-convert]'); + if (target) { + if (!this.confirmMWEBChangeConvert()) { + e.preventDefault(); + return false; + } + } + }); + document.addEventListener('click', (e) => { const target = e.target.closest('[data-confirm-utxo]'); if (target) { @@ -398,6 +412,7 @@ window.EventHandlers = EventHandlers; window.confirmReseed = EventHandlers.confirmReseed.bind(EventHandlers); + window.confirmMWEBChangeConvert = EventHandlers.confirmMWEBChangeConvert.bind(EventHandlers); window.confirmWithdrawal = EventHandlers.confirmWithdrawal.bind(EventHandlers); window.confirmUTXOResize = EventHandlers.confirmUTXOResize.bind(EventHandlers); window.confirmRemoveExpired = EventHandlers.confirmRemoveExpired.bind(EventHandlers); diff --git a/basicswap/templates/wallet.html b/basicswap/templates/wallet.html index c21026f..5df2a53 100644 --- a/basicswap/templates/wallet.html +++ b/basicswap/templates/wallet.html @@ -185,6 +185,17 @@ {% endif %} + {% if w.mweb_in_plain %} +
MWEB in Plain Balance:
Spark Balance: