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 %}
| Balance: | {{ w.balance }} | {% if w.unconfirmed %}Unconfirmed: | {{ w.unconfirmed }} | {% endif %}
| Blind Balance: | {{ w.blind_balance }} | {% if w.blind_unconfirmed %}Blind Unconfirmed: | {{ w.blind_unconfirmed }} | {% endif %}
| Anon Balance: | {{ w.anon_balance }} | {% if w.anon_unconfirmed %}Anon Unconfirmed: | {{ w.anon_unconfirmed }} | {% endif %}
| Blocks: | {{ w.blocks }} | ||
| Synced: | {{ w.synced }} | ||
| Expected Seed: | {{ w.expected_seed }} | {% if w.expected_seed != true %}{% endif %} | |
| 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 }} |