diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index 8685173..d68cc4c 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -598,6 +598,13 @@ class BasicSwap(BaseApp): "chain_median_time": None, } + # Passthrough settings + for setting_name in ("wallet_name", "mweb_wallet_name"): + if setting_name in chain_client_settings: + self.coin_clients[coin][setting_name] = chain_client_settings[ + setting_name + ] + if coin in (Coins.FIRO, Coins.LTC): if not chain_client_settings.get("min_relay_fee"): chain_client_settings["min_relay_fee"] = 0.00001 @@ -842,17 +849,11 @@ class BasicSwap(BaseApp): elif coin == Coins.XMR: from .interface.xmr import XMRInterface - xmr_i = XMRInterface(self.coin_clients[coin], self.chain, self) - chain_client_settings = self.getChainClientSettings(coin) - xmr_i.setWalletFilename(chain_client_settings["walletfile"]) - return xmr_i + return XMRInterface(self.coin_clients[coin], self.chain, self) elif coin == Coins.WOW: from .interface.wow import WOWInterface - wow_i = WOWInterface(self.coin_clients[coin], self.chain, self) - chain_client_settings = self.getChainClientSettings(coin) - wow_i.setWalletFilename(chain_client_settings["walletfile"]) - return wow_i + return WOWInterface(self.coin_clients[coin], self.chain, self) elif coin == Coins.PIVX: from .interface.pivx import PIVXInterface diff --git a/basicswap/bin/prepare.py b/basicswap/bin/prepare.py index 5afef2a..c1e85f8 100755 --- a/basicswap/bin/prepare.py +++ b/basicswap/bin/prepare.py @@ -33,7 +33,7 @@ import basicswap.config as cfg from basicswap import __version__ from basicswap.base import getaddrinfo_tor from basicswap.basicswap import BasicSwap -from basicswap.chainparams import Coins +from basicswap.chainparams import Coins, chainparams, getCoinIdFromName from basicswap.contrib.rpcauth import generate_salt, password_to_hmac from basicswap.ui.util import getCoinName from basicswap.util import toBool @@ -363,6 +363,18 @@ def shouldManageDaemon(prefix: str) -> bool: return toBool(manage_daemon) +def getWalletName(coin_params: str, default_name: str, prefix_override=None) -> str: + prefix: str = coin_params["ticker"] if prefix_override is None else prefix_override + env_var_name: str = prefix + "_WALLET_NAME" + + if env_var_name in os.environ and coin_params.get("has_multiwallet", True) is False: + raise ValueError("Can't set wallet name for {}.".format(coin_params["ticker"])) + + wallet_name: str = os.getenv(env_var_name, default_name) + assert len(wallet_name) > 0 + return wallet_name + + def getKnownVersion(coin_name: str) -> str: version, version_tag, _ = known_coins[coin_name] return version + version_tag @@ -1121,6 +1133,8 @@ def writeTorSettings(fp, coin, coin_settings, tor_control_password): def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}): core_settings = settings["chainclients"][coin] + wallet_name = core_settings.get("wallet_name", "wallet.dat") + assert len(wallet_name) > 0 data_dir = core_settings["datadir"] tor_control_password = extra_opts.get("tor_control_password", None) @@ -1301,7 +1315,7 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}): fp.write("rpcport={}\n".format(core_settings["rpcport"])) fp.write("printtoconsole=0\n") fp.write("daemon=0\n") - fp.write("wallet=wallet.dat\n") + fp.write(f"wallet={wallet_name}\n") if tor_control_password is not None: writeTorSettings(fp, coin, core_settings, tor_control_password) @@ -1771,6 +1785,7 @@ def initialise_wallets( ] + [c for c in with_coins if c != "particl"] for coin_name in start_daemons: coin_settings = settings["chainclients"][coin_name] + wallet_name = coin_settings.get("wallet_name", "wallet.dat") c = swap_client.getCoinIdFromName(coin_name) if c == Coins.XMR: @@ -1862,9 +1877,9 @@ def initialise_wallets( swap_client.waitForDaemonRPC(c, with_wallet=False) # Create wallet if it doesn't exist yet wallets = swap_client.callcoinrpc(c, "listwallets") - if len(wallets) < 1: + if wallet_name not in wallets: logger.info( - "Creating wallet.dat for {}.".format(getCoinName(c)) + f'Creating wallet "{wallet_name}" for {getCoinName(c)}.' ) if c in (Coins.BTC, Coins.LTC, Coins.DOGE, Coins.DASH): @@ -1873,7 +1888,7 @@ def initialise_wallets( c, "createwallet", [ - "wallet.dat", + wallet_name, False, True, WALLET_ENCRYPTION_PWD, @@ -1883,7 +1898,13 @@ def initialise_wallets( ) swap_client.ci(c).unlockWallet(WALLET_ENCRYPTION_PWD) else: - swap_client.callcoinrpc(c, "createwallet", ["wallet.dat"]) + swap_client.callcoinrpc( + c, + "createwallet", + [ + wallet_name, + ], + ) if WALLET_ENCRYPTION_PWD != "": encrypt_wallet(swap_client, c) @@ -2400,7 +2421,6 @@ def main(): "walletrpchost": XMR_WALLET_RPC_HOST, "walletrpcuser": XMR_WALLET_RPC_USER, "walletrpcpassword": XMR_WALLET_RPC_PWD, - "walletfile": "swap_wallet", "datadir": os.getenv("XMR_DATA_DIR", os.path.join(data_dir, "monero")), "bindir": os.path.join(bin_dir, "monero"), "restore_height": xmr_restore_height, @@ -2487,7 +2507,6 @@ def main(): "walletrpchost": WOW_WALLET_RPC_HOST, "walletrpcuser": WOW_WALLET_RPC_USER, "walletrpcpassword": WOW_WALLET_RPC_PWD, - "walletfile": "swap_wallet", "datadir": os.getenv("WOW_DATA_DIR", os.path.join(data_dir, "wownero")), "bindir": os.path.join(bin_dir, "wownero"), "restore_height": wow_restore_height, @@ -2500,6 +2519,25 @@ def main(): }, } + for coin_name, coin_settings in chainclients.items(): + coin_id = getCoinIdFromName(coin_name) + coin_params = chainparams[coin_id] + if coin_settings.get("core_type_group", "") == "xmr": + default_name = "swap_wallet" + else: + default_name = "wallet.dat" + + if coin_name == "litecoin": + set_name: str = getWalletName( + coin_params, "mweb", prefix_override="LTC_MWEB" + ) + if set_name != "mweb": + coin_settings["mweb_wallet_name"] = set_name + + set_name: str = getWalletName(coin_params, default_name) + if set_name != default_name: + coin_settings["wallet_name"] = set_name + if PART_RPC_USER != "": chainclients["particl"]["rpcuser"] = PART_RPC_USER chainclients["particl"]["rpcpassword"] = PART_RPC_PWD diff --git a/basicswap/chainparams.py b/basicswap/chainparams.py index 3f3eca4..07b2376 100644 --- a/basicswap/chainparams.py +++ b/basicswap/chainparams.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019-2024 tecnovert -# Copyright (c) 2024 The Basicswap developers +# Copyright (c) 2024-2025 The Basicswap developers # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -199,6 +199,7 @@ chainparams = { "message_magic": "Decred Signed Message:\n", "blocks_target": 60 * 5, "decimal_places": 8, + "has_multiwallet": False, "mainnet": { "rpcport": 9109, "pubkey_address": 0x073F, @@ -404,6 +405,7 @@ chainparams = { "has_cltv": False, "has_csv": False, "has_segwit": False, + "has_multiwallet": False, "mainnet": { "rpcport": 8888, "pubkey_address": 82, @@ -443,6 +445,7 @@ chainparams = { "decimal_places": 8, "has_csv": True, "has_segwit": True, + "has_multiwallet": False, "mainnet": { "rpcport": 44444, "pubkey_address": 53, @@ -519,10 +522,13 @@ chainparams = { }, }, } + +name_map = {} ticker_map = {} for c, params in chainparams.items(): + name_map[params["name"].lower()] = c ticker_map[params["ticker"].lower()] = c @@ -530,4 +536,11 @@ def getCoinIdFromTicker(ticker: str) -> str: try: return ticker_map[ticker.lower()] except Exception: - raise ValueError("Unknown coin") + raise ValueError(f"Unknown coin {ticker}") + + +def getCoinIdFromName(name: str) -> str: + try: + return name_map[name.lower()] + except Exception: + raise ValueError(f"Unknown coin {name}") diff --git a/basicswap/interface/btc.py b/basicswap/interface/btc.py index 0caa59b..3041f18 100644 --- a/basicswap/interface/btc.py +++ b/basicswap/interface/btc.py @@ -266,7 +266,7 @@ class BTCInterface(Secp256k1Interface): self._rpcport = coin_settings["rpcport"] self._rpcauth = coin_settings["rpcauth"] self.rpc = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host) - self._rpc_wallet = "wallet.dat" + self._rpc_wallet = coin_settings.get("wallet_name", "wallet.dat") self.rpc_wallet = make_rpc_func( self._rpcport, self._rpcauth, host=self._rpc_host, wallet=self._rpc_wallet ) @@ -301,16 +301,14 @@ class BTCInterface(Secp256k1Interface): # Wallet name is "" for some LTC and PART installs on older cores if self._rpc_wallet not in wallets and len(wallets) > 0: - self._log.debug("Changing {} wallet name.".format(self.ticker())) + self._log.debug(f"Changing {self.ticker()} wallet name.") for wallet_name in wallets: # Skip over other expected wallets if wallet_name in ("mweb",): continue self._rpc_wallet = wallet_name self._log.info( - "Switched {} wallet name to {}.".format( - self.ticker(), self._rpc_wallet - ) + f"Switched {self.ticker()} wallet name to {self._rpc_wallet}." ) self.rpc_wallet = make_rpc_func( self._rpcport, @@ -381,9 +379,9 @@ class BTCInterface(Secp256k1Interface): chain_synced = round(blockchaininfo["verificationprogress"], 3) if chain_synced < 1.0: - raise ValueError("{} chain isn't synced.".format(self.coin_name())) + raise ValueError(f"{self.coin_name()} chain isn't synced.") - self._log.debug("Finding block at time: {}".format(start_time)) + self._log.debug(f"Finding block at time: {start_time}") rpc_conn = self.open_rpc() try: @@ -397,7 +395,7 @@ class BTCInterface(Secp256k1Interface): block_hash = block_header["previousblockhash"] finally: self.close_rpc(rpc_conn) - raise ValueError("{} wallet restore height not found.".format(self.coin_name())) + raise ValueError(f"{self.coin_name()} wallet restore height not found.") def getWalletSeedID(self) -> str: wi = self.rpc_wallet("getwalletinfo") @@ -1806,16 +1804,20 @@ class BTCInterface(Secp256k1Interface): def unlockWallet(self, password: str): if password == "": return - self._log.info("unlockWallet - {}".format(self.ticker())) + self._log.info(f"unlockWallet - {self.ticker()}") if self.coin_type() == Coins.BTC: # Recreate wallet if none found # Required when encrypting an existing btc wallet, workaround is to delete the btc wallet and recreate wallets = self.rpc("listwallets") if len(wallets) < 1: - self._log.info("Creating wallet.dat for {}.".format(self.coin_name())) + self._log.info( + f'Creating wallet "{self._rpc_wallet}" for {self.coin_name()}.' + ) # wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors - self.rpc("createwallet", ["wallet.dat", False, True, "", False, False]) + self.rpc( + "createwallet", [self._rpc_wallet, False, True, "", False, False] + ) self.rpc_wallet("encryptwallet", [password]) # Max timeout value, ~3 years @@ -1823,7 +1825,7 @@ class BTCInterface(Secp256k1Interface): self._sc.checkWalletSeed(self.coin_type()) def lockWallet(self): - self._log.info("lockWallet - {}".format(self.ticker())) + self._log.info(f"lockWallet - {self.ticker()}") self.rpc_wallet("walletlock") def get_p2sh_script_pubkey(self, script: bytearray) -> bytearray: diff --git a/basicswap/interface/dcr/dcr.py b/basicswap/interface/dcr/dcr.py index 9bd6843..ac9ecf9 100644 --- a/basicswap/interface/dcr/dcr.py +++ b/basicswap/interface/dcr/dcr.py @@ -277,6 +277,9 @@ class DCRInterface(Secp256k1Interface): self._connection_type = coin_settings["connection_type"] self._altruistic = coin_settings.get("altruistic", True) + if "wallet_name" in coin_settings: + raise ValueError(f"Invalid setting for {self.coin_name()}: wallet_name") + def open_rpc(self): return openrpc(self._rpcport, self._rpcauth, host=self._rpc_host) diff --git a/basicswap/interface/firo.py b/basicswap/interface/firo.py index 033592c..2d9747d 100644 --- a/basicswap/interface/firo.py +++ b/basicswap/interface/firo.py @@ -45,6 +45,9 @@ class FIROInterface(BTCInterface): self._rpcport, self._rpcauth, host=self._rpc_host ) + if "wallet_name" in coin_settings: + raise ValueError(f"Invalid setting for {self.coin_name()}: wallet_name") + def getExchangeName(self, exchange_name: str) -> str: return "zcoin" diff --git a/basicswap/interface/ltc.py b/basicswap/interface/ltc.py index 3e1d554..b5e6eee 100644 --- a/basicswap/interface/ltc.py +++ b/basicswap/interface/ltc.py @@ -18,7 +18,7 @@ class LTCInterface(BTCInterface): def __init__(self, coin_settings, network, swap_client=None): super(LTCInterface, self).__init__(coin_settings, network, swap_client) - self._rpc_wallet_mweb = "mweb" + self._rpc_wallet_mweb = coin_settings.get("mweb_wallet_name", "mweb") self.rpc_wallet_mweb = make_rpc_func( self._rpcport, self._rpcauth, @@ -94,7 +94,7 @@ class LTCInterfaceMWEB(LTCInterface): def __init__(self, coin_settings, network, swap_client=None): super(LTCInterfaceMWEB, self).__init__(coin_settings, network, swap_client) - self._rpc_wallet = "mweb" + self._rpc_wallet = coin_settings.get("mweb_wallet_name", "mweb") self.rpc_wallet = make_rpc_func( self._rpcport, self._rpcauth, host=self._rpc_host, wallet=self._rpc_wallet ) @@ -128,7 +128,7 @@ class LTCInterfaceMWEB(LTCInterface): self._log.info("init_wallet - {}".format(self.ticker())) - self._log.info("Creating mweb wallet for {}.".format(self.coin_name())) + self._log.info(f"Creating wallet {self._rpc_wallet} for {self.coin_name()}.") # wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors, load_on_startup self.rpc("createwallet", ["mweb", False, True, password, False, False, True]) diff --git a/basicswap/interface/nav.py b/basicswap/interface/nav.py index ba8f190..1211e1f 100644 --- a/basicswap/interface/nav.py +++ b/basicswap/interface/nav.py @@ -81,6 +81,9 @@ class NAVInterface(BTCInterface): self._rpcport, self._rpcauth, host=self._rpc_host ) + if "wallet_name" in coin_settings: + raise ValueError(f"Invalid setting for {self.coin_name()}: wallet_name") + def use_p2shp2wsh(self) -> bool: # p2sh-p2wsh return True diff --git a/basicswap/interface/wow.py b/basicswap/interface/wow.py index 2156e23..d615b01 100644 --- a/basicswap/interface/wow.py +++ b/basicswap/interface/wow.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +# Copyright (c) 2024 The Basicswap developers # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. diff --git a/basicswap/interface/xmr.py b/basicswap/interface/xmr.py index 6fcd9b6..4a206ca 100644 --- a/basicswap/interface/xmr.py +++ b/basicswap/interface/xmr.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020-2024 tecnovert -# Copyright (c) 2024 The Basicswap developers +# Copyright (c) 2024-2025 The Basicswap developers # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -99,6 +99,7 @@ class XMRInterface(CoinInterface): self._log = self._sc.log if self._sc and self._sc.log else logging self._wallet_password = None self._have_checked_seed = False + self._wallet_filename = coin_settings.get("wallet_name", "swap_wallet") daemon_login = None if coin_settings.get("rpcuser", "") != "": @@ -175,9 +176,6 @@ class XMRInterface(CoinInterface): ensure(new_priority >= 0 and new_priority < 4, "Invalid fee_priority value") self._fee_priority = new_priority - def setWalletFilename(self, wallet_filename): - self._wallet_filename = wallet_filename - def createWallet(self, params): if self._wallet_password is not None: params["password"] = self._wallet_password diff --git a/tests/basicswap/extended/test_wow.py b/tests/basicswap/extended/test_wow.py index b231bb3..8a022e0 100644 --- a/tests/basicswap/extended/test_wow.py +++ b/tests/basicswap/extended/test_wow.py @@ -189,7 +189,7 @@ class Test(BaseTest): "walletrpcport": WOW_BASE_WALLET_RPC_PORT + node_id, "walletrpcuser": "test" + str(node_id), "walletrpcpassword": "test_pass" + str(node_id), - "walletfile": "testwallet", + "wallet_name": "testwallet", "datadir": os.path.join(datadir, "xmr_" + str(node_id)), "bindir": WOW_BINDIR, } diff --git a/tests/basicswap/test_xmr.py b/tests/basicswap/test_xmr.py index 0552c9f..c483b49 100644 --- a/tests/basicswap/test_xmr.py +++ b/tests/basicswap/test_xmr.py @@ -197,7 +197,7 @@ def prepare_swapclient_dir( "walletrpcport": XMR_BASE_WALLET_RPC_PORT + node_id, "walletrpcuser": "test" + str(node_id), "walletrpcpassword": "test_pass" + str(node_id), - "walletfile": "testwallet", + "wallet_name": "testwallet", "datadir": os.path.join(datadir, "xmr_" + str(node_id)), "bindir": cfg.XMR_BINDIR, }