diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index 2433df2..9bc9b92 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -534,7 +534,8 @@ class BasicSwap(BaseApp): self.zmqSubscriber.setsockopt_string(zmq.SUBSCRIBE, 'smsg') for c in Coins: - self.setCoinConnectParams(c) + if c in chainparams: + self.setCoinConnectParams(c) if self.chain == 'mainnet': self.coin_clients[Coins.PART]['explorers'].append(ExplorerInsight( @@ -711,6 +712,8 @@ class BasicSwap(BaseApp): self.upgradeDatabase(self.db_version) for c in Coins: + if not c in chainparams: + continue self.setCoinRunParams(c) self.createCoinInterface(c) @@ -777,6 +780,8 @@ class BasicSwap(BaseApp): def stopDaemons(self): for c in Coins: + if not c in chainparams: + continue chain_client_settings = self.getChainClientSettings(c) if self.coin_clients[c]['connection_type'] == 'rpc' and chain_client_settings['manage_daemon'] is True: self.stopDaemon(c) @@ -1382,7 +1387,7 @@ class BasicSwap(BaseApp): self.mxDB.release() def getReceiveAddressForCoin(self, coin_type): - new_addr = self.coin_clients[coin_type]['interface'].getNewAddress(self.coin_clients[coin_type]['use_segwit']) + new_addr = self.ci(coin_type).getNewAddress(self.coin_clients[coin_type]['use_segwit']) self.log.debug('Generated new receive address %s for %s', new_addr, str(coin_type)) return new_addr @@ -1412,6 +1417,17 @@ class BasicSwap(BaseApp): ci = self.ci(coin_type) return ci.withdrawCoin(value, addr_to, subfee) + def withdrawParticl(self, type_from, type_to, value, addr_to, subfee): + self.log.info('withdrawParticl %s %s to %s %s %s', value, type_from, type_to, addr_to, ' subfee' if subfee else '') + + if type_from == 'plain': + type_from = 'part' + if type_to == 'plain': + type_to = 'part' + + ci = self.ci(Coins.PART) + return ci.sendTypeTo(type_from, type_to, value, addr_to, subfee) + def cacheNewAddressForCoin(self, coin_type): self.log.debug('cacheNewAddressForCoin %s', coin_type) key_str = 'receive_addr_' + chainparams[coin_type]['name'] @@ -1483,6 +1499,31 @@ class BasicSwap(BaseApp): self.mxDB.release() return addr + def getCachedStealthAddressForCoin(self, coin_type): + self.log.debug('getCachedStealthAddressForCoin %s', coin_type) + # TODO: auto refresh after used + + ci = self.ci(coin_type) + key_str = 'stealth_addr_' + ci.coin_name() + self.mxDB.acquire() + try: + session = scoped_session(self.session_factory) + try: + addr = session.query(DBKVString).filter_by(key=key_str).first().value + except Exception: + addr = ci.getNewStealthAddress() + self.log.info('Generated new stealth address for %s', coin_type) + session.add(DBKVString( + key=key_str, + value=addr + )) + session.commit() + finally: + session.close() + session.remove() + self.mxDB.release() + return addr + def getNewContractId(self): self.mxDB.acquire() try: @@ -4850,11 +4891,20 @@ class BasicSwap(BaseApp): 'synced': '{0:.2f}'.format(round(blockchaininfo['verificationprogress'], 2)), 'expected_seed': ci.knownWalletSeed(), } + + if coin == Coins.PART: + rv['anon_balance'] = walletinfo['anon_balance'] + rv['anon_unconfirmed'] = walletinfo['unconfirmed_anon'] + rv['blind_balance'] = walletinfo['blind_balance'] + rv['blind_unconfirmed'] = walletinfo['unconfirmed_blind'] + return rv def getWalletsInfo(self, opts=None): rv = {} for c in Coins: + if not c in chainparams: + continue if self.coin_clients[c]['connection_type'] == 'rpc': try: rv[c] = self.getWalletInfo(c) diff --git a/basicswap/chainparams.py b/basicswap/chainparams.py index 80b1573..9c33bba 100644 --- a/basicswap/chainparams.py +++ b/basicswap/chainparams.py @@ -21,6 +21,8 @@ class Coins(IntEnum): # DCR = 4 NMC = 5 XMR = 6 + PART_BLIND = 7 + PART_ANON = 8 chainparams = { @@ -202,6 +204,10 @@ chainparams = { class CoinInterface: def __init__(self): self._unknown_wallet_seed = True + self.setDefaults() + + def setDefaults(self): + pass def make_int(self, amount_in, r=0): return make_int(amount_in, self.exp(), r=r) diff --git a/basicswap/http_server.py b/basicswap/http_server.py index 2951a29..66b38b7 100644 --- a/basicswap/http_server.py +++ b/basicswap/http_server.py @@ -76,6 +76,8 @@ def extractDomain(url): def listAvailableExplorers(swap_client): explorers = [] for c in Coins: + if not c in chainparams: + continue for i, e in enumerate(swap_client.coin_clients[c]['explorers']): explorers.append(('{}_{}'.format(int(c), i), swap_client.coin_clients[c]['name'].capitalize() + ' - ' + extractDomain(e.base_url))) return explorers @@ -213,10 +215,13 @@ class HttpHandler(BaseHTTPRequestHandler): def page_wallets(self, url_split, post_string): swap_client = self.server.swap_client + page_data = {} messages = [] form_data = self.checkForm(post_string, 'wallets', messages) if form_data: for c in Coins: + if not c in chainparams: + continue cid = str(int(c)) if bytes('newaddr_' + cid, 'utf-8') in form_data: @@ -228,12 +233,43 @@ class HttpHandler(BaseHTTPRequestHandler): except Exception as ex: messages.append('Reseed failed ' + str(ex)) elif bytes('withdraw_' + cid, 'utf-8') in form_data: - value = form_data[bytes('amt_' + cid, 'utf-8')][0].decode('utf-8') - address = form_data[bytes('to_' + cid, 'utf-8')][0].decode('utf-8') + try: + value = form_data[bytes('amt_' + cid, 'utf-8')][0].decode('utf-8') + page_data['wd_value_' + cid] = value + except Exception as e: + messages.append('Error: 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: + messages.append('Error: Missing address') + subfee = True if bytes('subfee_' + cid, 'utf-8') in form_data else False - txid = swap_client.withdrawCoin(c, value, address, subfee) - ticker = swap_client.getTicker(c) - messages.append('Withdrew {} {} to address {}
In txid: {}'.format(value, ticker, address, txid)) + page_data['wd_subfee_' + cid] = subfee + + if c == Coins.PART: + try: + type_from = form_data[bytes('withdraw_type_from_' + cid, 'utf-8')][0].decode('utf-8') + type_to = form_data[bytes('withdraw_type_to_' + cid, 'utf-8')][0].decode('utf-8') + page_data['wd_type_from_' + cid] = type_from + page_data['wd_type_to_' + cid] = type_to + except Exception as e: + messages.append('Error: Missing type') + + if len(messages) == 0: + ticker = swap_client.getTicker(c) + if c == Coins.PART: + try: + txid = swap_client.withdrawParticl(type_from, type_to, value, address, subfee) + messages.append('Withdrew {} {} ({} to {}) to address {}
In txid: {}'.format(value, ticker, type_from, type_to, address, txid)) + except Exception as e: + messages.append('Error: {}'.format(str(e))) + else: + try: + txid = swap_client.withdrawCoin(c, value, address, subfee) + messages.append('Withdrew {} {} to address {}
In txid: {}'.format(value, ticker, address, txid)) + except Exception as e: + messages.append('Error: {}'.format(str(e))) wallets = swap_client.getWalletsInfo() @@ -249,10 +285,11 @@ class HttpHandler(BaseHTTPRequestHandler): ci = swap_client.ci(k) fee_rate, fee_src = swap_client.getFeeRateForCoin(k) est_fee = swap_client.estimateWithdrawFee(k, fee_rate) - wallets_formatted.append({ + cid = str(int(k)) + wf = { 'name': w['name'], 'version': w['version'], - 'cid': str(int(k)), + 'cid': cid, 'fee_rate': ci.format_amount(int(fee_rate * ci.COIN())), 'fee_rate_src': fee_src, 'est_fee': 'Unknown' if est_fee is None else ci.format_amount(int(est_fee * ci.COIN())), @@ -262,9 +299,32 @@ class HttpHandler(BaseHTTPRequestHandler): 'deposit_address': w['deposit_address'], 'expected_seed': w['expected_seed'], 'balance_all': float(w['balance']) + float(w['unconfirmed']), - }) + } if float(w['unconfirmed']) > 0.0: - wallets_formatted[-1]['unconfirmed'] = w['unconfirmed'] + wf['unconfirmed'] = w['unconfirmed'] + + if k == Coins.PART: + wf['stealth_address'] = swap_client.getCachedStealthAddressForCoin(Coins.PART) + wf['blind_balance'] = w['blind_balance'] + if float(w['blind_unconfirmed']) > 0.0: + wf['blind_unconfirmed'] = w['blind_unconfirmed'] + wf['anon_balance'] = w['anon_balance'] + if float(w['anon_unconfirmed']) > 0.0: + wf['anon_unconfirmed'] = w['anon_unconfirmed'] + + if 'wd_type_from_' + cid in page_data: + wf['wd_type_from'] = page_data['wd_type_from_' + cid] + if 'wd_type_to_' + cid in page_data: + wf['wd_type_to'] = page_data['wd_type_to_' + cid] + + if 'wd_value_' + cid in page_data: + wf['wd_value'] = page_data['wd_value_' + cid] + if 'wd_address_' + cid in page_data: + wf['wd_address'] = page_data['wd_address_' + cid] + if 'wd_subfee_' + cid in page_data: + wf['wd_subfee'] = page_data['wd_subfee_' + cid] + + wallets_formatted.append(wf) template = env.get_template('wallets.html') return bytes(template.render( diff --git a/basicswap/interface_part.py b/basicswap/interface_part.py index 5718142..7d358c1 100644 --- a/basicswap/interface_part.py +++ b/basicswap/interface_part.py @@ -5,6 +5,8 @@ # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. +from enum import IntEnum + from .contrib.test_framework.messages import ( CTxOutPart, ) @@ -17,11 +19,21 @@ from .interface_btc import BTCInterface from .chainparams import Coins +class BalanceTypes(IntEnum): + PLAIN = 1 + BLIND = 2 + ANON = 3 + + class PARTInterface(BTCInterface): @staticmethod def coin_type(): return Coins.PART + @staticmethod + def balance_type(): + return BalanceTypes.PLAIN + @staticmethod def witnessScaleFactor(): return 2 @@ -38,6 +50,10 @@ class PARTInterface(BTCInterface): def txoType(): return CTxOutPart + def setDefaults(self): + super().setDefaults() + self._anon_tx_ring_size = 8 # TODO: Make option + def knownWalletSeed(self): # TODO: Double check return True @@ -45,6 +61,9 @@ class PARTInterface(BTCInterface): def getNewAddress(self, use_segwit): return self.rpc_callback('getnewaddress', ['swap_receive']) + def getNewStealthAddress(self): + return self.rpc_callback('getnewstealthaddress', ['swap_stealth']) + def haveSpentIndex(self): version = self.getDaemonVersion() index_info = self.rpc_callback('getinsightinfo' if int(str(version)[:2]) > 19 else 'getindexinfo') @@ -57,5 +76,25 @@ class PARTInterface(BTCInterface): params = [addr_to, value, '', '', subfee, '', True, self._conf_target] return self.rpc_callback('sendtoaddress', params) + def sendTypeTo(self, type_from, type_to, value, addr_to, subfee): + params = [type_from, type_to, + [{'address': addr_to, 'amount': value, 'subfee': subfee}, ], + '', '', self._anon_tx_ring_size, 1, False, + {'conf_target': self._conf_target}] + return self.rpc_callback('sendtypeto', params) + def getScriptForPubkeyHash(self, pkh): return CScript([OP_DUP, OP_HASH160, pkh, OP_EQUALVERIFY, OP_CHECKSIG]) + + +class PARTInterfaceBlind(PARTInterface): + @staticmethod + def balance_type(): + return BalanceTypes.BLIND + + +class PARTInterfaceAnon(PARTInterface): + @staticmethod + def balance_type(): + return BalanceTypes.ANON + diff --git a/basicswap/js_server.py b/basicswap/js_server.py index 77713b2..5ac3da1 100644 --- a/basicswap/js_server.py +++ b/basicswap/js_server.py @@ -30,6 +30,7 @@ def js_error(self, error_str): def js_wallets(self, url_split, post_string, is_json): + # TODO: Withdrawals return bytes(json.dumps(self.server.swap_client.getWalletsInfo()), 'UTF-8') diff --git a/basicswap/templates/wallets.html b/basicswap/templates/wallets.html index b59ee92..a8566c1 100644 --- a/basicswap/templates/wallets.html +++ b/basicswap/templates/wallets.html @@ -18,11 +18,35 @@ {% else %} {% if w.unconfirmed %}{% endif %} + + +{% if w.cid == '1' %} +{% if w.blind_unconfirmed %}{% endif %} +{% if w.anon_unconfirmed %}{% endif %} +{% endif %} + + {% if w.expected_seed != true %}{% endif %} +{% if w.cid == '1' %} + +{% endif %} - + +{% if w.cid == '1' %} + +{% endif %}
Balance:{{ w.balance }}Unconfirmed:{{ w.unconfirmed }}
Blind Balance:{{ w.blind_balance }}Blind Unconfirmed:{{ w.blind_unconfirmed }}
Anon Balance:{{ w.anon_balance }}Anon Unconfirmed:{{ w.anon_unconfirmed }}
Blocks:{{ w.blocks }}
Synced:{{ w.synced }}
Expected Seed:{{ w.expected_seed }}
Stealth Address{{ w.stealth_address }}
{{ w.deposit_address }}
Amount: Address: Subtract fee:
Amount: Address: Subtract fee:
Type From, To + +
Fee Rate:{{ w.fee_rate }}Est Fee:{{ w.est_fee }}
{% endif %}